Android SQLite DB何时关闭


96

我正在使用android上的SQLite数据库。我的数据库管理器是一个单例,现在在初始化时打开与数据库的连接。确保数据库始终处于打开状态是安全的,这样当有人调用我的班级来使用数据库时,该数据库已经打开了吗?还是应该在每次访问之前和之后打开和关闭数据库。始终保持打开状态是否有害?

谢谢!

Answers:


60

我会一直保持打开状态,并以某种生命周期方法(例如onStop或)关闭它onDestroy。这样,您可以在每次使用数据库之前通过调用isDbLockedByCurrentThreadisDbLockedByOtherThreads对单个SQLiteDatabase对象轻松地检查数据库是否已在使用中。这样可以防止对数据库进行多次操作,并避免应用程序崩溃

因此,在您的单例中,您可能有一个像这样的方法来获取单个SQLiteOpenHelper对象:

private SQLiteDatabase db;
private MyDBOpenHelper mySingletonHelperField;
public MyDBOpenHelper getDbHelper() {
    db = mySingletonHelperField.getDatabase();//returns the already created database object in my MyDBOpenHelper class(which extends `SQLiteOpenHelper`)
    while(db.isDbLockedByCurrentThread() || db.isDbLockedByOtherThreads()) {
        //db is locked, keep looping
    }
    return mySingletonHelperField;
}

因此,每当您要使用打开的辅助对象时,请调用此getter方法(确保已线程化)

单例中的另一个方法可能是(在尝试调用上述getter之前称为EVERY TIME):

public void setDbHelper(MyDBOpenHelper mySingletonHelperField) {
    if(null == this.mySingletonHelperField) {
        this.mySingletonHelperField = mySingletonHelperField;
        this.mySingletonHelperField.setDb(this.mySingletonHelperField.getWritableDatabase());//creates and sets the database object in the MyDBOpenHelper class
    }
}

您可能还希望以单例方式关闭数据库:

public void finalize() throws Throwable {
    if(null != mySingletonHelperField)
        mySingletonHelperField.close();
    if(null != db)
        db.close();
    super.finalize();
}

如果您的应用程序用户能够非常快速地创建许多数据库交互,则应该使用上面我已经演示过的方法。但是,如果数据库交互最少,我将不必担心,只需每次创建和关闭数据库即可。


我可以通过这种方法(保持数据库打开)将数据库访问速度提高2倍,谢谢
teh.fonsi 2014年

2
旋转是一种非常糟糕的技术。请参阅下面的答案。
mixel 2015年

1
@mixel好点。我相信我在API 16可用之前就发布了这个答案,但我可能是错的
james 2015年

1
@binnyb我认为最好更新您的答案,这样才不会误导人们。
mixel

3
自API 16起,WARN isDbLockedByOtherThreads()被描述并返回false。另外,检查isDbLockedByCurrentThread()会产生无限循环,因为它停止了当前线程并且没有任何东西可以“解锁数据库”,因此该方法返回true。
matreshkin '16

20

到目前为止,无需检查数据库是否已被另一个线程锁定。在每个线程中使用单例SQLiteOpenHelper时,您很安全。从isDbLockedByCurrentThread文档:

该方法的名称来自与数据库建立活动连接时的时间,这意味着该线程正在对数据库持有实际的锁。如今,不再存在真正的“数据库锁”,尽管如果线程无法获取数据库连接以执行特定操作,它们可能会阻塞。

isDbLockedByOtherThreads 自API级别16开始不推荐使用。


每个线程使用一个实例没有意义。SQLiteOpenHelper是线程安全的。这也是非常低效的内存。相反,应用程序应在每个数据库中保留一个SQLiteOpenHelper实例。为了获得更好的并发性,建议使用WriteAheadLogging,它提供最多4个连接的连接池。
ejboy

@ejboy我的意思是。“在每个线程中使用单例(=一个)SQLiteOpenHelper”,而不是“每个线程”。
mixel

15

关于这些问题:

我的数据库管理器是一个单例,现在在初始化时打开与数据库的连接。

我们应该划分“打开数据库”,“打开连接”。SQLiteOpenHelper.getWritableDatabase()给出一个打开的数据库。但是我们不必像内部那样控制连接。

确保数据库始终处于打开状态是安全的,这样当有人调用我的班级来使用数据库时,该数据库已经打开了吗?

是的。如果正确关闭事务,连接不会挂起。请注意,如果GC完成,则数据库也会自动关闭。

还是应该在每次访问之前和之后打开和关闭数据库。

关闭SQLiteDatabase实例除了关闭连接没有任何其他好处,但是如果此时有一些连接,这对开发人员来说是不好的。同样,在SQLiteDatabase.close()之后,SQLiteOpenHelper.getWritableDatabase()将返回一个新实例。

始终保持打开状态是否有害?

不,没有。还要注意,在不相关的时刻和线程(例如在Activity.onStop()中)关闭数据库可能会关闭活动连接并使数据保持不一致状态。


谢谢,在读完“在SQLiteDatabase.close()之后,SQLiteOpenHelper.getWritableDatabase()将返回一个新实例”一词后,我意识到终于可以对我的应用程序中的一个老问题做出回答。为了一个问题,迁移到Android 9后成为关键(见stackoverflow.com/a/54224922/297710
yvolk


1

从性能角度来看,最佳方法是在应用程序级别上保留单个SQLiteOpenHelper实例。打开数据库可能会很昂贵并且是一项阻塞操作,因此不应在主线程上和/或在活动生命周期方法中进行打开。

当不使用数据库时,可以使用setIdleConnectionTimeout()方法(在Android 8.1中引入)释放RAM。如果设置了空闲超时,则一段时间不活动之后(即,当未访问数据库时),数据库连接将关闭。当执行新查询时,连接将透明地重新打开到应用程序。

除此之外,应用程序可以在进入后台或检测到内存压力时调用releaseMemory(),例如在onTrimMemory()中



-9

创建您自己的应用程序上下文,然后从那里打开和关闭数据库。该对象还有一个OnTerminate()方法,您可以用来关闭连接。我还没有尝试过,但这似乎是一个更好的方法。

@binnyb:我不喜欢使用finalize()关闭连接。可能可行,但是据我了解,使用Java finalize()方法编写代码是一个坏主意。


您不想依靠finalize,因为您永远不知道何时调用它。在GC决定清理它之前,对象可能一直处于混乱状态,然后才调用finalize()。这就是为什么它没用的原因。正确的方法是拥有一个不再使用该对象时(无论是否进行垃圾收集)都被调用的方法,而这正是onTerminate()的含义。
erwan

5
文件说很清楚,onTerminate不会被调用在生产环境- developer.android.com/reference/android/app/...
埃利泽
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.