前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >小白到大神,你需要了解的 sqlite 最佳实践

小白到大神,你需要了解的 sqlite 最佳实践

作者头像
AndroidTraveler
发布2019-06-20 20:44:21
8990
发布2019-06-20 20:44:21
举报
背景

本文是对一篇英文文档的翻译,原文请见文末链接。


并发数据库访问

假设你实现了自己的 SQLiteOpenHelper。

public class DatabaseHelper extends SQLiteOpenHelper { ... }

现在你想要在多个线程中对数据库写入数据。

 // Thread 1 Context context = getApplicationContext(); DatabaseHelper helper = new DatabaseHelper(context); SQLiteDatabase database = helper.getWritableDatabase(); database.insert(…); database.close();
 // Thread 2 Context context = getApplicationContext(); DatabaseHelper helper = new DatabaseHelper(context); SQLiteDatabase database = helper.getWritableDatabase(); database.insert(…); database.close();

你将会在你的 logcat 中发现下面信息,并且你的其中一个改变不会写入数据库:

android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)

产生这个错误的原因是因为,每次你创建新的 SQLiteOpenHelper 对象,实际上你创建了新的数据库连接。如果你尝试从不同的连接同时对数据库写入数据,其中一个会失败。

为了在多线程使用数据库,我们要确保只使用一个数据库连接。

让我们构造单例类 DatabaseManager,它会持有并返回单个 SQLiteOpenHelper 对象。

public class DatabaseManager {
    private static DatabaseManager instance;    private static SQLiteOpenHelper mDatabaseHelper;
    public static synchronized void initializeInstance(SQLiteOpenHelper helper) {        if (instance == null) {            instance = new DatabaseManager();            mDatabaseHelper = helper;        }    }
    public static synchronized DatabaseManager getInstance() {        if (instance == null) {            throw new IllegalStateException(DatabaseManager.class.getSimpleName() +                    " is not initialized, call initialize(..) method first.");        }
        return instance;    }
    public synchronized SQLiteDatabase getDatabase() {        return mDatabaseHelper.getWritableDatabase();    }
}

在多个线程中对数据库写入数据,修改后的代码如下所示。

// In your application classDatabaseManager.initializeInstance(new DatabaseHelper());
// Thread 1DatabaseManager manager = DatabaseManager.getInstance();SQLiteDatabase database = manager.getDatabase()database.insert(…);database.close();
// Thread 2DatabaseManager manager = DatabaseManager.getInstance();SQLiteDatabase database = manager.getDatabase()database.insert(…);database.close();

这会带来另一个奔溃。

java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase

由于我们只使用了一个数据库连接,Thread1Thread2getDatabase() 方法都会返回同一个 SQLiteDatabase 对象实例。可能发生的场景是 Thread1 关闭了数据库,然而 Thread2 还在使用它。这也就是为什么我们会有 IllegalStateException 的奔溃的原因。

我们需要确保没有人正在使用数据库,这个时候我们才可以关闭它。stackoveflow 上有人推荐永远不要关闭你的 SQLiteDatabase。这会让你看到下面的 logcat 信息。所以我一点也不认为这是一个好的想法。

Leak foundCaused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed

实战例子

一种可能的解决方案是使用计数器跟踪打开/关闭的数据库连接。

public class DatabaseManager {
    private AtomicInteger mOpenCounter = new AtomicInteger();
    private static DatabaseManager instance;    private static SQLiteOpenHelper mDatabaseHelper;    private SQLiteDatabase mDatabase;
    public static synchronized void initializeInstance(SQLiteOpenHelper helper) {        if (instance == null) {            instance = new DatabaseManager();            mDatabaseHelper = helper;        }    }
    public static synchronized DatabaseManager getInstance() {        if (instance == null) {            throw new IllegalStateException(DatabaseManager.class.getSimpleName() +                    " is not initialized, call initializeInstance(..) method first.");        }
        return instance;    }
    public synchronized SQLiteDatabase openDatabase() {        if(mOpenCounter.incrementAndGet() == 1) {            // Opening new database            mDatabase = mDatabaseHelper.getWritableDatabase();        }        return mDatabase;    }
    public synchronized void closeDatabase() {        if(mOpenCounter.decrementAndGet() == 0) {            // Closing database            mDatabase.close();
        }    }}

然后如下所示来使用。

SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();database.insert(...);// database.close(); Don't close it directly!DatabaseManager.getInstance().closeDatabase(); // correct way

每当你需要使用数据库的时候你应该调用 DatabaseManager 类的 openDatabase() 方法。在这个方法里面,我们有一个计数器,用来表明数据库打开的次数。如果计数为 1,意味着我们需要创建新的数据库连接,否则,数据库连接已经建立。

对于 closeDatabase() 方法来说也是一样的。每次我们调用这个方法的时候,计数器在减少,当减为 0 的时候,我们关闭数据库连接。

现在你能够使用你的数据库并且确保是线程安全的。


本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 安卓小煜 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 并发数据库访问
    • 实战例子
    相关产品与服务
    数据库
    云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档