Android での SQLiteDatabase のクローズについて (2)


SQLiteOpenHelper は、同じ SQLiteDatabase オブジェクトを返します。それを踏まえた close() 処理について述べます。

SQLiteOpenHelper は、同じ SQLiteDatabase オブジェクトを返す

以前に、Android での SQLiteDatabase のクローズについて | DeVlog という記事の中で、SQLiteDatabase オブジェクトは適切にクローズすべきであると述べました。

その後、 Android Tips(20):知っておきたいSQLiteデータベースの注意点 (1/2) – MONOist(モノイスト) という記事を見つけました。 (記事の日付は、 2012年 9月なので、私の記事よりずっと前に書かれたものです。)

その記事の 「データベースのクローズについての注意点」という項目の中で、「SQLiteOpenHelper で取得したオブジェクトは同じオブジェクトを返すので、 オブジェクトを close() しないように実装する場合が多い。」との記述があり、気になったので調べてみました。

まず、「SQLiteOpenHelper は内部に SQLiteDatabase オブジェクトを保持し、同じ SQLiteDatabase オブジェクトを返す。」事はソースで確認できました。内部に mDatabase という private フィールドを保持し、それを返すようになっています。

対処の考え方は同じ

ただし、対処の仕方は 以前の記事 の [パターン1] ~ [パターン3] とほぼ同じで、適切に close() するのが良いと思います。

[パターン1]
1つの SQLiteOpenHelper からは、必要時に 1 つのオブジェクトを取得し、不要になったらすぐ close() する。私は基本このスタイルです。

これは、以前の記事そのままです。

因みに、取得した SQLiteDatabase オブジェクトを close() した後、同じ SQLiteOpenHelper の getWritableDatabase で SQLiteDatabase オブジェクトを取得することは問題ありません。この場合は、新しい SQLiteDatabase オブジェクトが生成されます。

[パターン2]
1つの SQLiteOpenHelper から複数のオブジェクトを取得して(同一スレッドで)利用する場合は、全て不要になった時点で(onDestory() 等で)まとめて close() する。そのためには、 SQLiteOpenHelper#close() を使う。(内部の SQLiteDatabase オブジェクトを close() してくれます。)

[パターン3]
1つの SQLiteOpenHelper から取得したオブジェクトを別スレッドで使用する場合は、aquireReference() を使う

[パターン2]の db1 と db2、[パターン3]の mDb1 と mDb2 は、見かけは別のオブジェクトですが実体は同じオブジェクトです。そのため、その扱いは、以前の記事で複数の参照を扱う場合と同じになります。

[参考にしたサイト]

  1. Android Tips(20):知っておきたいSQLiteデータベースの注意点 (1/2) – MONOist(モノイスト)
  2. SQLiteOpenHelper | Android Developers
  3. SQLiteDatabase | Android Developers

[関連記事]
Android での SQLiteDatabase のクローズについて | DeVlog

Android での SQLiteDatabase のクローズについて


SQLite を使ったアプリを作成しているのですが、アプリを実行してしばらく放置した後ログを確認すると、以下の様な警告が出ていました。

W/SQLiteConnectionPool(1261): A SQLiteConnection object for database ‘/data/data/…..’ was leaked! Please fix your application to end transactions in progress properly and to close the database when it is no longer needed.

データベース ‘/data/data/…..’ に対する SQLiteConnection オブジェクトがリークしました。データベースが必要なくなったら、進行中のトランザクションを適切に終了し、データベースをクローズしてください。

以前、SQLiteDatabase オブジェクトは明示的にクローズすべきでないとの記事を読んだことがあり、今回もそのようにしていました。あらためて Android のソースを確認 (close() の実体は、SQLiteClosable#close() で、SQLiteClosable.java にあります。) し、

SQLiteDatabase オブジェクトは、適切かつ確実に close() すべき

と確信しましたので、以下に記述します。(残念ながら Developers サイトには特にこの点に触れた記述がありません。)

ソースを見ると、SQLiteDatabase は内部でリファレンスカウンタを管理しているので、close() を呼んでもカウンタが 0 にならない限り勝手にネイティブコード側のクローズ処理を呼び出したりはしません。従って、 1 つのSQLiteDatabase オブジェクトに対しては、適切な回数の close() を呼ぶようにすることが大切です。多いとおそらく例外が発生します。少ないとリークしてしまいます。

[パターン1]
使用時にオブジェクトを取得し、使い終わったらすぐ close() を実行する。
これが、最も単純で安全な方法です。try ~ finally を使えば、確実に close() を実行できます。

[パターン2]
インスタンスフィールドに SQLiteDatabase オブジェクトを保持しておき、最後に 1 回だけ close() を呼ぶ。
毎回オブジェクトを取得するコストが気になる場合は、この方法が良いでしょう。ActivityService なら、onDestroy() で close() すれば良いと思います。

[パターン3]
他のスレッドに SQLiteDatabase オブジェクトを渡す時は、元のスレッドで acquireReference() を実行し、呼び出されたスレッド内でも close() を呼ぶ。
acquireReference() はリファレンスカウンタをインクリメントするので、必要な close() の数が 1 つ増えます。そのため、作成したスレッドと元のスレッドの両方で close() が呼ばれてはじめて実際のクローズが行われます。どちらの close() が先に呼ばれても問題ありません。

以上のようにして、SQLiteDatabase を使用した時は不要になった時点で必ず close() を実行すれば、リークを避けることができます。
なお、クエリを実行した時の Cursor オブジェクトも、使い終わったら close() する必要があります。こちらは、aquireReference() に相当するメソッドが無いので、別のスレッドに渡すことはやめた方が良いと思います。

[関連記事]
Android での SQLiteDatabase のクローズについて (2) | DeVlog