3

この主題に関する多くのトピックを読みましたが、誰も私の質問に答えることができませんでした.

並行スレッドからデータベースにアクセスします。SQLiteOpenHelper はシングルトンの設計パターンを実装しているため、アプリのインスタンスは 1 つしかありません。

次のようなコードでデータベースにアクセスします。

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

「db already closed」というエラーがまだ表示される理由がわかりません。getWritableDatabase() メソッドは、close() が呼び出されるまでデータベースをロックしないでください 他のスレッドからの他の getWritableDatabase() 呼び出しは、データベースが閉じられるまで待機する必要がありますか? そうですか、それとも何か見逃しましたか?

4

2 に答える 2

7

elhadiの答えを拡張すると、複数の非同期タスク間でデータベース接続を開いたり閉じたりするときに、同様の問題が発生しました。当時の私の調査から、db接続を絶えず開いたり閉じたりする必要がないことが明らかになりました。私が採用することになったアプローチは、サブクラス化Applicationして、単一のデータベースを開いている間onCreateと単一のデータベースを閉じて実行することでしonTerminateた。次に、すでに開いているオブジェクトを取得するための静的ゲッターを設定し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を読み返して、私は確かにどこかからこれを苦しめましたが、この静的な単一データベースのオープン/クローズは、あなたが抱えているこの問題を解決しました。この解決策を説明するSOに関する別の答えがどこかにあります。

より詳しく:

以下のNPEに関するFr4nzのコメントに応えて、私は特定の実装の詳細を提供しました。

短縮版

以下の「全体像」は、BroadcastReceiversを十分に理解していないと理解するのが困難です。あなたの場合(そして最初に)、DB作成コードを追加し、データベースを作成した後でデータベースを初期化して開きます。だから書く;

      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);
       }
     }

ロングバージョン

はい、上記のコードだけではありません。最初のインスタンスでの例外キャッチに関する私のコメントに注意してください(つまり、アプリケーションを初めて実行するとき)。これは、「私たちのアプリは、db作成タスクを生成するイベントを発生させます」と言っています。私たちのアプリで実際に行われるのは、リスナー(AndroidのBroadcastReceiverフレームワーク)が登録されており、メインのアプリケーションアクティビティが最初に行うことの1つは、のdatabase静的変数MainApplicationがnullでないことを確認することです。nullの場合、dbを作成する非同期タスクが生成され、dbが終了すると(つまり、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:アプリケーションのロールランディングアクティビティであり、最初にデータベース変数がnullでないことを確認します。
    • database無効である?I / Oを実行するフラグメントを追加せず、「アプリケーションデータベースの作成」などのダイアログを追加します。
    • databasenullではありませんか?メインアプリケーションの実行フローを続行し、dbなどに裏打ちされたリストを追加します
  3. クラス:DatabaseCreationDialogFragment:ロール-非同期タスクを生成してデータベースを作成します。
    • データベースが作成されたときにリッスンする新しいレシーバーを登録します。
    • 「データベースを作成しました」というメッセージを収集すると、(受信者から)別のイベントが発生し、アプリにデータベースを開くように指示します。
  4. クラス:MainApplication:ロール2-「データベースが作成されました」というメッセージをリッスンします。
    • 上記のレシーバー(OpenDatabaseReceiver)はデータベースを開き、データベースを使用する準備ができていることを(別のイベントによって!)ブロードキャストします。
  5. クラス:MainActivity:ロール2-「データベースの準備ができました」というメッセージを取得し、「データベースを作成しています」ダイアログを削除して、アプリにデータ/機能を表示します。

平和が回復します。

于 2012-05-29T15:36:39.827 に答える
1

スレッドで DatabaseHelper.getInstance().getWritableDatabase() を呼び出す場合は、スレッドを開始する前にそれを管理することをお勧めします。メインプログラムでデータベースを開き、スレッドを呼び出します。スレッドの終了後、メイン プログラムでデータベースを閉じます。

于 2012-05-29T14:46:14.137 に答える