Android'de SQLite için en iyi uygulamalar nelerdir?


694

Bir Android uygulaması içindeki bir SQLite veritabanında sorgu yürütürken en iyi uygulamalar neler olarak kabul edilir?

Bir AsyncTask'in doInBackground öğesinden ekleri çalıştırmak, silmek ve seçmek sorgulamak güvenli midir? Yoksa UI İş Parçasını mı kullanmalıyım? Veritabanı sorgularının "ağır" olabileceğini ve uygulamayı kilitleyebileceği için UI iş parçacığını kullanmaması gerektiğini düşünüyorum - Uygulama Yanıt Vermiyor (ANR).

Birden fazla AsyncTasks'ım varsa, bir bağlantı paylaşmalı mı yoksa her biri bir bağlantı mı açmalılar?

Bu senaryolar için en iyi uygulamalar var mı?


10
Ne yaparsanız yapın, içerik sağlayıcınız (veya SQLite arabiriminiz) herkesin karşısına çıkıyorsa, girişlerinizi sterilize etmeyi unutmayın!
Kristopher Micinski

37
Kesinlikle UI iş parçacığından db erişim yapıyor OLMAMALIDIR, ben çok söyleyebilirim.
Edward Falk

@EdwardFalk Neden olmasın? Elbette bunu yapmanın geçerli olduğu kullanım durumları var mı?
Michael

4
UI iş parçacığından herhangi bir G / Ç, ağ erişimi vb. Yaparsanız, işlem tamamlanana kadar tüm cihaz donar. 1/20 saniyede tamamlanırsa, iyi. Daha uzun sürerse kötü bir kullanıcı deneyiminiz olur.
Edward Falk

Yanıtlar:


631

Ekler, güncellemeler, silme ve okumalar genellikle birden çok iş parçacığından sorun oluşturmaz, ancak Brad'in yanıtı doğru değildir. Bağlantılarınızı nasıl oluşturduğunuza ve kullandığınıza dikkat etmelisiniz. Veritabanınız bozulmasa bile güncelleme çağrılarınızın başarısız olacağı durumlar vardır.

Temel cevap.

SqliteOpenHelper nesnesi bir veritabanı bağlantısında tutar. Size bir okuma ve yazma bağlantısı sunuyor gibi görünüyor, ama gerçekten değil. Salt okunur olarak adlandırdığınızda veritabanı veritabanı bağlantısını alırsınız.

Yani, bir yardımcı örnek, bir db bağlantısı. Birden çok iş parçacığından kullansanız bile, her seferinde bir bağlantı. SqliteDatabase nesnesi, erişimi seri halde tutmak için java kilitlerini kullanır. Bu nedenle, 100 iş parçacığının bir db örneği varsa, gerçek disk üzerindeki veritabanına yapılan çağrılar serileştirilir.

Yani, java kodunda serileştirilmiş bir yardımcı, bir db bağlantısı. Bir iş parçacığı, 1000 iş parçacığı, aralarında paylaşılan bir yardımcı örneği kullanırsanız, tüm db erişim kodunuz seri olur. Ve hayat iyidir.

Veritabanına aynı anda gerçek farklı bağlantılardan yazmaya çalışırsanız, biri başarısız olur. İlk yapılana kadar beklemez ve sonra yazmaz. Sadece değişikliğinizi yazmaz. Daha da kötüsü, SQLiteDatabase'de doğru ekleme / güncelleştirme sürümünü çağırmazsanız, bir istisna alamazsınız. LogCat'inize bir mesaj alacaksınız, hepsi bu olacak.

Peki, birden fazla iş parçacığı? Bir yardımcı kullanın. Dönemi. Yalnızca bir iş parçacığının yazacağını BİLİYORSANIZ, birden çok bağlantı kullanabilir ve okumalarınız daha hızlı olur, ancak alıcı dikkat eder. O kadar çok test etmedim.

İşte çok daha ayrıntılı bir blog yazısı ve örnek bir uygulama.

Gray ve ben, Android veritabanı uygulamaları ile yerel olarak çalışan ve blog yazısında tanımladığım güvenli oluşturma / arama yapısını izleyen Ormlite tabanlı bir ORM aracını tamamlıyoruz. Çok yakında çıkmış olmalı. Bir göz at.


Bu arada bir takip blog yazısı var:

Çatalı daha önce belirtilen kilitleme örneğinin 2 puanına kadar kontrol edin :


2
Bir yana, Ormlite'nin android desteği ormlite.sourceforge.net/sqlite_java_android_orm.html adresinde bulunabilir . Örnek projeler, belgeler ve kavanozlar var.
Gri

1
Bir kenara. Ormlite kodu, dbhelper örneklerini yönetmek için kullanılabilecek yardımcı sınıflara sahiptir. Ormlite öğelerini kullanabilirsiniz, ancak gerekli değildir. Yardımcı sınıfları yalnızca bağlantı yönetimi yapmak için kullanabilirsiniz.
Kevin Galligan

31
Kāgii, detaylı açıklama için teşekkür ederim. Bir şeyi açıklığa kavuşturabilir misiniz - BİR yardımcıya sahip olmanız gerektiğini anlıyorum, ancak yalnızca bir bağlantınız mı var (yani bir SqliteDatabase nesnesi)? Başka bir deyişle, getWritableDatabase'i ne sıklıkta çağırmalısınız? Ve aynı derecede önemlisi, ne zaman close () diyorsunuz?
Artem

3
Kodu güncelledim. Blog ana bilgisayarlarını değiştirdiğimde orijinal kayboldu, ancak sorunu gösterecek zayıf bir örnek kod ekledim. Ayrıca, tek bağlantıyı nasıl yönetiyorsunuz? Başlangıçta çok daha karmaşık bir çözümüm vardı, ama o zamandan beri bunu değiştirdim. Buraya bir göz atın: touchlab.co/uncategorized/single-sqlite-connection
Kevin Galligan

bağlantıyı atmaya ihtiyaç var mı ve nerede yapmalı?
tugce

189

Eşzamanlı Veritabanı Erişimi

Blogumdaki aynı makale (daha fazla biçimlendirmeyi seviyorum)

Ben android veritabanı iş parçacığına güvenli erişim nasıl açıklayan küçük bir makale yazdı.


Kendi SQLiteOpenHelper'ınız olduğunu varsayarsak .

public class DatabaseHelper extends SQLiteOpenHelper { ... }

Şimdi veriyi ayrı bir iş parçacığında veritabanına yazmak istiyorsunuz.

 // Thread 1
 Context context = getApplicationContext();
 DatabaseHelper helper = new DatabaseHelper(context);
 SQLiteDatabase database = helper.getWritableDatabase();
 database.insert(…);
 database.close();

 // Thread 2
 Context context = getApplicationContext();
 DatabaseHelper helper = new DatabaseHelper(context);
 SQLiteDatabase database = helper.getWritableDatabase();
 database.insert(…);
 database.close();

Logcat'inizde aşağıdaki mesajı alacaksınız ve değişikliklerinizden biri yazılmayacak.

android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)

Bu, yeni SQLiteOpenHelper nesnesi her oluşturduğunuzda aslında yeni veritabanı bağlantısı oluşturduğunuz için oluyor. Veritabanına aynı anda gerçek farklı bağlantılardan yazmaya çalışırsanız, biri başarısız olur. (yukarıdaki yanıttan)

Birden çok iş parçacıklı veritabanı kullanmak için tek bir veritabanı bağlantısı kullandığımızdan emin olmamız gerekir.

Tek SQLiteOpenHelper nesnesini tutacak ve döndürecek singleton sınıfı Veritabanı Yöneticisi yapalım .

public class DatabaseManager {

    private static DatabaseManager instance;
    private static SQLiteOpenHelper mDatabaseHelper;

    public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
        if (instance == null) {
            instance = new DatabaseManager();
            mDatabaseHelper = helper;
        }
    }

    public static synchronized DatabaseManager getInstance() {
        if (instance == null) {
            throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
                    " is not initialized, call initialize(..) method first.");
        }

        return instance;
    }

    public SQLiteDatabase getDatabase() {
        return new mDatabaseHelper.getWritableDatabase();
    }

}

Veriyi veritabanına ayrı dizilerde yazan güncellenmiş kod şöyle görünecektir.

 // In your application class
 DatabaseManager.initializeInstance(new MySQLiteOpenHelper());
 // Thread 1
 DatabaseManager manager = DatabaseManager.getInstance();
 SQLiteDatabase database = manager.getDatabase()
 database.insert(…);
 database.close();

 // Thread 2
 DatabaseManager manager = DatabaseManager.getInstance();
 SQLiteDatabase database = manager.getDatabase()
 database.insert(…);
 database.close();

Bu size başka bir kilitlenme getirecektir.

java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase

Yalnızca bir veritabanı bağlantısı kullandığımız için, getDatabase () yöntemi , Thread1 ve Thread2 için aynı SQLiteDatabase nesnesinin örneğini döndürür . Ne oluyor, Thread1 veritabanını kapatabilirken Thread2 hala onu kullanıyor. Bu yüzden IllegalStateException çökmesi var.

Kimsenin veritabanını kullanmadığından emin olmamız ve ancak sonra kapatmamız gerekir. Stackoveflow'daki bazı kişilerin SQLiteDatabase'inizi asla kapatmamaları önerilir . Bu, aşağıdaki logcat mesajıyla sonuçlanacaktır.

Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed

Çalışma örnek

public class DatabaseManager {

    private int mOpenCounter;

    private static DatabaseManager instance;
    private static SQLiteOpenHelper mDatabaseHelper;
    private SQLiteDatabase mDatabase;

    public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
        if (instance == null) {
            instance = new DatabaseManager();
            mDatabaseHelper = helper;
        }
    }

    public static synchronized DatabaseManager getInstance() {
        if (instance == null) {
            throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
                    " is not initialized, call initializeInstance(..) method first.");
        }

        return instance;
    }

    public synchronized SQLiteDatabase openDatabase() {
        mOpenCounter++;
        if(mOpenCounter == 1) {
            // Opening new database
            mDatabase = mDatabaseHelper.getWritableDatabase();
        }
        return mDatabase;
    }

    public synchronized void closeDatabase() {
        mOpenCounter--;
        if(mOpenCounter == 0) {
            // Closing database
            mDatabase.close();

        }
    }

}

Aşağıdaki gibi kullanın.

SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way

Veritabanına her ihtiyacınız olduğunda DatabaseManager sınıfının openDatabase () yöntemini çağırmalısınız . Bu yöntemin içinde, veritabanının kaç kez açıldığını gösteren bir sayacımız var. Birine eşitse, yeni veritabanı bağlantısı oluşturmamız gerektiği anlamına gelir, eğer değilse, veritabanı bağlantısı zaten oluşturulmuş demektir.

Aynı şey closeDatabase () yönteminde de olur. Bu yöntemi her adlandırdığımızda sayaç azalır, sıfıra gittiğinde veritabanı bağlantısını kapatıyoruz.


Şimdi veritabanınızı kullanabilmeli ve iş parçacığının güvenli olduğundan emin olmalısınız.


10
Asla db'yi kapatmamanızı öneren insanlardan biriyim. Eğer sadece db açarsanız, "Sızıntı bulundu" hatası alırsınız, kapatmayın, sonra tekrar açmayı deneyin. Yalnızca tek bir açık yardımcı kullanıyorsanız ve db'yi asla kapatmazsanız, bu hatayı almazsınız. Başka türlü bulursanız, lütfen bana bildirin (kodla birlikte). Bir yerde bu konuda daha uzun bir yazım vardı, ama bulamıyorum. Buradaki ortak yazılım tarafından cevaplanan ve her ikisini de SO puan departmanında kimin üstesinden geldi: stackoverflow.com/questions/7211941/…
Kevin Galligan

4
Daha fazla düşünce. # 1, yardımcıyı yöneticinizin içinde yaratırdım. Dışarıda olmak için sorun istemek. Yeni geliştirici yardımcıyı çılgın bir nedenden dolayı doğrudan arayabilir. Ayrıca, bir init yöntemine ihtiyacınız olacaksa, örnek zaten varsa bir istisna atın. Multi-db uygulamaları olduğu gibi başarısız olur. # 2, neden mDatabase alanı? Yardımcıdan edinilebilir. # 3, "asla kapanma" yönünde ilk adımınız olarak, uygulamanız çöktüğünde ve "kapatılmadığında" db'nize ne olur? İpucu, hiçbir şey. Güzel, çünkü SQLite süper kararlı. Bu, neden kapatmanız gerekmediğini anlamadaki 1. adımdı .
Kevin Galligan

1
Örneği almadan önce aramak için herkese açık bir başlatma yöntemi kullanmanızın herhangi bir nedeni var mı? Neden adı verilen özel bir kurucu yok if(instance==null)? Her seferinde başlatma çağırmaktan başka seçenek bırakmazsınız; başka uygulamalarda vb. başlatılmış olup olmadığını başka nasıl bileceksiniz?
ChiefTwoPencils

1
initializeInstance()türünde bir parametre var SQLiteOpenHelper, ancak yorumunuzda kullanmayı belirtmiştiniz DatabaseManager.initializeInstance(getApplicationContext());. Ne oluyor? Bu nasıl olabilir?
faizal

2
@DmytroDanylyk "DatabaseManager, iş parçacığı için güvenli singleton'dur, bu nedenle virsir'in sorusuna yanıt olarak doğru olmayan herhangi bir yerde kullanılabilir". Nesneler paylaşılan çapraz işlemler değildir. DatabaseManager'ınız senkronizasyon adaptöründe olduğu gibi farklı bir işlemde hiçbir duruma sahip olmayacaktır (android: process = ": sync")
15'te çırpın

17
  • Uzun süreli işlemler (50ms +) için a Threadveya kullanın AsyncTask. Nerede olduğunu görmek için uygulamanızı test edin. Çoğu işlem (muhtemelen) bir iş parçacığı gerektirmez, çünkü çoğu işlem (muhtemelen) yalnızca birkaç satır içerir. Toplu işlemler için bir iplik kullanın.
  • SQLiteDatabaseDiskler arasındaki her DB için bir örneği iş parçacıkları arasında paylaşın ve açık bağlantıları izlemek için bir sayma sistemi uygulayın.

Bu senaryolar için en iyi uygulamalar var mı?

Tüm sınıflarınız arasında statik bir alan paylaşın. Bunun ve paylaşılması gereken diğer şeyler için bir singleton tutuyordum. Veritabanını asla erken kapatmamanız veya açık bırakmamanız için bir sayma şeması da (genellikle AtomicInteger kullanılarak) kullanılmalıdır.

Çözümüm:

En güncel sürüm için bkz. Https://github.com/JakarCo/databasemanager ama kodu da burada güncel tutmaya çalışacağım. Çözümümü anlamak istiyorsanız, koda bakın ve notlarımı okuyun. Notlarım genellikle oldukça faydalı.

  1. kodu kopyalayın / yeni bir dosyaya yapıştırın DatabaseManager. (veya github'dan indirin)
  2. uzatmak DatabaseManagerve uygulamak onCreateve onUpgradeher zamanki gibi. DatabaseManagerDiskte farklı veritabanlarına sahip olmak için bir sınıfın birden çok alt sınıfını oluşturabilirsiniz .
  3. Alt sınıfınızı başlatın ve sınıfı getDb()kullanmak için arayın SQLiteDatabase.
  4. Gerçekleştirdiğiniz close()her alt sınıf için arayın

Kopyalanacak / yapıştırılacak kod :

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;

import java.util.concurrent.ConcurrentHashMap;

/** Extend this class and use it as an SQLiteOpenHelper class
 *
 * DO NOT distribute, sell, or present this code as your own. 
 * for any distributing/selling, or whatever, see the info at the link below
 *
 * Distribution, attribution, legal stuff,
 * See https://github.com/JakarCo/databasemanager
 * 
 * If you ever need help with this code, contact me at support@androidsqlitelibrary.com (or support@jakar.co )
 * 
 * Do not sell this. but use it as much as you want. There are no implied or express warranties with this code. 
 *
 * This is a simple database manager class which makes threading/synchronization super easy.
 *
 * Extend this class and use it like an SQLiteOpenHelper, but use it as follows:
 *  Instantiate this class once in each thread that uses the database. 
 *  Make sure to call {@link #close()} on every opened instance of this class
 *  If it is closed, then call {@link #open()} before using again.
 * 
 * Call {@link #getDb()} to get an instance of the underlying SQLiteDatabse class (which is synchronized)
 *
 * I also implement this system (well, it's very similar) in my <a href="http://androidslitelibrary.com">Android SQLite Libray</a> at http://androidslitelibrary.com
 * 
 *
 */
abstract public class DatabaseManager {

    /**See SQLiteOpenHelper documentation
    */
    abstract public void onCreate(SQLiteDatabase db);
    /**See SQLiteOpenHelper documentation
     */
    abstract public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
    /**Optional.
     * *
     */
    public void onOpen(SQLiteDatabase db){}
    /**Optional.
     * 
     */
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
    /**Optional
     * 
     */
    public void onConfigure(SQLiteDatabase db){}



    /** The SQLiteOpenHelper class is not actually used by your application.
     *
     */
    static private class DBSQLiteOpenHelper extends SQLiteOpenHelper {

        DatabaseManager databaseManager;
        private AtomicInteger counter = new AtomicInteger(0);

        public DBSQLiteOpenHelper(Context context, String name, int version, DatabaseManager databaseManager) {
            super(context, name, null, version);
            this.databaseManager = databaseManager;
        }

        public void addConnection(){
            counter.incrementAndGet();
        }
        public void removeConnection(){
            counter.decrementAndGet();
        }
        public int getCounter() {
            return counter.get();
        }
        @Override
        public void onCreate(SQLiteDatabase db) {
            databaseManager.onCreate(db);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            databaseManager.onUpgrade(db, oldVersion, newVersion);
        }

        @Override
        public void onOpen(SQLiteDatabase db) {
            databaseManager.onOpen(db);
        }

        @Override
        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            databaseManager.onDowngrade(db, oldVersion, newVersion);
        }

        @Override
        public void onConfigure(SQLiteDatabase db) {
            databaseManager.onConfigure(db);
        }
    }

    private static final ConcurrentHashMap<String,DBSQLiteOpenHelper> dbMap = new ConcurrentHashMap<String, DBSQLiteOpenHelper>();

    private static final Object lockObject = new Object();


    private DBSQLiteOpenHelper sqLiteOpenHelper;
    private SQLiteDatabase db;
    private Context context;

    /** Instantiate a new DB Helper. 
     * <br> SQLiteOpenHelpers are statically cached so they (and their internally cached SQLiteDatabases) will be reused for concurrency
     *
     * @param context Any {@link android.content.Context} belonging to your package.
     * @param name The database name. This may be anything you like. Adding a file extension is not required and any file extension you would like to use is fine.
     * @param version the database version.
     */
    public DatabaseManager(Context context, String name, int version) {
        String dbPath = context.getApplicationContext().getDatabasePath(name).getAbsolutePath();
        synchronized (lockObject) {
            sqLiteOpenHelper = dbMap.get(dbPath);
            if (sqLiteOpenHelper==null) {
                sqLiteOpenHelper = new DBSQLiteOpenHelper(context, name, version, this);
                dbMap.put(dbPath,sqLiteOpenHelper);
            }
            //SQLiteOpenHelper class caches the SQLiteDatabase, so this will be the same SQLiteDatabase object every time
            db = sqLiteOpenHelper.getWritableDatabase();
        }
        this.context = context.getApplicationContext();
    }
    /**Get the writable SQLiteDatabase
     */
    public SQLiteDatabase getDb(){
        return db;
    }

    /** Check if the underlying SQLiteDatabase is open
     *
     * @return whether the DB is open or not
     */
    public boolean isOpen(){
        return (db!=null&&db.isOpen());
    }


    /** Lowers the DB counter by 1 for any {@link DatabaseManager}s referencing the same DB on disk
     *  <br />If the new counter is 0, then the database will be closed.
     *  <br /><br />This needs to be called before application exit.
     * <br />If the counter is 0, then the underlying SQLiteDatabase is <b>null</b> until another DatabaseManager is instantiated or you call {@link #open()}
     *
     * @return true if the underlying {@link android.database.sqlite.SQLiteDatabase} is closed (counter is 0), and false otherwise (counter > 0)
     */
    public boolean close(){
        sqLiteOpenHelper.removeConnection();
        if (sqLiteOpenHelper.getCounter()==0){
            synchronized (lockObject){
                if (db.inTransaction())db.endTransaction();
                if (db.isOpen())db.close();
                db = null;
            }
            return true;
        }
        return false;
    }
    /** Increments the internal db counter by one and opens the db if needed
    *
    */
    public void open(){
        sqLiteOpenHelper.addConnection();
        if (db==null||!db.isOpen()){
                synchronized (lockObject){
                    db = sqLiteOpenHelper.getWritableDatabase();
                }
        } 
    }
}

1
"Kapat" dediğinizde ve sonra sınıfı yeniden kullanmaya çalıştığınızda ne olur? çökecek mi yoksa DB'yi tekrar kullanabilmek için otomatik olarak kendini yeniden mi başlatacak?
android geliştirici

1
@androiddeveloper, Eğer ararsanız close, openaynı sınıf örneğini kullanmadan önce tekrar aramanız gerekir VEYA yeni bir örnek oluşturabilirsiniz. İçinde bu yana closekod, ben seti db=null, aralarından dönüş değeri kullanmak için mümkün olmaz getDbEğer bir olsun diye, (boş değerli olacağından) NullPointerExceptionbir şey gibi yapsammyInstance.close(); myInstance.getDb().query(...);
Reed

Neden birleştirmek değil getDb()ve open()tek yöntem haline?
Alex Burdusel

@Burdu, Veritabanı sayacı ve kötü tasarım üzerinde ekstra kontrol sağlayan bir karışım. Yine de bunu yapmanın en iyi yolu değil. Birkaç gün içinde güncelleyeceğim.
Reed

@Burdu, yeni güncelledim. Yeni kodu buradan alabilirsiniz . Test etmedim, bu yüzden lütfen değişiklikleri yapmam gerekip gerekmediğini bana bildirin.
Reed,

11

Veritabanı çoklu iş parçacığı ile çok esnektir. Uygulamalarım DB'lerini aynı anda birçok farklı iş parçacığından vurdu ve gayet iyi. Bazı durumlarda aynı anda DB isabet birden çok işlem var ve bu da iyi çalışıyor.

Zaman uyumsuz görevleriniz - yapabildiğiniz zaman aynı bağlantıyı kullanın, ancak yapmanız gerekiyorsa, DB'ye farklı görevlerden erişmek için sorun yoktur.


Ayrıca, farklı bağlantılarda okuyucularınız ve yazarlarınız var mı yoksa tek bir bağlantı mı paylaşmalılar? Teşekkürler.
Gri

@Gray - Doğru, bundan açıkça bahsetmeliydim. Bağlantılar olabildiğince aynı bağlantıyı kullanırdım, ancak kilitleme dosya sistemi düzeyinde işlendiğinden, kodda birden çok kez açabilirsiniz, ancak mümkün olduğunca tek bir bağlantı kullanırdım. Android sqlite DB çok esnek ve affedicidir.
Brad Hein

3
@Gray, sadece bu yöntemi kullanan kişiler için güncellenmiş bir bilgi göndermek istedi. Belgeler diyor ki: Bu yöntem artık hiçbir şey yapmıyor. Kullanmayın.
Pijusn

3
Bu yöntemi korkunç bir şekilde başarısız buldum, birden fazla uygulamadan erişmek için ContentProvider'a geçiyoruz. Yöntemlerimizde biraz eşzamanlılık yapmamız gerekecek, ancak bu, verilere aynı anda erişen süreçlerle ilgili sorunları çözmelidir.
JPM

2
Bunun eski olduğunu biliyorum, ama yanlış. Bu belki farklı gelen erişim DB tamam çalışmak SQLiteDatabasefarklı nesnelerin AsyncTasks / Threads, ancak bazen hangi hatalara yol açacaktır neden SQLiteDatabase (hat 1297) kullanır Locks
Reed

7

Dmytro'nun cevabı davam için iyi çalışıyor. Bence işlevi senkronize olarak beyan etmek daha iyidir. en azından benim durumum için, aksi takdirde boş bir işaretçi istisna çağırır, örneğin getWritableDatabase henüz bir iş parçacığında döndürülmedi ve openDatabse başka bir iş parçacığında aradı.

public synchronized SQLiteDatabase openDatabase() {
    if(mOpenCounter.incrementAndGet() == 1) {
        // Opening new database
        mDatabase = mDatabaseHelper.getWritableDatabase();
    }
    return mDatabase;
}

mDatabaseHelper.getWritableDatabase (); Bu yeni veritabanı nesnesi oluşturmak olmaz
uzaktaki

5

bununla birkaç saat uğraştıktan sonra, db yürütme başına yalnızca bir db yardımcı nesnesi kullanabileceğinizi buldum. Örneğin,

for(int x = 0; x < someMaxValue; x++)
{
    db = new DBAdapter(this);
    try
    {

        db.addRow
        (
                NamesStringArray[i].toString(), 
                StartTimeStringArray[i].toString(),
                EndTimeStringArray[i].toString()
        );

    }
    catch (Exception e)
    {
        Log.e("Add Error", e.toString());
        e.printStackTrace();
    }
    db.close();
}

olduğu gibi:

db = new DBAdapter(this);
for(int x = 0; x < someMaxValue; x++)
{

    try
    {
        // ask the database manager to add a row given the two strings
        db.addRow
        (
                NamesStringArray[i].toString(), 
                StartTimeStringArray[i].toString(),
                EndTimeStringArray[i].toString()
        );

    }
    catch (Exception e)
    {
        Log.e("Add Error", e.toString());
        e.printStackTrace();
    }

}
db.close();

döngü yinelediğinde yeni bir DBAdapter oluşturmak, dizelerimi yardımcı sınıfım aracılığıyla bir veritabanına alabilmemin tek yoluydu.


4

SQLiteDatabase API'lerini anladığım, çok iş parçacıklı bir uygulamanız olması durumunda, tek bir veritabanına işaret eden 1'den fazla SQLiteDatabase nesnesine sahip olamayacağınızdır.

Nesne kesinlikle oluşturulabilir, ancak farklı iş parçacıkları / işlemler (farklı) farklı SQLiteDatabase nesnelerini (JDBC Bağlantısında nasıl kullandığımız gibi) kullanmaya başlarsa ekler / güncellemeler başarısız olur.

Buradaki tek çözüm 1 SQLiteDatabase nesnesine bağlı kalmaktır ve 1'den fazla iş parçacığında bir startTransaction () kullanıldığında, Android farklı iş parçacıkları arasındaki kilitlemeyi yönetir ve özel güncelleme erişimine sahip olmak için bir kerede yalnızca 1 iş parçacığına izin verir.

Ayrıca veritabanından "Okumalar" yapabilir ve farklı bir iş parçacığında aynı SQLiteDatabase nesnesini kullanabilirsiniz (başka bir iş parçacığı yazarken) ve hiçbir zaman veritabanı bozulması olmaz yani "okuma iş parçacığı" veritabanından " write thread "verileri her ikisinde de aynı SQLiteDatabase nesnesini kullanmasına rağmen verir.

Bu, JDBC'de bağlantı nesnesinin farklı olduğundan, okuma ve yazma iş parçacıkları arasında bağlantı nesnesini geçirirseniz (aynı kullanırsanız), muhtemelen taahhüt edilmemiş verileri de yazdırırız.

Benim kurumsal uygulamada, BG iş parçacığı SQLiteDatabase nesnesini (yalnızca) tutarken, UI iş parçacığı asla beklemek zorunda kalmamak için koşullu denetimleri kullanmaya çalışın. Kullanıcı arabirimi eylemlerini tahmin etmeye ve BG iş parçacığının 'x' saniye boyunca çalışmasını ertelemeye çalışıyorum. Ayrıca, UI İş Parçacığı ilk önce onu almak için SQLiteDatabase Bağlantı nesneleri dağıtmayı yönetmek için PriorityQueue koruyabilirsiniz.


Ve bu PriorityQueue - dinleyicileri (veritabanı nesnesi almak isteyen) veya SQL sorgularına ne koyarsınız?
Pijusn

Öncelik sırası yaklaşımını değil, aslında "arayan" iş parçacıklarını kullanmadım.
Swaroop

@Swaroop: PCMIIW "read thread" wouldn't read the data from the database till the "write thread" commits the data although both use the same SQLiteDatabase object,. Bu her zaman doğru değildir, eğer "thread yaz" dan hemen sonra "thread okuma" yı başlatırsanız, yeni güncellenmiş verileri (write thread'e eklenmiş veya güncellenmiş) alamayabilirsiniz. Okuma iş parçacığı, yazma iş parçacığı başlamadan önce verileri okuyabilir. Bunun nedeni, yazma işleminin başlangıçta özel kilit yerine Ayrılmış kilidi etkinleştirmesidir.
Amit Vikram Singh

4

Google I / O 2017'de açıklanan yeni mimari yaklaşımını uygulamayı deneyebilirsiniz .

Oda adı verilen yeni ORM kütüphanesini de içerir

Üç ana bileşen içerir: @Entity, @dao ve @Database

User.java

@Entity
public class User {
  @PrimaryKey
  private int uid;

  @ColumnInfo(name = "first_name")
  private String firstName;

  @ColumnInfo(name = "last_name")
  private String lastName;

  // Getters and setters are ignored for brevity,
  // but they're required for Room to work.
}

UserDao.java

@Dao
public interface UserDao {
  @Query("SELECT * FROM user")
  List<User> getAll();

  @Query("SELECT * FROM user WHERE uid IN (:userIds)")
  List<User> loadAllByIds(int[] userIds);

  @Query("SELECT * FROM user WHERE first_name LIKE :first AND "
       + "last_name LIKE :last LIMIT 1")
  User findByName(String first, String last);

  @Insert
  void insertAll(User... users);

  @Delete
  void delete(User user);
}

AppDatabase.java

@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
  public abstract UserDao userDao();
}

Birden çok N-N ilişkisine sahip bir Veritabanına Oda önermiyorum, çünkü bu yaklaşımı iyi işlemiyor ve bu ilişkiler için bir çözüm bulmak için çok fazla kod yazmanız gerekiyor.
AlexPad

3

Bazı sorunlar yaşadım, sanırım neden yanlış gittiğimi anladım.

Ben dahil bir veritabanı sarmalayıcı sınıfı yazmıştı close() bir ayna yakın olarak open()getWriteableDatabase denilen ve sonra bir geçiş var yardımcı denilen ContentProvider. Modeli ContentProviderkullanmak için kullanmıyor SQLiteDatabase.close()kod kullanmak gibi büyük bir ipucu olduğunu getWriteableDatabaseBazı durumlarda hala doğrudan erişim (ekran doğrulama sorguları ana bu yüzden getWriteableDatabase / rawQuery modeline taşındı.

Singleton kullanıyorum ve yakın belgelerde biraz uğursuz yorum var

Kapat Herhangi bir açık veritabanı nesnesini

(cesurum).

Ben veritabanına erişmek için arka plan iş parçacıkları kullanın ve onlar ön planda aynı zamanda çalıştırmak aralıklı çökmeler oldu.

Bu yüzden close()veritabanı referansları tutan herhangi bir iş parçacığı ne olursa olsun kapanmaya zorlar - bu yüzden close()kendisi sadece eşleşen geri alma değil, herhangi birgetWriteableDatabase zorla kapatma açık istekleri . Çoğu zaman kod tek iş parçacığı olduğu için bu bir sorun değildir, ancak çok iş parçacıklı durumlarda her zaman senkronizasyonun açılması ve kapatılması olasılığı vardır.

SqLiteDatabaseHelper kod örneğinin önemli olduğunu açıklayan başka bir yerde yorumları okuduktan sonra, kapatmak istediğiniz tek zaman yedek kopya yapmak istediğiniz durumun olmasını ve tüm bağlantıların kapanmasını zorlamak ve SqLite'ı zorlamak istediğiniz yerdir hakkında loit olabilecek önbellekleri yazın - diğer bir deyişle, tüm uygulama veritabanı etkinliğini durdurun, Yardımcı'nın izini kaybetmesi durumunda kapatın, herhangi bir dosya düzeyinde etkinlik (yedekleme / geri yükleme) yapın ve yeniden başlayın.

Kontrollü bir şekilde denemek ve kapatmak iyi bir fikir gibi görünse de, gerçek şu ki Android VM'nizi çöpe atma hakkını saklı tutar, böylece herhangi bir kapanış önbelleğe alınan güncellemelerin yazılmaması riskini azaltır, ancak cihaz garanti edilemezse vurgulanırsa ve imleçlerinizi ve veritabanlarına (statik üyeler olmamalıdır) referansları doğru şekilde serbest bıraktıysanız, yardımcı yine de veritabanını kapatmış olacaktır.

Benim görüşüm yaklaşımın:

Tek bir sarıcıdan açmak için getWriteableDatabase'i kullanın. (Bir bağlam ihtiyacını çözmek için statik bir uygulama bağlamı sağlamak için türetilmiş bir uygulama sınıfı kullandım).

Asla doğrudan yakın aramayınız.

Sonuçta ortaya çıkan veritabanını hiçbir zaman belirgin bir kapsamı olmayan herhangi bir nesnede saklamayın ve örtülü bir close () tetiklemek için referans sayımına güvenmeyin.

Dosya düzeyinde işlem yapıyorsanız, tüm veritabanı etkinliğini durma noktasına getirin ve kaçak iş parçasının başarısız olması ve kapalı veritabanının en azından uygun işlemlere sahip olması için uygun işlemleri yazdığınız varsayımında kaçak bir iş parçacığı olması durumunda kapatın. potansiyel olarak kısmi bir işlemin dosya düzeyinde bir kopyasından daha fazladır.


0

Yanıtın geç olduğunu biliyorum, ancak android'de sqlite sorguları yürütmenin en iyi yolu özel bir içerik sağlayıcısıdır. Bu şekilde kullanıcı arabirimi veritabanı sınıfıyla (SQLiteOpenHelper sınıfını genişleten sınıf) ayrılır. Ayrıca sorgular bir arka plan iş parçacığında (İmleç Yükleyici) yürütülür.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.