android - 并发访问 Android 中的 SQLite 数据库 - 数据库已关闭

标签 android sqlite sqliteopenhelper getwritabledatabase

我阅读了很多关于这个主题的主题,但没有人能回答我的问题。

我从并发线程访问我的数据库,我的 SQLiteOpenHelper 实现了单例的设计模式,所以我的应用程序只有一个实例。

我用这样的代码访问我的数据库:

 SQLiteDatabase db = DatabaseHelper.getInstance().getWritableDatabase();
 ...
 Do some update in the DB
 ...
 db.close();

我不明白为什么我仍然收到“db already closed”错误,getWritableDatabase() 方法不应该锁定数据库直到调用 close() 吗?来自其他线程的其他 getWritableDatabase() 调用应该等到数据库关闭?是这样还是我错过了什么?

最佳答案

扩展 elhadi 的回答 我在跨多个异步任务打开和关闭数据库连接时遇到过类似的问题。根据我当时的调查,很明显没有必要不断打开和关闭数据库连接。我最终采用的方法是对 Application 进行子类化,并在 onCreate 期间执行单个数据库打开,并在 onTerminate 期间执行单个数据库关闭。然后我设置了一个静态 getter 来检索已经打开的 SQLiteDatabase 对象。 DI(依赖注入(inject))不友好,但 Android 还不能真正做到这一点。

类似这样的东西;

    public class MainApplication extends Application {
          private static SQLiteDatabase database;

          /**
           * Called when the application is starting, before any other 
           * application objects have been created. Implementations 
           * should be as quick as possible...
           */
          @Override
          public void onCreate() {
          super.onCreate();
          try {
           database = SQLiteDatabase.openDatabase("/data/data/<yourdbpath>", null, SQLiteDatabase.OPEN_READWRITE);
          } catch (SQLiteException e) {
            // Our app fires an event spawning the db creation task...
           }
         }


          /**
           * Called when the application is stopping. There are no more 
           * application objects running and the process will exit.
           * <p>
           * Note: never depend on this method being called; in many 
           * cases an unneeded application process will simply be killed 
           * by the kernel without executing any application code...
           * <p>
           */
          @Override
          public void onTerminate() {
            super.onTerminate();
            if (database != null && database.isOpen()) {
              database.close();
            }
          }


          /**
           * @return an open database.
           */
          public static SQLiteDatabase getOpenDatabase() {
            return database;
          }
    }

回读 JavaDoc 我肯定从某个地方抄袭了这个,但是这个静态的单一数据库打开/关闭解决了你遇到的这个问题。在某处描述此解决方案的 SO 上还有另一个答案。

更多细节:

为了回应 Fr4nz 关于下面 NPE 的评论,我提供了我们具体实现的更多细节。

精简版

如果没有很好地理解 BroadcastReceivers,则很难掌握下面的“全貌”。在您的情况下(并且作为第一个关闭)添加您的数据库创建代码并在创建数据库后初始化并打开数据库。所以写;

      try {
       database = SQLiteDatabase.openDatabase("/data/data/<yourdbpath>", null, SQLiteDatabase.OPEN_READWRITE);
      } catch (SQLiteException e) {
        // Create your database here!
        database = SQLiteDatabase.openDatabase("/data/data/<your db path>", null, SQLiteDatabase.OPEN_READWRITE);
       }
     }

长版

是的,除了上面的代码之外还有更多内容。请注意我对第一个异常捕获的评论(即你的应用程序第一次运行时)。这里说的是,“我们的应用程序触发了一个生成数据库创建任务的事件”。在我们的应用程序中实际发生的是注册了一个监听器(Android 的 BroadcastReceiver 框架),并且主要应用程序 Activity 所做的第一件事就是检查 database 中的静态变量MainApplication 不为空。如果它为 null,则会生成一个创建数据库的异步任务,当它完成时(即运行 onPostExecute() 方法)最终会触发我们知道将由监听器接收的事件我们在 try-catch 中注册了。接收器作为内部类位于 MainApplication 类下,如下所示;

    /**
    * Listener waiting for the application to finish
    * creating the database.
    * <p>
    * Once this has been completed the database is ready for I/O.
    * </p>
    *
    * @author David C Branton
    */
      public class OpenDatabaseReceiver extends BroadcastReceiver {
        public static final String BROADCAST_DATABASE_READY = "oceanlife.core.MainApplication$OpenDatabaseReceiver.BROADCAST_DATABASE_READY";

        /**
         * @see android.content.BroadcastReceiver#onReceive(android.content.Context, android.content.Intent)
         */
        @Override
        public void onReceive(final Context context, final Intent intent) {
          Log.i(CreatedDatabaseReceiver.class.getSimpleName(), String.format("Received filter event, '%s'", intent.getAction()));
          database = SQLiteDatabase.openDatabase("/data/data/<your db path>", null, SQLiteDatabase.OPEN_READWRITE);
          unregisterReceiver(openDatabaseReceiver);

          // Broadcast event indicating that the creation process has completed.
          final Intent databaseReady = new Intent();
          databaseReady.setAction(BROADCAST_DATABASE_READY);
          context.sendBroadcast(databaseReady);
        }
      }

所以第一次安装的启动过程总结是这样的;

  1. 类:MainApplication,角色- 检查是否有数据库?
    • 是吗?数据库变量已初始化
    • 没有?接收器注册(OpenDatabaseReceiver)
  2. 类:MainActivity:应用程序的角色登陆 Activity ,并最初检查数据库变量是否不为空。
    • database 为空?不添加执行 I/O 的 fragment ,而是添加“创建应用程序数据库”或类似内容的对话框。
    • database 不为空?继续主应用程序执行流程,添加由数据库等支持的列表
  3. 类:DatabaseCreationDialogFragment:角色生成异步任务以创建数据库。
    • 注册新的接收器监听数据库何时创建。
    • 在收集到“我已经创建了数据库”消息时,会触发另一个事件(来自接收方),告诉应用程序打开数据库。
  4. 类:MainApplication:角色 2- 监听“数据库已创建”消息。
    • 上述接收器 (OpenDatabaseReceiver) 打开数据库并广播(通过另一个事件!)数据库已准备好使用。
  5. 类:MainActivity:角色 2 - 获取“数据库已准备就绪”消息,摆脱“我们正在创建数据库”对话框并继续在应用中显示数据/功能。

恢复和平。

关于android - 并发访问 Android 中的 SQLite 数据库 - 数据库已关闭,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10801119/

相关文章:

Python 类 SQLite3 请求返回对象

java - SQLite 插入时出现空指针异常

Android:进程内广播

python - Django runserver 失败,并显示 django.db.utils.IntegrityError : NOT NULL constraint failed: reviewer_post. post_id (AutoField)

mysql - 为什么SQLite数据库rowid要自己创建?安卓

android - 并发写入android数据库(来自多个服务)?

android - 在我的 DbHelper 类的 oCreate 中插入记录时递归调用 getDatabase

android - 在 Android Studio 中同步 Gradle 时无法解析 3DR 服务库依赖项

android - 在 RecyclerView 中更改可绘制对象会更新所有行

android - 在 webview 加载之前加载图像?