【问题标题】:Concurrent access to a SQLite database in Android - db already closed在 Android 中并发访问 SQLite 数据库 - db 已关闭
【发布时间】:2012-05-29 14:24:40
【问题描述】:

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

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

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

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

我不明白为什么我仍然得到“数据库已关闭”错误,getWritableDatabase() 方法不应该在调用 close() 之前锁定数据库吗?来自其他线程的其他 getWritableDatabase() 调用应该等到数据库关闭?这是对的还是我错过了什么?

【问题讨论】:

    标签: android sqlite sqliteopenhelper getwritabledatabase


    【解决方案1】:

    扩展 elhadi 的回答我在跨多个异步任务打开和关闭数据库连接时遇到了类似的问题。根据我当时的调查,很明显没有必要经常打开和关闭数据库连接。我最终采用的方法是对Application 进行子类化,并在onCreate 期间执行单个数据库打开和onTerminate 期间执行单个数据库关闭。然后我设置了一个静态 getter 来检索已经打开的 SQLiteDatabase 对象。对 DI(依赖注入)不友好,但 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 我肯定从某个地方 plagerised 这个但是这个静态的单个 db open/close 解决了你遇到的这个问题。 SO上有另一个答案描述这个解决方案。

    更多详情:

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

    短版

    如果没有对广播接收器有很好的了解,很难掌握下面的“全貌”。在您的情况下(并且首先)添加您的数据库创建代码并在创建数据库后初始化并打开数据库。所以写;

          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 框架),并且主要应用程序活动所做的第一件事就是检查MainApplication 中的database 静态变量不为空。如果它为空,则生成一个创建数据库的异步任务,当它完成时(即运行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:应用程序的角色登陆活动,最初检查数据库变量是否为空。
      • database 为空?不添加执行 I/O 的片段,并在对话框中添加“创建应用程序数据库”或类似内容。
      • database 不为空?继续主应用程序执行流程,添加由 db 支持的列表等
    3. 类:DatabaseCreationDialogFragment:角色 - 生成异步任务以创建数据库。
      • 注册新的接收器以监听数据库何时创建。
      • 在收集“我已创建数据库”消息时,会触发另一个事件(来自接收方),告诉应用打开数据库。
    4. 类:MainApplication:角色 2- 侦听“数据库已创建”消息。
      • 上述接收器 (OpenDatabaseReceiver) 打开数据库并广播(通过另一个事件!)数据库已准备好使用。
    5. 类:MainActivity:角色 2 - 获取“数据库已准备就绪”消息,摆脱“我们正在创建数据库”对话框,继续在应用中显示数据/功能。

    和平恢复了。

    【讨论】:

    • 如果您尝试这样做,请不要忘记使用新应用程序“名称”的路径更新您的 Manifest.xml
    • 您好,我现在遇到一些数据库路径问题。显然它找不到数据库并且 getOpenDatabase 抛出 NullPointerException。我可以使用类似 DatabaseHelper.getInstance().getWritableDatabase() 的东西代替 openDatabase() 吗(仅一次)?
    • (显着地)添加了更多细节 Fr4nz。阅读并画出一些东西。如果您想了解我们之所以选择“长版”,我们最好在“聊天”中了解。这完全是关于 Fragments 的角色和职责,并告知用户正在发生的事情。
    • 感谢您的详细回答,我要试试这个
    • 好总结! (即使概述了一个有点恕我直言。愚蠢的设计 - 对话框没有业务产生线程和接收器
    【解决方案2】:

    如果你打电话 DatabaseHelper.getInstance().getWritableDatabase() 在您的线程中,我建议您在开始线程之前对其进行管理。你在主程序中打开你的数据库,你调用你的线程。线程终止后,您在主程序中关闭您的数据库。

    【讨论】:

    • 是的,我在每个线程中调用 DatabaseHelper.getInstance().getWritableDatabase()。谢谢你的回答我要试试这个。但理论上我的反应是对的吗?
    • 在您的解决方案中,我是否应该在启动线程之前调用 getWritableDatabase() 以启动数据库?我真的不明白它应该如何提供帮助,因为我仍然需要在我的线程中调用 getWritableDatabase(),对吧?
    猜你喜欢
    • 1970-01-01
    • 2011-09-26
    • 1970-01-01
    • 1970-01-01
    • 2012-04-08
    • 2011-06-01
    • 2023-03-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多