SQLite API Database:データベース(識別子)のOpenとClose

投稿日:  更新日:

SQLite API(Android SDK)でデータベースを構築した場合、データベースへアクセスする際に、データベースのOpenとCloseという処理を伴います。

この点について、まとめました。

※環境:Android Studio Ladybug | 2024.2.1 Patch 2
    Kotlin 2.0.0
※サンプルの全体像は「SQLite APIでデータベースを構築」を参照

スポンサーリンク

データベースのOpen

データベースのアクセスを開始する前に、データベースと接続の確立を行います。

この接続の確立を行う処理が「データベース(識別子)のOpen」に当たります。

データベース識別子は、その結果として得られるものです。

SQLiteOpenHelper(GameDb)はプロパティwritableDatabaseに識別子を保持します。プロパティの参照を行えば、識別子が取り出されます。

この時、内部でデータベースのOpenが行われています。

    private SQLiteDatabase mDatabase;
    ...
	
    public SQLiteDatabase getWritableDatabase() {
        synchronized (this) {
            return getDatabaseLocked(true);
        }
    }
	...
	
    private SQLiteDatabase getDatabaseLocked(boolean writable) {
        if (mDatabase != null) {
            if (!mDatabase.isOpen()) {
                // Darn!  The user closed the database by calling mDatabase.close().
                mDatabase = null;
            } else if (!writable || !mDatabase.isReadOnly()) {
                // The database is already open for business.
                return mDatabase;
            }
        }
		
		...	// データベースをオープンする処理
	}
	...

SQLiteDatabaseの取り出しフロー

スポンサーリンク

データベースのClose

データベースの為に確保されたリソース(主にメモリー)を開放します。

Closeは関数SQLiteOpenHelper#close( )で行います。

    private SQLiteDatabase mDatabase;
	...
	
    public synchronized void close() {
        if (mIsInitializing) throw new IllegalStateException("Closed during initialization");

        if (mDatabase != null && mDatabase.isOpen()) {
            mDatabase.close();
            mDatabase = null;
        }
    }
	...
スポンサーリンク

注意点1:Close後の識別子は使用不可

Close後の識別子は使用できません。使用した場合はエラーになり、アプリが落ちます。

    val _gameDb = GameDb(this, 1)
	
    val _dbObj = _gameDb.writableDatabase
    _dbObj.insert(tableName, null, values)
    _gameDb.close()
    _dbObj.insert(tableName, null, values)
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase: データベース名
スポンサーリンク

注意点2:Close後の識別子を再Open

同じSQLiteOpenHelperインスタンスから取り出される識別子は、同じインスタンスになります。つまり、シングルトンです。

Closeを行うと、識別子の機能は失われます。その後、識別子を参照した際に、再びOpenが行われます。

    val _gameDb = GameDb(this, 1)

    Log.i(TAG, "SQLiteDatabase(A) = ${_gameDb.writableDatabase.hashCode()}")
    Log.i(TAG, "SQLiteDatabase(B) = ${_gameDb.writableDatabase.hashCode()}")
    close()
    Log.i(TAG, "SQLiteDatabase(C) = ${_gameDb.writableDatabase.hashCode()}")
SQLiteDatabase(A) = 250998571	// Open⇒新期作成の識別子
SQLiteDatabase(B) = 250998571	//      (A)と同じ識別子
SQLiteDatabase(C) = 117358060   // Open⇒新規作成の識別子

ですので、次のような使い方はエラーになりません。再びOpenされるからです。

    val _gameDb = GameDb(this, 1)
	
    _gameDb.writableDatabase.insert(tableName, null, values) // 初回Open
    _gameDb.close()
    _gameDb.writableDatabase.insert(tableName, null, values) // 再びOpen

ただし、Openは多くのコスト(処理時間、CPUの能力消費、メモリー消費)を必要とします。

小まめなCloseとOpenの繰り返しは、アプリケーション全体のパフォーマンス低下を引き起こす可能性があるので、行わない実装にすべきです。

注意点3:複数スレッドでClose

データベースへのアクセスはスレッドセーフです。

異なるスレッドから同時刻にSQLが送られて来ても、マネージメントシステムが正しく処理できるように調整してくれます。

データベースのアクセスはスレッドセーフ

ただし、マネージメントシステムの処理が終わる前に、識別子をクローズしてしまうと、エラーになります。

識別子のクローズによるエラー

スポンサーリンク

Closeのタイミング

識別子のCloseは、問題点1,2,3を引き起こす可能性があります。エラー時は「アプリが落ちる」という、非常に深刻な状況です。

ですので、アプリケーションの起動している間はCloseを行いません。

明示的にCloseを行わなかったとしても、アプリケーションのプロセスが終了する時に、全てのリソースは開放されます。その開放に任せれば良いです。
※昔は未Closeを継続すると、ワーニングを出していたと思うのですが…

あえて明示的に行いたいのであれば、アプリケーションコンポーネントのトップに当たるMainActivity#onDestroyで行います。アプリケーションが終了に達していると、考えられるためです。

識別子をオープンしておく範囲

class MainActivity : ComponentActivity() {

    private lateinit var gameDb: GameDb
    private lateinit var playerFlow: MutableStateFlow<List<Player>>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
		...

        gameDb = (application as MyApplication).gameDb
        playerFlow = MutableStateFlow<List<Player>>(gameDb.fetchTop10())
    }

    override fun onDestroy() {
        super.onDestroy()
        gameDb.close()
    }
}
スポンサーリンク

関連記事:

Androidは標準でSQLiteというRDBMS(Relational Database Management System)を扱います。 SQLiteは少し個性的なデータベースです。 データベース全般の説明を通して、SQLiteと他の違いをまとめます。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 2     Kotlin 2.0.0 ...
データベースを扱うアプリケーションの開発で、動作を確認するためにデータベースの閲覧が出来ると便利です。 データベースは、マネージメントシステムを介してアクセスされるため、プログラムからブラックボックスに見えます。 ですので、データベース内部の問題は見つけ難いです。 デバックおいてデータベースの閲覧が出来れば、内部の問題を用意に確認できます。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 2 ...
Androidが標準で扱うデータベースはSQLiteです。 ※詳細は「Androidで扱うデータベース」を参照 データベースを構築する方法は「SQLite API(Android SDK)」と「Room(Android Jetpack)」の2通りがあります。 今回は「SQLite API」でデータベースを構築する方法を紹介します。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 2     Kotlin 2.0.0 ...
アプリケーションが始めて起動した直後に、データの事前取り込み(初期状態の設定)を必要とする場合があります。 事前取り込みの方法ついて、まとめました。 SQLite API(Android SDK)でデータベースを構築した場合です。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 2     Kotlin 2.0.0 ※サンプルの全体像は「SQLite APIでデータベースを構築」を参照 ...
SQLite API(Android SDK)でデータベースを構築した場合、アプリのパフォーマンスを考えると、識別子の共有が必要になってきます。 この点について、まとめました。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 2     Kotlin 2.0.0 ※サンプルの全体像は「SQLite APIでデータベースを構築」を参照 ...
SQLite API(Android SDK)でデータベースを構築した場合、アクセスはスレッドセーフになります。 この点について、まとめました。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 2     Kotlin 2.0.0 ※サンプルの全体像は「SQLite APIでデータベースを構築」を参照 ...
スポンサーリンク