在 Android 应用程序中,SQLite 是默认的嵌入式数据库解决方案,Android 系统为开发者提供了相应的 API 来管理 SQLite 数据库。通过使用 SQLiteOpenHelper 类和 SQLiteDatabase 类,开发者可以方便地创建、查询、更新和删除数据库中的数据。

以下是关于如何在 Android 中使用 SQLite 的详细介绍和示例。


一、在 Android 中使用 SQLite 的步骤

1. 创建 SQLiteOpenHelper 类

SQLiteOpenHelper 是一个辅助类,用于管理数据库的创建和版本管理。它提供了两个重要的方法:

  • onCreate(SQLiteDatabase db):当数据库第一次创建时调用。
  • onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion):当数据库需要升级时调用。
  • 示例:创建 SQLiteOpenHelper 子类

    public class MyDatabaseHelper extends SQLiteOpenHelper {
        private static final String DATABASE_NAME = "MyDatabase.db";  // 数据库名称
        private static final int DATABASE_VERSION = 1;                // 数据库版本
    
        public MyDatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }
    
        // 第一次创建数据库时调用
        @Override
        public void onCreate(SQLiteDatabase db) {
            // 创建数据库中的表
            String CREATE_TABLE = "CREATE TABLE users ("
                    + "id INTEGER PRIMARY KEY AUTOINCREMENT,"
                    + "name TEXT NOT NULL,"
                    + "age INTEGER,"
                    + "email TEXT);";
            db.execSQL(CREATE_TABLE);
        }
    
        // 当数据库版本升级时调用
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            // 如果表已存在则删除,重新创建
            db.execSQL("DROP TABLE IF EXISTS users");
            onCreate(db);
        }
    }
    
  • DATABASE_NAME 定义了数据库的名称。
  • DATABASE_VERSION 是数据库的版本号,每当数据库结构发生变化时,需要升级版本号。
  • onCreate() 方法在数据库首次创建时调用,执行 SQL 语句来创建数据库中的表。
  • onUpgrade() 方法在数据库需要升级时调用,可以用于处理表结构的变更。
  • 2. 打开数据库

    使用 SQLiteOpenHelper 类时,可以通过调用 getWritableDatabase()getReadableDatabase() 方法来打开数据库。前者允许执行写操作,而后者只允许执行读操作。

    MyDatabaseHelper dbHelper = new MyDatabaseHelper(context);
    SQLiteDatabase db = dbHelper.getWritableDatabase();
    
    3. 插入数据

    使用 insert() 方法将数据插入到表中。

    // 创建一个 ContentValues 对象,用于存储键值对
    ContentValues values = new ContentValues();
    values.put("name", "Alice");
    values.put("age", 25);
    values.put("email", "alice@example.com");
    
    // 插入数据
    long newRowId = db.insert("users", null, values);
    
  • ContentValues 类用于保存列名与列值的映射。
  • insert() 方法将数据插入到指定的表中,返回插入数据的行 ID。
  • 4. 查询数据

    使用 query() 方法从数据库中查询数据。该方法返回一个 Cursor 对象,用于遍历查询结果。

    // 定义要查询的列
    String[] projection = {
        "id",
        "name",
        "age",
        "email"
    };
    
    // 查询数据
    Cursor cursor = db.query(
        "users",   // 表名
        projection,   // 要返回的列
        null,         // WHERE 子句
        null,         // WHERE 子句的参数
        null,         // GROUP BY 子句
        null,         // HAVING 子句
        null          // 排序方式
    );
    
    // 遍历查询结果
    while (cursor.moveToNext()) {
        long userId = cursor.getLong(cursor.getColumnIndexOrThrow("id"));
        String userName = cursor.getString(cursor.getColumnIndexOrThrow("name"));
        int userAge = cursor.getInt(cursor.getColumnIndexOrThrow("age"));
        String userEmail = cursor.getString(cursor.getColumnIndexOrThrow("email"));
    }
    
    // 关闭游标
    cursor.close();
    
  • projection 定义了需要查询的列。
  • Cursor 对象用于从查询结果中提取数据,moveToNext() 方法用于遍历结果集。
  • 5. 更新数据

    使用 update() 方法更新表中的数据。

    // 创建 ContentValues 对象
    ContentValues values = new ContentValues();
    values.put("age", 26);
    
    // 更新记录
    String selection = "name = ?";
    String[] selectionArgs = { "Alice" };
    
    int count = db.update(
        "users",    // 表名
        values,     // 要更新的值
        selection,  // WHERE 子句
        selectionArgs  // WHERE 子句的参数
    );
    
  • update() 方法根据指定的条件更新表中的记录,返回受影响的行数。
  • 6. 删除数据

    使用 delete() 方法删除表中的数据。

    // 定义删除条件
    String selection = "name = ?";
    String[] selectionArgs = { "Alice" };
    
    // 删除记录
    int deletedRows = db.delete("users", selection, selectionArgs);
    
  • delete() 方法根据指定的条件删除表中的记录,返回删除的行数。

  • 二、数据库操作的异步处理

    在 Android 中,数据库操作通常需要在子线程中进行,以避免在主线程上执行耗时操作,防止阻塞 UI。

    可以使用 AsyncTaskHandlerThreadJetpack 提供的 Room 库来简化和管理异步的数据库操作。

    使用 Room 作为 SQLite 的抽象层

    Room 是 Android Jetpack 提供的一个持久性库,它简化了与 SQLite 的交互,并且支持异步查询。使用 Room 可以避免手动编写大量的 SQL 代码,并提供类型安全的 API。

    Room 的基础组件:
    1. 实体 (Entity):对应数据库中的表。
    2. DAO (Data Access Object):定义数据操作方法。
    3. 数据库 (Database):数据库持有者,管理实体和 DAO。

    示例:使用 Room 管理数据库

    1. 定义实体类:
    @Entity
    public class User {
        @PrimaryKey(autoGenerate = true)
        public int id;
        public String name;
        public int age;
        public String email;
    }
    
    1. 定义 DAO 接口:
    @Dao
    public interface UserDao {
        @Insert
        void insertUser(User user);
    
        @Query("SELECT * FROM User WHERE id = :id")
        User getUserById(int id);
    
        @Update
        void updateUser(User user);
    
        @Delete
        void deleteUser(User user);
    }
    
    1. 创建数据库类:
    @Database(entities = {User.class}, version = 1)
    public abstract class AppDatabase extends RoomDatabase {
        public abstract UserDao userDao();
    }
    
    1. 初始化 Room 数据库:
    AppDatabase db = Room.databaseBuilder(getApplicationContext(),
            AppDatabase.class, "MyDatabase").build();
    
    // 在子线程中执行数据库操作
    new Thread(() -> {
        User user = new User();
        user.name = "Alice";
        user.age = 25;
        user.email = "alice@example.com";
        db.userDao().insertUser(user);
    }).start();
    

    三、SQLite 数据库备份和恢复

    由于 SQLite 数据存储在一个单一的文件中,因此备份和恢复操作非常简单。可以直接复制数据库文件来备份或恢复数据。

    备份数据库文件:

    File dbFile = context.getDatabasePath("MyDatabase.db");
    File backupFile = new File(Environment.getExternalStorageDirectory(), "MyDatabase_backup.db");
    
    try (FileChannel src = new FileInputStream(dbFile).getChannel();
         FileChannel dst = new FileOutputStream(backupFile).getChannel()) {
        dst.transferFrom(src, 0, src.size());
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    恢复数据库文件:

    File dbFile = context.getDatabasePath("MyDatabase.db");
    File backupFile = new File(Environment.getExternalStorageDirectory(), "MyDatabase_backup.db");
    
    try (FileChannel src = new FileInputStream(backupFile).getChannel();
         FileChannel dst = new FileOutputStream(dbFile).getChannel()) {
        dst.transferFrom(src, 0, src.size());
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    四、SQLite 数据库的性能优化

    1. 使用索引:为经常查询的列创建索引可以显著提升查询速度。

      CREATE INDEX idx_user_name ON users(name);
      
    2. 事务处理:将多个操作放在一个事务中执行,可以减少磁盘 I/O,从而提高性能。

      BEGIN TRANSACTION;
      -- 执行多条 SQL 语句
      COMMIT;
      
    3. 按需加载数据:在查询大量数据时,使用分页查询减少内存消耗。

      SELECT * FROM users LIMIT 10 OFFSET 20;
      

    通过上述步骤,开发者可以轻松在 Android 中集成并使用 SQLite 数据库。为了进一步深入理解,接下来将继续介绍 SQLite 在 Android 中的性能优化、常见的最佳实践,以及在不同场景下的高级用法。


    五、SQLite 在 Android 中的性能优化

    SQLite 数据库在 Android 应用中处理小规模数据时表现非常优异,但当数据量增大时,可能会面临性能问题。以下是几种常见的性能优化方法:

    1. 使用事务

    将多条 SQL 操作放在一个事务中可以显著提升数据库操作的效率。SQLite 的每一条独立的插入或更新操作都会触发一次磁盘写操作,增加了 I/O 的开销。如果将多条插入或更新操作包裹在同一个事务中,则只会有一次磁盘写入,减少了 I/O 操作,极大提高了性能。

    示例:使用事务插入多条记录

    db.beginTransaction();
    try {
        for (User user : userList) {
            ContentValues values = new ContentValues();
            values.put("name", user.getName());
            values.put("age", user.getAge());
            values.put("email", user.getEmail());
            db.insert("users", null, values);
        }
        db.setTransactionSuccessful();  // 标记事务成功
    } finally {
        db.endTransaction();  // 结束事务
    }
    
  • db.beginTransaction() 开启事务。
  • db.setTransactionSuccessful() 在事务成功时调用。
  • db.endTransaction() 结束事务。
  • 2. 使用索引 (Indexing)

    为常用查询的列创建索引,可以加速查询速度。索引本质上是表中特定列的排序结构,它减少了查询时的扫描行数。

    创建索引的示例:

    CREATE INDEX idx_user_name ON users(name);
    

    注意,虽然索引可以加快查询速度,但也会增加插入和更新操作的开销,因此应根据需要平衡索引的使用。

    3. 减少数据查询

    在查询数据时,避免使用 SELECT * 来检索所有列,尽量只查询需要的列,减少不必要的数据传输。

    示例:只查询需要的列

    String[] projection = {
        "id",
        "name"
    };
    Cursor cursor = db.query(
        "users",
        projection,
        null,
        null,
        null,
        null,
        null
    );
    
    4. 使用批量操作

    在处理大批量数据操作时,使用批量插入、更新或删除可以提高操作效率。批量操作可以通过事务和 SQLiteStatement 提高性能。

    批量插入示例:

    SQLiteStatement stmt = db.compileStatement("INSERT INTO users (name, age, email) VALUES (?, ?, ?)");
    
    db.beginTransaction();
    try {
        for (User user : userList) {
            stmt.bindString(1, user.getName());
            stmt.bindLong(2, user.getAge());
            stmt.bindString(3, user.getEmail());
            stmt.execute();
            stmt.clearBindings();
        }
        db.setTransactionSuccessful();
    } finally {
        db.endTransaction();
    }
    
    5. 使用 PRAGMA 进行性能调优

    SQLite 提供了许多 PRAGMA 命令,允许开发者动态配置数据库的行为,从而提升性能。例如:

  • PRAGMA synchronous = OFF;:关闭同步写入,提升写入速度(在数据一致性不是首要关注时使用)。
  • PRAGMA journal_mode = WAL;:将数据库设置为“写入时日志”模式,这样可以在高并发读写的场景下提升性能。
  • 示例:设置 WAL 模式

    PRAGMA journal_mode = WAL;
    

    在 WAL 模式下,写操作不会直接覆盖原数据,而是记录在日志中,并在空闲时间将日志内容合并回主数据库文件,从而提高并发读写的性能。


    六、SQLite 的最佳实践

    1. 善用 ContentValues 和 Cursor
  • 使用 ContentValues 来存储插入或更新的数据,不仅简化了代码,还提高了可读性。
  • 使用 Cursor 遍历查询结果时,始终在使用后关闭 Cursor,防止内存泄漏。
  • 关闭 Cursor 示例:

    Cursor cursor = db.query("users", null, null, null, null, null, null);
    try {
        while (cursor.moveToNext()) {
            // 获取数据
        }
    } finally {
        cursor.close();  // 确保 cursor 关闭
    }
    
    2. 使用 Room 框架

    在复杂的 Android 应用程序中,直接使用 SQLite 可能会导致 SQL 语句冗长、难以维护。为了简化与 SQLite 的交互,推荐使用 Room 框架,它提供了类型安全的 API,并能与 LiveData 和 RxJava 轻松集成,实现异步查询操作。

    3. 避免在主线程进行数据库操作

    数据库操作可能会耗时较长,尤其是在处理大数据集或网络存储时。为了避免 UI 卡顿,务必在工作线程中执行数据库操作。可以使用 AsyncTaskExecutorService 或 Room 提供的异步操作方法。

    示例:使用 ExecutorService 进行异步操作

    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.execute(() -> {
        // 在后台线程中执行数据库操作
        db.userDao().insertUser(user);
    });
    
    4. 确保数据库版本管理

    当数据库的结构发生变化时,需要通过增加数据库的版本号并在 onUpgrade() 中进行适当的迁移操作,以确保数据的完整性。

    版本升级示例:

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        if (oldVersion < 2) {
            db.execSQL("ALTER TABLE users ADD COLUMN phone TEXT");
        }
    }
    

    通过这种方式,可以安全地向数据库表中添加新字段,而不会破坏现有数据。


    七、SQLite 高级用法

    1. 数据库加密

    Android 系统默认的 SQLite 数据库是不加密的。如果需要对敏感数据进行加密,可以使用 SQLCipher 这样的第三方库。SQLCipher 是对 SQLite 的增强版,提供了对数据库文件的透明加密支持。

    使用 SQLCipher 加密数据库:

    1. 添加依赖:
    implementation 'net.zetetic:android-database-sqlcipher:4.5.0'
    
    1. 创建或打开加密数据库:
    SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(databaseFile, "your-password", null);
    

    通过加密,数据在数据库文件中存储时将是加密的,即使文件被拷贝,未经授权的用户也无法直接查看数据。

    2. 数据库迁移

    当应用程序的数据库架构需要更新时,必须处理好数据迁移。Room 框架提供了方便的迁移机制,可以帮助开发者在数据库版本升级时保持数据的完整性。

    Room 迁移示例:

    Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            database.execSQL("ALTER TABLE users ADD COLUMN phone TEXT");
        }
    };
    
    Room.databaseBuilder(context, AppDatabase.class, "MyDatabase")
        .addMigrations(MIGRATION_1_2)
        .build();
    

    通过 Room 的迁移功能,数据库的版本升级变得更加安全和可控。

    3. 数据库备份与恢复

    SQLite 的备份和恢复操作可以通过文件操作来实现。开发者可以直接复制 SQLite 数据库文件以创建备份,或者通过将文件移动到安全的存储位置来进行恢复。

    示例:备份数据库文件到外部存储

    File dbFile = context.getDatabasePath("MyDatabase.db");
    File backupFile = new File(Environment.getExternalStorageDirectory(), "MyDatabase_backup.db");
    
    try (FileChannel src = new FileInputStream(dbFile).getChannel();
         FileChannel dst = new FileOutputStream(backupFile).getChannel()) {
        dst.transferFrom(src, 0, src.size());
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    示例:从备份文件恢复数据库

    File dbFile = context.getDatabasePath("MyDatabase.db");
    File backupFile = new File(Environment.getExternalStorageDirectory(), "MyDatabase_backup.db");
    
    try (FileChannel src = new FileInputStream(backupFile).getChannel();
         FileChannel dst = new FileOutputStream(dbFile).getChannel()) {
        dst.transferFrom(src, 0, src.size());
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    总结

    SQLite 是一个轻量、可靠、简单易用的数据库解决方案,特别适合嵌入式系统和移动应用中的数据存储需求。通过 Android 提供的 API,开发者可以轻松地实现数据持久化功能。在实际开发中

    作者:榴月~

    物联沃分享整理
    物联沃-IOTWORD物联网 » SQLite在安卓中的应用

    发表回复