当前位置:首页 > 问答 > 正文

greendao3里到底咋关闭数据库才不会出错,有啥注意的地方没?

在 GreenDao 3 中,你通常不需要频繁地手动关闭数据库,这是很多初学者容易陷入的误区,也是导致很多“莫名其妙”错误的根源,GreenDao 的核心对象是 DaoSessionDaoMaster.DevOpenHelper,它们内部管理着数据库连接(SQLiteDatabase)。

来源依据:根据 GreenDao 的官方文档和其设计理念,DaoMaster.DevOpenHelper 封装了 SQLiteOpenHelper,它利用了 Android 系统原生的数据库连接管理机制,这套机制本身具备自动管理数据库连接生命周期的能力,会在适当的时候(例如应用进程存活期间连接闲置时)自动关闭数据库,以避免资源泄露。

第一个最重要的注意事项就是:避免在短时间、高频次的业务操作中(比如循环插入数据时)重复调用 getWritableDb()getReadableDb() 然后立刻关闭。

为什么这样做会出错? 因为你每次调用 getWritableDb(),GreenDao 都会尝试获取一个可写的数据库连接,如果你紧接着调用 close() 方法,那么这个连接就被关闭了,在单线程环境下,这可能看起来没问题,但一旦涉及到多线程异步操作,灾难就来了,想象一下这个场景:

  1. 线程 A 通过 DaoSessiongetUserDao() 获取了 UserDao 对象,并开始执行一个插入操作,这个 DaoSession 背后关联着一个数据库连接。
  2. 线程 B 出于某种原因(比如在 onDestroy 中)调用了数据库关闭。
  3. 线程 A 的插入操作还在进行中,它试图写入的数据库连接已经被线程 B 关闭了。
  4. 结果就是必然的崩溃,你会看到类似 “attempt to re-open an already-closed object” 或 “database not open” 这样的错误日志。

这种错误非常难调试,因为它不是每次必现,依赖于线程调度的时机。

正确的做法是什么呢?

最佳实践:在 Application 类中初始化并全局持有 DaoSession 单例。

具体步骤和代码如下:

greendao3里到底咋关闭数据库才不会出错,有啥注意的地方没?

  1. 初始化: 在你的自定义 Application 类的 onCreate() 方法中,完成 GreenDao 的初始化。

    public class MyApplication extends Application {
        private DaoSession daoSession;
        @Override
        public void onCreate() {
            super.onCreate();
            // 创建辅助数据库对象 DevOpenHelper
            DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "your-db-name.db");
            // 获取可写数据库
            Database db = helper.getWritableDb();
            // 创建 DaoMaster
            DaoMaster daoMaster = new DaoMaster(db);
            // 创建 DaoSession
            daoSession = daoMaster.newSession();
        }
        public DaoSession getDaoSession() {
            return daoSession;
        }
    }
  2. 全局使用: 在整个应用程序的任何地方,当你需要进行数据库操作时,都通过这个单例来获取 DaoSession,然后再获取具体的 XXXDao 对象。

    // 在 Activity 或 Fragment 中
    DaoSession daoSession = ((MyApplication) getApplication()).getDaoSession();
    UserDao userDao = daoSession.getUserDao();
    // 然后使用 userDao 进行 insert, delete, update, query 等操作
    User user = new User();
    user.setName("张三");
    userDao.insert(user);

采用这种方式,整个应用进程内都共享同一个 DaoSession 实例(也就意味着共享同一个数据库连接),你不需要,也不应该在任何业务代码中手动关闭它。

什么时候才需要关闭数据库呢?

答案是:在应用进程即将被销毁时。 这个时机是在你的 Application 类的 onTerminate() 方法中,但是请注意,onTerminate() 方法只在模拟器上会调用,在真机上永远不会被调用。 依赖这个方法是不靠谱的。

greendao3里到底咋关闭数据库才不会出错,有啥注意的地方没?

由于 Android 系统的进程管理机制,当你的应用被切换到后台,并最终因内存不足等原因被系统杀死时,进程会被直接终止,所有资源(包括数据库连接)都会被系统自动回收,在绝大多数情况下,你根本不需要手动关闭数据库,让系统去处理是最安全、最省心的方式。

什么情况下需要考虑手动关闭?

只有在一种非常特殊的情况下,你可能需要手动管理:当你明确知道某个 DaoSession 及其关联的数据库连接在未来很长一段时间内(甚至永远)都不会再被使用,并且你希望立即释放其占用的文件句柄等资源。 你开发了一个支持多用户切换的应用,每个用户有独立的数据库文件,当用户退出登录时,你需要关闭当前用户的数据库连接,然后切换到新用户的数据库。

在这种情况下,关闭数据库的注意事项就非常关键了:

  1. 确保没有正在进行中的操作: 在调用 close() 之前,你必须确保所有使用该 DaoSession 的线程都已经完成了它们的数据库操作,这可能需要你引入一些同步机制,比如计数器、锁或者通过 RxJava 等异步框架来确保所有流都已完成。
  2. 关闭后停止使用: 一旦数据库连接被关闭,任何试图通过旧的 DaoSessionXXXDao 进行的操作都会导致异常,在关闭后,你应该将持有这些对象的引用置为 null,并重新初始化新的连接。
  3. 不要在 Activity 或 Fragment 的 onDestroy 中草率关闭: 因为 onDestroy 的调用并不代表应用进程结束,其他后台 Service 或正在执行异步任务的线程可能还在使用数据库,在这里关闭极易引发上述的多线程崩溃。

总结一下核心要点:

  • 首选方案: 在 Application 中初始化全局 DaoSession 单例,全程使用它,永不手动关闭,这是 GreenDao 设计的推荐用法,能避免 99% 的关闭相关问题。
  • 错误做法: 避免在局部作用域(如 Activity 的某个方法内)频繁打开和关闭数据库。
  • 特殊需求: 如果确有需要手动关闭(如切换数据库),务必在确保绝对没有并发操作的前提下进行,并且处理好关闭后的对象状态。
  • 信任系统: 对于应用程序主数据库,放心地让 Android 系统在进程终止时帮你回收资源,这比你自己在任何时机关闭都更安全。

遵循以上原则,你在使用 GreenDao 3 时基本就不会再遇到因关闭数据库而导致的崩溃错误了。