MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Android SQLite应用:创建新工程与添加数据库

2024-08-164.7k 阅读

1. 创建新的 Android 工程

在开始使用 SQLite 数据库之前,我们首先要创建一个新的 Android 工程。这里以 Android Studio 为例,详细介绍创建过程。

1.1 打开 Android Studio

启动 Android Studio,如果你是首次打开,可能会看到欢迎界面。如果已经打开过其他项目,你可以通过点击菜单栏的 File -> New -> New Project 来创建新项目。

1.2 选择项目模板

在弹出的 New Project 对话框中,你需要选择项目模板。这里有多种模板可供选择,例如 Empty Activity(空白活动)、Basic Activity(基础活动)等。对于简单的 SQLite 应用示例,我们选择 Empty Activity 模板,因为它最为简洁,便于我们专注于数据库相关的操作。选择好模板后,点击 Next

1.3 配置项目

接下来进入项目配置页面,你需要填写以下信息:

  • Name:项目名称,例如 “SQLiteExample”。这是你项目在本地和最终应用展示给用户的名称。
  • Package name:包名,通常遵循反域名命名规则,例如 “com.example.sqliteexample”。包名在 Android 系统中用于唯一标识你的应用,确保它的唯一性非常重要。
  • Save location:项目保存路径,你可以选择本地磁盘上的任意目录来保存你的项目文件。
  • Language:选择项目的编程语言,Android 支持 Java 和 Kotlin 两种主流语言。这里我们以 Java 为例进行讲解,如果选择 Kotlin,部分代码语法会有所不同,但整体思路一致。
  • Minimum SDK:最低支持的 Android 系统版本。根据你的应用目标受众和功能需求选择合适的版本。较低的版本可以覆盖更多用户,但可能无法使用一些新的 API 特性。

填写完上述信息后,点击 Finish,Android Studio 将会开始创建项目,这个过程可能需要一些时间,取决于你的电脑性能和网络状况(如果需要下载相关依赖)。

2. 理解 SQLite 在 Android 中的作用

SQLite 是一个轻量级的嵌入式数据库,它在 Android 开发中扮演着重要的角色。以下是 SQLite 在 Android 应用中的几个关键作用:

2.1 本地数据存储

在很多情况下,Android 应用需要在本地设备上存储数据。例如,一个待办事项应用需要保存用户创建的任务列表,一个笔记应用需要存储用户的笔记内容等。SQLite 提供了一种可靠且高效的方式来在设备上持久化这些数据,即使应用关闭或设备重启,数据依然能够保留。

2.2 数据管理与查询

SQLite 支持标准的 SQL 查询语言,这使得开发者可以方便地对存储的数据进行各种操作,如插入新数据、更新现有数据、删除数据以及查询符合特定条件的数据。通过 SQL 查询,我们可以实现复杂的数据检索逻辑,满足应用多样化的需求。

2.3 轻量级与资源友好

与一些大型的数据库系统(如 MySQL、Oracle 等)相比,SQLite 非常轻量级。它不需要独立的服务器进程,直接嵌入到应用程序中运行。这意味着 SQLite 对设备资源的占用极少,非常适合资源有限的移动设备,如手机和平板电脑。

2.4 跨平台性

虽然我们这里讨论的是 Android 平台上的 SQLite 应用,但 SQLite 本身具有很好的跨平台性。它可以在多种操作系统上使用,包括 Windows、Linux、macOS 等。这使得开发者在不同平台之间迁移应用或共享数据库相关代码时更加容易。

3. 在 Android 工程中添加 SQLite 数据库

在创建好 Android 工程后,我们需要在工程中添加 SQLite 数据库相关的代码。Android 提供了 SQLiteOpenHelper 类来帮助我们管理数据库的创建、升级等操作。以下是具体的实现步骤:

3.1 创建 SQLiteOpenHelper 子类

首先,我们需要创建一个继承自 SQLiteOpenHelper 的子类。这个子类将负责数据库的创建和版本管理。在项目的 java 目录下,找到你的应用包名对应的文件夹,右键点击,选择 New -> Java Class,命名为例如 “DatabaseHelper”。代码如下:

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DatabaseHelper extends SQLiteOpenHelper {

    // 数据库名称
    private static final String DATABASE_NAME = "example.db";
    // 数据库版本
    private static final int DATABASE_VERSION = 1;

    // 表名
    public static final String TABLE_NAME = "users";
    // 列名
    public static final String COLUMN_ID = "_id";
    public static final String COLUMN_NAME = "name";
    public static final String COLUMN_AGE = "age";

    // 创建表的 SQL 语句
    private static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" +
            COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
            COLUMN_NAME + " TEXT, " +
            COLUMN_AGE + " INTEGER)";

    public DatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        // 执行创建表的 SQL 语句
        db.execSQL(CREATE_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // 当数据库版本更新时,这里可以实现数据迁移等操作
        // 简单示例:删除旧表,创建新表
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
        onCreate(db);
    }
}

在上述代码中:

  • 我们定义了数据库的名称 DATABASE_NAME 和版本 DATABASE_VERSION
  • 定义了表名 TABLE_NAME 以及表中的列名 COLUMN_IDCOLUMN_NAMECOLUMN_AGE
  • CREATE_TABLE 字符串中编写了创建表的 SQL 语句,该表包含一个自增长的主键 _id,以及 nameage 两个字段。
  • DatabaseHelper 的构造函数中,调用了父类 SQLiteOpenHelper 的构造函数,传入上下文、数据库名称、游标工厂(这里为 null)和版本号。
  • onCreate 方法在数据库首次创建时被调用,我们在其中执行创建表的 SQL 语句。
  • onUpgrade 方法在数据库版本发生变化时被调用,这里我们简单地删除旧表并重新创建新表,实际应用中可能需要更复杂的数据迁移逻辑。

3.2 使用 DatabaseHelper

在创建好 DatabaseHelper 类后,我们可以在 Activity 或其他组件中使用它来操作数据库。以下是在 MainActivity 中使用 DatabaseHelper 的示例代码:

import android.os.Bundle;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    private DatabaseHelper databaseHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 创建 DatabaseHelper 实例
        databaseHelper = new DatabaseHelper(this);

        // 这里可以进行数据库的操作,例如插入数据
        long result = databaseHelper.getWritableDatabase().insert(
                DatabaseHelper.TABLE_NAME,
                null,
                ContentValuesUtils.createContentValues("John", 25)
        );
        if (result != -1) {
            Toast.makeText(this, "数据插入成功", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "数据插入失败", Toast.LENGTH_SHORT).show();
        }
    }
}

同时,我们还创建了一个 ContentValuesUtils 工具类来方便地创建 ContentValues 对象,代码如下:

import android.content.ContentValues;

public class ContentValuesUtils {
    public static ContentValues createContentValues(String name, int age) {
        ContentValues values = new ContentValues();
        values.put(DatabaseHelper.COLUMN_NAME, name);
        values.put(DatabaseHelper.COLUMN_AGE, age);
        return values;
    }
}

在上述 MainActivity 代码中:

  • 首先声明了一个 DatabaseHelper 类型的成员变量 databaseHelper
  • onCreate 方法中,创建了 DatabaseHelper 的实例。
  • 通过调用 databaseHelper.getWritableDatabase() 获取一个可写的数据库实例,并使用 insert 方法向 users 表中插入一条数据。insert 方法的第一个参数是表名,第二个参数是 nullColumnHack(当 ContentValues 为空时使用,这里为 null),第三个参数是包含要插入数据的 ContentValues 对象,我们通过 ContentValuesUtils.createContentValues 方法创建了这个对象。
  • 根据 insert 方法的返回值判断数据插入是否成功,并通过 Toast 提示用户。

4. SQLite 数据库的基本操作

在 Android 应用中,使用 SQLite 数据库时,我们经常需要进行一些基本操作,如插入数据、查询数据、更新数据和删除数据。下面详细介绍这些操作的实现方法。

4.1 插入数据

除了前面示例中使用的 insert 方法外,我们还可以使用 execSQL 方法来执行插入数据的 SQL 语句。以下是两种方式的示例:

使用 insert 方法

ContentValues values = new ContentValues();
values.put(DatabaseHelper.COLUMN_NAME, "Jane");
values.put(DatabaseHelper.COLUMN_AGE, 30);
long result = databaseHelper.getWritableDatabase().insert(
        DatabaseHelper.TABLE_NAME,
        null,
        values
);
if (result != -1) {
    Toast.makeText(this, "数据插入成功", Toast.LENGTH_SHORT).show();
} else {
    Toast.makeText(this, "数据插入失败", Toast.LENGTH_SHORT).show();
}

使用 execSQL 方法

String insertQuery = "INSERT INTO " + DatabaseHelper.TABLE_NAME + " (" +
        DatabaseHelper.COLUMN_NAME + ", " +
        DatabaseHelper.COLUMN_AGE + ") VALUES ('Bob', 28)";
databaseHelper.getWritableDatabase().execSQL(insertQuery);
Toast.makeText(this, "数据插入成功", Toast.LENGTH_SHORT).show();

使用 insert 方法更面向对象,并且对 ContentValues 的处理较为方便,适合大多数情况。而使用 execSQL 方法则更加灵活,可以执行复杂的插入语句,例如插入多条数据的批量插入操作。

4.2 查询数据

查询数据是 SQLite 数据库操作中非常重要的一部分。Android 提供了多种方式来查询数据,下面以查询 users 表中所有数据为例,介绍几种常见的查询方法。

使用 query 方法

Cursor cursor = databaseHelper.getReadableDatabase().query(
        DatabaseHelper.TABLE_NAME,
        null,
        null,
        null,
        null,
        null,
        null
);
if (cursor.moveToFirst()) {
    do {
        int id = cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COLUMN_ID));
        String name = cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_NAME));
        int age = cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COLUMN_AGE));
        Log.d("QueryResult", "ID: " + id + ", Name: " + name + ", Age: " + age);
    } while (cursor.moveToNext());
}
cursor.close();

在上述代码中:

  • query 方法的第一个参数是表名。
  • 第二个参数是要查询的列名数组,如果为 null 则表示查询所有列。
  • 第三个参数是 selection,用于指定查询条件,例如 “age > 25”。
  • 第四个参数是 selectionArgs,用于为 selection 中的占位符提供具体值。
  • 第五个参数是 groupBy,用于分组查询。
  • 第六个参数是 having,用于对分组结果进行过滤。
  • 第七个参数是 orderBy,用于指定排序方式。

使用 rawQuery 方法

String query = "SELECT * FROM " + DatabaseHelper.TABLE_NAME;
Cursor cursor = databaseHelper.getReadableDatabase().rawQuery(query, null);
if (cursor.moveToFirst()) {
    do {
        int id = cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COLUMN_ID));
        String name = cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_NAME));
        int age = cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COLUMN_AGE));
        Log.d("QueryResult", "ID: " + id + ", Name: " + name + ", Age: " + age);
    } while (cursor.moveToNext());
}
cursor.close();

rawQuery 方法允许我们直接执行 SQL 查询语句,更加灵活,但需要手动编写完整的 SQL 语句。

4.3 更新数据

更新数据可以通过 update 方法来实现。以下是更新 users 表中某个用户年龄的示例:

ContentValues values = new ContentValues();
values.put(DatabaseHelper.COLUMN_AGE, 35);
String whereClause = DatabaseHelper.COLUMN_NAME + " =?";
String[] whereArgs = {"John"};
int result = databaseHelper.getWritableDatabase().update(
        DatabaseHelper.TABLE_NAME,
        values,
        whereClause,
        whereArgs
);
if (result > 0) {
    Toast.makeText(this, "数据更新成功", Toast.LENGTH_SHORT).show();
} else {
    Toast.makeText(this, "数据更新失败", Toast.LENGTH_SHORT).show();
}

在上述代码中:

  • update 方法的第一个参数是表名。
  • 第二个参数是包含要更新数据的 ContentValues 对象。
  • 第三个参数是 whereClause,用于指定更新条件,这里使用占位符 ?
  • 第四个参数是 whereArgs,用于为 whereClause 中的占位符提供具体值。

4.4 删除数据

删除数据可以通过 delete 方法来实现。以下是删除 users 表中某个用户数据的示例:

String whereClause = DatabaseHelper.COLUMN_NAME + " =?";
String[] whereArgs = {"Jane"};
int result = databaseHelper.getWritableDatabase().delete(
        DatabaseHelper.TABLE_NAME,
        whereClause,
        whereArgs
);
if (result > 0) {
    Toast.makeText(this, "数据删除成功", Toast.LENGTH_SHORT).show();
} else {
    Toast.makeText(this, "数据删除失败", Toast.LENGTH_SHORT).show();
}

在上述代码中:

  • delete 方法的第一个参数是表名。
  • 第二个参数是 whereClause,用于指定删除条件。
  • 第三个参数是 whereArgs,用于为 whereClause 中的占位符提供具体值。

5. 数据库事务处理

在 SQLite 数据库操作中,事务处理是非常重要的概念。事务可以确保一组数据库操作要么全部成功执行,要么全部失败回滚,从而保证数据的一致性和完整性。以下是在 Android 中使用 SQLite 进行事务处理的示例。

假设我们有一个场景,需要向 users 表中插入两条数据,并且这两条数据的插入操作必须要么全部成功,要么全部失败。代码如下:

SQLiteDatabase db = databaseHelper.getWritableDatabase();
try {
    db.beginTransaction();

    ContentValues values1 = new ContentValues();
    values1.put(DatabaseHelper.COLUMN_NAME, "Alice");
    values1.put(DatabaseHelper.COLUMN_AGE, 22);
    long result1 = db.insert(DatabaseHelper.TABLE_NAME, null, values1);

    ContentValues values2 = new ContentValues();
    values2.put(DatabaseHelper.COLUMN_NAME, "Eve");
    values2.put(DatabaseHelper.COLUMN_AGE, 24);
    long result2 = db.insert(DatabaseHelper.TABLE_NAME, null, values2);

    if (result1 != -1 && result2 != -1) {
        db.setTransactionSuccessful();
    } else {
        throw new RuntimeException("插入数据失败");
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    db.endTransaction();
}

在上述代码中:

  • 首先通过 databaseHelper.getWritableDatabase() 获取一个可写的数据库实例。
  • 使用 db.beginTransaction() 开始一个事务。
  • 分别执行两条插入数据的操作,并根据插入结果判断是否全部成功。
  • 如果两条插入操作都成功(即 result1 != -1result2 != -1),则调用 db.setTransactionSuccessful() 标记事务成功。
  • 如果有任何一条插入操作失败,则抛出一个运行时异常,事务会在 finally 块中回滚。
  • 无论事务执行过程中是否发生异常,最终都会执行 db.endTransaction() 来结束事务。如果事务被标记为成功(通过 setTransactionSuccessful),则事务中的所有操作会被提交到数据库;否则,事务中的所有操作会被回滚,数据库状态保持不变。

6. SQLite 数据库的优化

在 Android 应用中使用 SQLite 数据库时,为了提高应用的性能和响应速度,我们需要对数据库操作进行优化。以下是一些常见的优化方法:

6.1 合理设计数据库表结构

数据库表结构的设计直接影响到查询和数据操作的效率。在设计表结构时,应遵循以下原则:

  • 避免冗余数据:尽量减少数据的重复存储,以节省存储空间并避免数据不一致的问题。例如,如果一个用户表中有多个地址字段,而这些地址可能在其他地方也会用到,那么可以考虑将地址信息单独提取出来,建立一个地址表,并通过外键与用户表关联。
  • 适当使用索引:索引可以加快查询速度,但过多的索引也会增加插入、更新和删除操作的时间,并且会占用更多的存储空间。因此,要根据实际查询需求,为经常用于查询条件的列创建索引。例如,如果经常根据用户的姓名查询用户信息,那么可以为 name 列创建索引。

6.2 批量操作数据

在进行插入、更新或删除操作时,如果需要处理大量数据,尽量采用批量操作的方式,而不是逐条操作。例如,在插入多条数据时,可以使用 execSQL 方法执行一条包含多条插入语句的 SQL 语句,或者使用 SQLiteStatement 进行批量插入。以下是使用 SQLiteStatement 进行批量插入的示例:

SQLiteDatabase db = databaseHelper.getWritableDatabase();
String insertQuery = "INSERT INTO " + DatabaseHelper.TABLE_NAME + " (" +
        DatabaseHelper.COLUMN_NAME + ", " +
        DatabaseHelper.COLUMN_AGE + ") VALUES (?,?)";
SQLiteStatement statement = db.compileStatement(insertQuery);
for (int i = 0; i < dataList.size(); i++) {
    User user = dataList.get(i);
    statement.bindString(1, user.getName());
    statement.bindLong(2, user.getAge());
    statement.executeInsert();
}
statement.close();

在上述代码中,首先通过 compileStatement 方法编译插入语句,然后在循环中通过 bindStringbindLong 方法为占位符赋值,并执行 executeInsert 方法插入数据。这种方式比逐条使用 insert 方法插入数据要高效得多。

6.3 及时关闭数据库连接

在使用完数据库后,应及时关闭数据库连接,以释放资源。特别是在频繁进行数据库操作的情况下,如果不及时关闭连接,可能会导致内存泄漏和资源耗尽的问题。在 Android 中,SQLiteDatabase 类提供了 close 方法来关闭数据库连接。例如,在 Activity 的 onDestroy 方法中关闭数据库连接:

@Override
protected void onDestroy() {
    super.onDestroy();
    if (databaseHelper != null) {
        databaseHelper.close();
    }
}

6.4 使用缓存

对于一些不经常变化的数据,可以考虑使用缓存来减少对数据库的查询次数。例如,可以在内存中使用 HashMap 或其他缓存机制来存储部分数据,当需要查询这些数据时,先从缓存中查找,如果缓存中没有,则再从数据库中查询,并将查询结果存入缓存。这样可以提高应用的响应速度,减少数据库的负载。

7. 常见问题及解决方法

在使用 Android SQLite 数据库的过程中,可能会遇到一些常见问题。下面介绍一些常见问题及其解决方法。

7.1 数据库版本升级问题

当应用需要对数据库结构进行修改,例如添加新表、新列或修改现有表结构时,就需要进行数据库版本升级。在 SQLiteOpenHelperonUpgrade 方法中,我们可以实现数据迁移逻辑。例如,如果要在 users 表中添加一个新列 email

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    if (oldVersion < 2) {
        String addColumnQuery = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN " +
                "email TEXT";
        db.execSQL(addColumnQuery);
    }
}

在上述代码中,首先判断当前数据库版本是否小于目标版本(这里假设目标版本为 2),如果是,则执行添加新列的 SQL 语句。

7.2 数据库操作异常

在进行数据库操作时,可能会遇到各种异常,如 SQLException。常见的原因包括 SQL 语句语法错误、数据库文件损坏等。当遇到异常时,首先要检查 SQL 语句是否正确,可以通过打印异常信息来获取具体的错误原因。例如:

try {
    String query = "SELECT * FROM " + DatabaseHelper.TABLE_NAME + " WHERE age > 100";
    Cursor cursor = databaseHelper.getReadableDatabase().rawQuery(query, null);
    // 处理查询结果
    cursor.close();
} catch (SQLException e) {
    e.printStackTrace();
    Log.e("DatabaseError", "SQL 语句错误: " + e.getMessage());
}

7.3 数据一致性问题

在多线程环境下操作数据库时,可能会出现数据一致性问题。为了避免这种情况,可以使用数据库事务来确保一组操作的原子性,或者使用锁机制来控制对数据库的访问。例如,在多线程插入数据时,可以使用 synchronized 关键字来同步访问数据库:

private static final Object lock = new Object();

public void insertDataInThread(User user) {
    synchronized (lock) {
        SQLiteDatabase db = databaseHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(DatabaseHelper.COLUMN_NAME, user.getName());
        values.put(DatabaseHelper.COLUMN_AGE, user.getAge());
        db.insert(DatabaseHelper.TABLE_NAME, null, values);
        db.close();
    }
}

在上述代码中,通过 synchronized 关键字对数据库操作进行同步,确保在同一时间只有一个线程可以访问数据库,从而避免数据一致性问题。

通过以上步骤和方法,我们可以在 Android 应用中成功创建和使用 SQLite 数据库,并对其进行各种操作和优化,以满足应用的需求。同时,在遇到问题时,能够根据具体情况进行分析和解决,确保数据库的稳定运行。