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

Android SQLite开发:SQLiteOpenHelper类详解

2022-04-156.1k 阅读

1. SQLiteOpenHelper类简介

在Android开发中,SQLiteOpenHelper类是一个非常重要的辅助类,它主要用于管理数据库的创建和版本更新。通过使用SQLiteOpenHelper,开发者可以更方便地处理数据库的初始化以及在应用升级时对数据库结构进行相应的更改。

SQLiteOpenHelper是一个抽象类,我们在实际使用时需要创建它的子类,并实现其中的一些抽象方法。这样做的目的是为了根据具体应用的需求来定制数据库的创建和升级逻辑。

2. 构造函数

SQLiteOpenHelper有两个构造函数:

public SQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version)
public SQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler)
  • context:上下文对象,用于获取应用程序的相关信息,例如数据库存储路径等。一般传入应用的Activity或Application的上下文。
  • name:数据库名称,如果设置为null,将会创建一个内存中的数据库,该数据库在应用关闭后就会消失。
  • factory:游标工厂,一般设置为null,使用默认的游标工厂。
  • version:数据库版本号,当数据库结构发生变化时,需要更新这个版本号,以便在onUpgrade方法中进行相应的升级操作。
  • errorHandler(第二个构造函数特有):用于处理数据库打开时发生错误的回调。

示例代码如下:

public class MyDatabaseHelper extends SQLiteOpenHelper {
    private static final String DATABASE_NAME = "my_database.db";
    private static final int DATABASE_VERSION = 1;

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

3. 核心方法

3.1 onCreate(SQLiteDatabase db)

当数据库首次创建时,系统会调用这个方法。在这个方法中,我们通常会执行一些创建表、插入初始数据等操作。

参数db是一个可写的SQLiteDatabase对象,通过它可以执行SQL语句来创建数据库结构。

示例:假设我们要创建一个简单的用户表,表中有id(自增长主键)、name(用户姓名)和age(用户年龄)字段。

@Override
public void onCreate(SQLiteDatabase db) {
    String createTableSql = "CREATE TABLE users (" +
            "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
            "name TEXT, " +
            "age INTEGER)";
    db.execSQL(createTableSql);
}

3.2 onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)

当数据库版本号发生变化时,系统会调用这个方法。一般来说,应用升级时,如果数据库结构有改变,就需要在这个方法中进行相应的处理,例如添加新表、修改现有表结构或者删除不再需要的表等。

  • db:可写的SQLiteDatabase对象。
  • oldVersion:旧的数据库版本号。
  • newVersion:新的数据库版本号。

示例:假设我们要在users表中添加一个email字段,并且数据库版本号从1升级到2。

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

3.3 onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)

从Android 4.1开始引入了这个方法,当数据库版本号从高到低变化时会调用此方法。它与onUpgrade方法类似,只不过是处理版本降低的情况。在实际应用中,版本降低的情况相对较少,但在某些情况下(例如应用回滚版本)可能会用到。

示例:假设要从版本2回滚到版本1,并且需要删除email字段。

@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    if (oldVersion >= 2) {
        String dropColumnSql = "ALTER TABLE users DROP COLUMN email";
        db.execSQL(dropColumnSql);
    }
}

3.4 getReadableDatabase()

这个方法用于获取一个可读取的SQLiteDatabase实例。如果数据库不存在,会先调用onCreate方法创建数据库;如果数据库版本需要升级,会调用onUpgrade方法进行升级。如果数据库正在进行写入操作,它会等待写入操作完成后返回一个可读的数据库实例。

示例:

MyDatabaseHelper helper = new MyDatabaseHelper(context);
SQLiteDatabase db = helper.getReadableDatabase();

3.5 getWritableDatabase()

该方法用于获取一个可写入的SQLiteDatabase实例。同样,如果数据库不存在,会先调用onCreate方法创建;如果需要升级,会调用onUpgrade方法。与getReadableDatabase不同的是,如果数据库磁盘空间已满或者其他原因导致无法写入,getWritableDatabase可能会返回一个只读的数据库实例。

示例:

MyDatabaseHelper helper = new MyDatabaseHelper(context);
SQLiteDatabase db = helper.getWritableDatabase();

4. 数据库操作示例

下面我们通过一个完整的示例来展示如何使用SQLiteOpenHelper进行数据库的基本操作,包括插入、查询、更新和删除。

4.1 数据库辅助类

首先,创建一个继承自SQLiteOpenHelper的数据库辅助类MyDatabaseHelper

public class MyDatabaseHelper extends SQLiteOpenHelper {
    private static final String DATABASE_NAME = "my_database.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";

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

    @Override
    public void onCreate(SQLiteDatabase db) {
        String createTableSql = "CREATE TABLE " + TABLE_NAME + " (" +
                COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
                COLUMN_NAME + " TEXT, " +
                COLUMN_AGE + " INTEGER)";
        db.execSQL(createTableSql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // 暂时不处理升级逻辑
    }

    @Override
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // 暂时不处理降级逻辑
    }
}

4.2 插入数据

public void insertUser(Context context, String name, int age) {
    MyDatabaseHelper helper = new MyDatabaseHelper(context);
    SQLiteDatabase db = helper.getWritableDatabase();

    ContentValues values = new ContentValues();
    values.put(MyDatabaseHelper.COLUMN_NAME, name);
    values.put(MyDatabaseHelper.COLUMN_AGE, age);

    long newRowId = db.insert(MyDatabaseHelper.TABLE_NAME, null, values);
    db.close();
}

4.3 查询数据

public List<User> queryAllUsers(Context context) {
    List<User> userList = new ArrayList<>();
    MyDatabaseHelper helper = new MyDatabaseHelper(context);
    SQLiteDatabase db = helper.getReadableDatabase();

    String[] projection = {
            MyDatabaseHelper.COLUMN_ID,
            MyDatabaseHelper.COLUMN_NAME,
            MyDatabaseHelper.COLUMN_AGE
    };

    Cursor cursor = db.query(
            MyDatabaseHelper.TABLE_NAME,
            projection,
            null,
            null,
            null,
            null,
            null
    );

    while (cursor.moveToNext()) {
        long id = cursor.getLong(cursor.getColumnIndexOrThrow(MyDatabaseHelper.COLUMN_ID));
        String name = cursor.getString(cursor.getColumnIndexOrThrow(MyDatabaseHelper.COLUMN_NAME));
        int age = cursor.getInt(cursor.getColumnIndexOrThrow(MyDatabaseHelper.COLUMN_AGE));
        User user = new User(id, name, age);
        userList.add(user);
    }

    cursor.close();
    db.close();
    return userList;
}

4.4 更新数据

public void updateUserAge(Context context, long id, int newAge) {
    MyDatabaseHelper helper = new MyDatabaseHelper(context);
    SQLiteDatabase db = helper.getWritableDatabase();

    ContentValues values = new ContentValues();
    values.put(MyDatabaseHelper.COLUMN_AGE, newAge);

    String selection = MyDatabaseHelper.COLUMN_ID + " =?";
    String[] selectionArgs = {String.valueOf(id)};

    int count = db.update(
            MyDatabaseHelper.TABLE_NAME,
            values,
            selection,
            selectionArgs
    );

    db.close();
}

4.5 删除数据

public void deleteUser(Context context, long id) {
    MyDatabaseHelper helper = new MyDatabaseHelper(context);
    SQLiteDatabase db = helper.getWritableDatabase();

    String selection = MyDatabaseHelper.COLUMN_ID + " =?";
    String[] selectionArgs = {String.valueOf(id)};

    int count = db.delete(
            MyDatabaseHelper.TABLE_NAME,
            selection,
            selectionArgs
    );

    db.close();
}

5. 注意事项

  • 数据库版本管理:在应用开发过程中,要谨慎管理数据库版本号。每次数据库结构发生变化,都应该相应地更新版本号,并在onUpgradeonDowngrade方法中处理好升级和降级逻辑,以避免数据丢失或不一致的问题。
  • 资源关闭:在使用完SQLiteDatabase对象后,要及时调用close方法关闭,以释放资源。否则可能会导致内存泄漏等问题。例如,在上述的插入、查询、更新和删除方法中,都在操作完成后关闭了数据库。
  • 多线程操作:SQLite本身是线程安全的,但在Android应用中,建议在一个线程中进行数据库操作,避免在多个线程中同时对数据库进行读写操作,以免造成数据不一致或其他并发问题。如果确实需要在多线程中使用数据库,可以考虑使用SQLiteDatabasebeginTransactionendTransaction方法来管理事务,确保数据的一致性。

6. SQLiteOpenHelper与事务

在Android SQLite开发中,事务是保证数据一致性和完整性的重要机制。SQLiteOpenHelper配合SQLiteDatabase的事务方法,可以有效地处理复杂的数据操作。

6.1 事务的基本概念

事务是一组数据库操作的集合,这些操作要么全部成功执行,要么全部失败回滚。例如,在银行转账操作中,从一个账户扣除金额和向另一个账户增加金额这两个操作必须作为一个事务来处理,以确保资金的正确转移。

6.2 使用SQLiteOpenHelper进行事务操作

假设我们有一个场景,需要在users表中插入多条记录,并且要求这些插入操作要么全部成功,要么全部失败。

public void insertMultipleUsers(Context context, List<User> userList) {
    MyDatabaseHelper helper = new MyDatabaseHelper(context);
    SQLiteDatabase db = helper.getWritableDatabase();

    try {
        db.beginTransaction();
        for (User user : userList) {
            ContentValues values = new ContentValues();
            values.put(MyDatabaseHelper.COLUMN_NAME, user.getName());
            values.put(MyDatabaseHelper.COLUMN_AGE, user.getAge());
            db.insert(MyDatabaseHelper.TABLE_NAME, null, values);
        }
        db.setTransactionSuccessful();
    } finally {
        db.endTransaction();
    }

    db.close();
}

在上述代码中:

  • beginTransaction方法开始一个事务。
  • 在事务执行过程中,我们进行多次插入操作。
  • setTransactionSuccessful方法标记事务执行成功,如果在这之前事务中的任何操作失败(例如插入数据违反唯一性约束),事务不会提交。
  • endTransaction方法结束事务,如果事务标记为成功(通过setTransactionSuccessful),则提交事务,否则回滚事务。

7. SQLiteOpenHelper的优化

7.1 减少数据库操作次数

在进行批量数据操作时,尽量将多个操作合并为一个事务处理,减少数据库的I/O次数。例如上述的批量插入操作,如果不使用事务,每次插入都会进行一次磁盘I/O,而使用事务可以将多次I/O合并为一次,提高效率。

7.2 合理使用索引

在创建表时,根据查询需求合理创建索引。例如,如果经常根据name字段查询用户,那么可以在name字段上创建索引。

@Override
public void onCreate(SQLiteDatabase db) {
    String createTableSql = "CREATE TABLE " + TABLE_NAME + " (" +
            COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
            COLUMN_NAME + " TEXT, " +
            COLUMN_AGE + " INTEGER)";
    db.execSQL(createTableSql);

    // 创建索引
    String createIndexSql = "CREATE INDEX idx_name ON " + TABLE_NAME + " (" + COLUMN_NAME + ")";
    db.execSQL(createIndexSql);
}

索引可以加快查询速度,但会增加插入、更新和删除操作的时间,因为每次数据变化时,索引也需要更新。所以要根据实际应用场景权衡是否需要创建索引。

7.3 缓存数据库操作结果

对于一些不经常变化的数据查询结果,可以进行缓存。例如,某些配置信息可能在应用运行期间不会改变,那么在首次查询后,可以将结果缓存起来,下次需要时直接从缓存中获取,避免重复查询数据库。

8. 不同Android版本下的SQLiteOpenHelper

在不同的Android版本中,SQLiteOpenHelper的行为和一些特性可能会略有不同。

8.1 早期版本

在早期的Android版本中,SQLiteOpenHelper的功能相对简单,主要就是创建和升级数据库。对于一些复杂的数据库操作,开发者需要自己编写更多的代码来实现。

8.2 Android 4.1及以后

从Android 4.1开始引入了onDowngrade方法,使得处理数据库版本降级有了更规范的方式。同时,在这之后的版本中,对SQLite的性能优化和稳定性改进也在不断进行,SQLiteOpenHelper在这些改进的基础上,也能更好地服务于应用开发。

8.3 适配不同版本

在实际开发中,为了确保应用在不同Android版本上都能正常使用SQLiteOpenHelper,建议尽量使用通用的特性和方法。对于一些版本特定的功能,要进行版本检查后再使用。例如,在使用onDowngrade方法时,可以先检查当前设备的Android版本:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
    // 执行onDowngrade相关逻辑
}

9. 与其他数据存储方式的比较

在Android开发中,除了SQLite,还有其他几种常见的数据存储方式,如SharedPreferences、文件存储等。下面将SQLite与这些方式进行比较。

9.1 与SharedPreferences比较

  • 存储结构:SharedPreferences主要用于存储简单的键值对数据,例如应用的配置信息、用户登录状态等。而SQLite可以存储复杂的结构化数据,支持多表关联、事务等高级特性。
  • 应用场景:如果只是存储少量的简单数据,如布尔值、字符串等,SharedPreferences是一个很好的选择,因为它使用简单,读写速度快。但如果需要存储大量的结构化数据并进行复杂的查询和操作,SQLite则更为合适。

9.2 与文件存储比较

  • 数据组织:文件存储可以存储各种类型的数据,包括文本、二进制等,但数据的组织和查询相对复杂。SQLite以表格形式组织数据,提供了标准的SQL查询语言,方便进行数据的查询、插入、更新和删除操作。
  • 数据一致性:文件存储在处理数据一致性方面相对较弱,而SQLite通过事务机制可以很好地保证数据的一致性和完整性。

10. 常见问题及解决方法

10.1 数据库升级失败

  • 原因:可能是onUpgrade方法中的SQL语句编写错误,或者数据库版本号更新不正确。
  • 解决方法:仔细检查onUpgrade方法中的SQL语句,确保语法正确。同时,确认在应用升级时正确更新了数据库版本号。可以在onUpgrade方法中添加日志输出,以便在出现问题时能够追踪错误。

10.2 数据库操作导致应用卡顿

  • 原因:频繁的数据库I/O操作,特别是在主线程中进行数据库操作,会导致应用卡顿。
  • 解决方法:将数据库操作放在子线程中执行,可以使用Android的AsyncTaskHandlerThread或者ThreadPool等机制。同时,优化数据库查询,避免全表扫描等性能较差的操作。

10.3 数据丢失

  • 原因:可能是在数据库升级过程中,没有正确处理数据迁移,或者事务操作不当导致数据未正确提交。
  • 解决方法:在onUpgrade方法中仔细编写数据迁移逻辑,确保原有数据能够正确迁移到新的数据库结构中。在进行事务操作时,确保setTransactionSuccessfulendTransaction方法的正确使用。

通过深入理解SQLiteOpenHelper类的各个方面,开发者能够在Android应用开发中更好地利用SQLite数据库,实现高效、稳定的数据存储和管理。无论是简单的应用配置存储,还是复杂的业务数据处理,SQLiteOpenHelper都为我们提供了强大的支持。在实际开发中,根据应用的需求合理运用其特性,注意优化和避免常见问题,能够打造出性能卓越的数据存储解决方案。