Android SQLite DB Kapan Harus Menutup

96

Saya bekerja dengan database SQLite di android. Manajer database saya adalah tunggal dan sekarang membuka koneksi ke database saat diinisialisasi. Aman untuk membiarkan database terbuka sepanjang waktu sehingga ketika seseorang memanggil kelas saya untuk bekerja dengan database itu sudah terbuka? Atau haruskah saya membuka dan menutup database sebelum dan sesudah setiap akses diperlukan. Apakah ada salahnya membiarkannya terbuka sepanjang waktu?

Terima kasih!

w. donahue
sumber

Jawaban:

60

saya akan tetap membukanya sepanjang waktu, dan menutupnya dalam beberapa metode siklus hidup seperti onStopatau onDestroy. dengan cara itu, Anda dapat dengan mudah memeriksa apakah database sudah digunakan dengan memanggil isDbLockedByCurrentThreadatau isDbLockedByOtherThreadspada satu SQLiteDatabaseobjek setiap kali sebelum Anda menggunakannya. ini akan mencegah beberapa manipulasi ke database dan menyimpan aplikasi Anda dari potensi crash

jadi di singleton Anda, Anda mungkin memiliki metode seperti ini untuk mendapatkan SQLiteOpenHelperobjek tunggal Anda :

private SQLiteDatabase db;
private MyDBOpenHelper mySingletonHelperField;
public MyDBOpenHelper getDbHelper() {
    db = mySingletonHelperField.getDatabase();//returns the already created database object in my MyDBOpenHelper class(which extends `SQLiteOpenHelper`)
    while(db.isDbLockedByCurrentThread() || db.isDbLockedByOtherThreads()) {
        //db is locked, keep looping
    }
    return mySingletonHelperField;
}

jadi setiap kali Anda ingin menggunakan objek pembantu terbuka, panggil metode pengambil ini (pastikan itu berulir)

Metode lain di tunggal Anda mungkin (dipanggil SETIAP KALI sebelum Anda mencoba memanggil pengambil di atas):

public void setDbHelper(MyDBOpenHelper mySingletonHelperField) {
    if(null == this.mySingletonHelperField) {
        this.mySingletonHelperField = mySingletonHelperField;
        this.mySingletonHelperField.setDb(this.mySingletonHelperField.getWritableDatabase());//creates and sets the database object in the MyDBOpenHelper class
    }
}

Anda mungkin ingin menutup database di singleton juga:

public void finalize() throws Throwable {
    if(null != mySingletonHelperField)
        mySingletonHelperField.close();
    if(null != db)
        db.close();
    super.finalize();
}

jika pengguna aplikasi Anda memiliki kemampuan untuk membuat banyak interaksi database dengan sangat cepat, Anda harus menggunakan sesuatu seperti yang telah saya tunjukkan di atas. tetapi jika ada interaksi database minimal, saya tidak akan mengkhawatirkannya, dan cukup buat dan tutup database setiap saat.

james
sumber
Saya dapat mempercepat akses database dengan faktor 2 dengan pendekatan ini (tetap buka database), terima kasih
teh.fonsi
2
Memutar adalah teknik yang sangat buruk. Lihat jawaban saya di bawah.
mixel
1
@ixel poin yang bagus. Saya yakin saya memposting jawaban ini sebelum API 16 tersedia, tetapi saya bisa saja salah
james
1
@binnyb Saya rasa lebih baik perbarui jawaban Anda agar tidak menyesatkan orang.
mixel
3
WARN isDbLockedByOtherThreads () adalah Depricated dan mengembalikan false sejak API 16. Juga memeriksa isDbLockedByCurrentThread () akan memberikan perulangan tak terbatas karena menghentikan thread saat ini dan tidak ada yang bisa 'membuka DB' sehingga metode ini mengembalikan true.
matreshkin
20

Sampai sekarang tidak perlu memeriksa apakah database dikunci oleh thread lain. Saat Anda menggunakan SQLiteOpenHelper tunggal di setiap utas, Anda aman. Dari isDbLockedByCurrentThreaddokumentasi:

Nama metode ini berasal dari saat memiliki koneksi aktif ke database yang berarti bahwa thread tersebut memegang kunci sebenarnya pada database. Saat ini, tidak ada lagi "kunci database" yang sebenarnya meskipun thread dapat memblokir jika thread tidak dapat memperoleh koneksi database untuk melakukan operasi tertentu.

isDbLockedByOtherThreads tidak digunakan lagi sejak API Level 16.

mixel
sumber
Tidak ada gunanya menggunakan satu contoh per utas. SQLiteOpenHelper aman untuk thread. Ini juga sangat tidak efisien dalam memori. Sebagai gantinya, aplikasi harus menyimpan satu instance SQLiteOpenHelper per database. Untuk konkurensi yang lebih baik, disarankan untuk menggunakan WriteAheadLogging yang menyediakan penggabungan koneksi hingga 4 koneksi.
ejboy
@ejboy maksudku itu. "Gunakan singleton (= satu) SQLiteOpenHelper di setiap utas", bukan "per utas".
mixel
15

Mengenai pertanyaan:

Manajer database saya adalah tunggal dan sekarang membuka koneksi ke database saat diinisialisasi.

Kita harus membagi 'membuka DB', 'membuka koneksi'. SQLiteOpenHelper.getWritableDatabase () memberikan DB terbuka. Tetapi kami tidak harus mengontrol koneksi karena dilakukan secara internal.

Aman untuk membiarkan database terbuka sepanjang waktu sehingga ketika seseorang memanggil kelas saya untuk bekerja dengan database itu sudah terbuka?

Ya itu. Koneksi tidak hang jika transaksi ditutup dengan benar. Perhatikan bahwa DB Anda juga akan ditutup secara otomatis jika GC menyelesaikannya.

Atau haruskah saya membuka dan menutup database sebelum dan sesudah setiap akses diperlukan.

Menutup instance SQLiteDatabase tidak memberikan hasil yang luar biasa kecuali menutup koneksi tetapi ini adalah hal yang buruk bagi developer jika ada beberapa koneksi saat ini. Selain itu, setelah SQLiteDatabase.close (), SQLiteOpenHelper.getWritableDatabase () akan mengembalikan instance baru.

Apakah ada salahnya membiarkannya terbuka sepanjang waktu?

Tidak, tidak ada. Perhatikan juga bahwa menutup DB pada momen dan utas yang tidak terkait misalnya di Activity.onStop () mungkin menutup koneksi aktif dan membiarkan data dalam keadaan tidak konsisten.

matreshkin.dll
sumber
Terima kasih, setelah membaca kata-kata Anda "setelah SQLiteDatabase.close (), SQLiteOpenHelper.getWritableDatabase () akan mengembalikan contoh baru" Saya menyadari bahwa akhirnya saya memiliki jawaban untuk masalah lama aplikasi saya. Untuk masalah, yang menjadi kritis setelah migrasi ke Android 9 (lihat stackoverflow.com/a/54224922/297710 )
yvolk
1

Dari perspektif kinerja, cara yang optimal adalah dengan menyimpan satu contoh SQLiteOpenHelper di tingkat aplikasi. Membuka database bisa mahal dan merupakan operasi pemblokiran, jadi tidak boleh dilakukan pada thread utama dan / atau dalam metode siklus hidup aktivitas.

Metode setIdleConnectionTimeout () (diperkenalkan di Android 8.1) bisa digunakan untuk mengosongkan RAM saat database tidak digunakan. Jika idle timeout diatur, koneksi database akan ditutup setelah periode tidak aktif, yaitu ketika database tidak diakses. Koneksi akan dibuka kembali secara transparan ke aplikasi, saat kueri baru dijalankan.

Selain itu, aplikasi bisa memanggil releaseMemory () saat masuk ke latar belakang atau mendeteksi tekanan memori, misalnya di onTrimMemory ()

ejboy
sumber
0

Anda juga dapat menggunakan ContentProvider. Ini akan melakukan hal ini untuk Anda.

Gregory Buiko
sumber
-9

Buat konteks Aplikasi Anda sendiri, lalu buka dan tutup database dari sana. Objek itu juga memiliki metode OnTerminate () yang bisa Anda gunakan untuk menutup koneksi. Saya belum mencobanya tetapi tampaknya pendekatan yang lebih baik.

@binnyb: Saya tidak suka menggunakan finalize () untuk menutup koneksi. Mungkin berhasil, tetapi dari apa yang saya pahami menulis kode dalam metode Java finalize () adalah ide yang buruk.

Leander
sumber
Anda tidak ingin mengandalkan finalisasi karena Anda tidak pernah tahu kapan akan dipanggil. Sebuah objek mungkin tetap dalam limbo sampai GC memutuskan untuk membersihkannya, dan baru kemudian finalize () akan dipanggil. Itulah mengapa tidak berguna. Cara yang benar untuk melakukannya adalah dengan memiliki metode yang dipanggil ketika objek tidak lagi digunakan (terlepas dari sampah dikumpulkan atau tidak), dan itulah onTerminate ().
erwan
5
Dokumen menyatakan dengan jelas bahwa onTerminatetidak akan dipanggil dalam lingkungan produksi - developer.android.com/reference/android/app/…
Eliezer