Android上SQLite的最佳做法是什么?


694

在Android应用程序中的SQLite数据库上执行查询时,最佳做法是什么?

从AsyncTask的doInBackground运行插入,删除和选择查询是否安全?还是应该使用UI线程?我想数据库查询可能“繁重”,并且不应该使用UI线程,因为它可以锁定应用程序-导致应用程序无响应(ANR)。

如果我有几个AsyncTask,它们应该共享一个连接还是应该每个打开一个连接?

这些方案是否有最佳实践?


10
无论您做什么,请记住如果您的内容提供者(或SQLite接口)公开面对您,请清理输入内容!
Kristopher Micinski 2012年

37
您绝对不应该从UI线程进行数据库访问,我可以告诉您很多。
爱德华·福尔克

@EdwardFalk为什么不呢?当然,在某些用例中可以这样做吗?
迈克尔(Michael)

4
如果您从UI线程执行任何I / O,网络访问等操作,则整个设备将冻结,直到操作完成。如果在1/20秒内完成,则可以。如果花费的时间更长,您将获得糟糕的用户体验。
爱德华·福尔克

Answers:


632

插入,更新,删除和读取通常可以从多个线程进行,但是Brad的回答是不正确的。您必须谨慎创建和使用连接。在某些情况下,即使数据库没有损坏,更新调用也会失败。

基本答案。

SqliteOpenHelper对象可保持一个数据库连接。它似乎为您提供了读写连接,但实际上没有。调用只读,无论如何都将获得写数据库连接。

因此,一个帮助程序实例,一个数据库连接。即使您从多个线程使用它,一次也只有一个连接。SqliteDatabase对象使用Java锁来保持访问序列化。因此,如果100个线程有一个数据库实例,则对实际磁盘数据库的调用将被序列化。

因此,一个帮助程序和一个数据库连接已通过Java代码序列化。一个线程(1000个线程),如果使用它们之间共享的一个帮助程序实例,则所有数据库访问代码都是串行的。生活是美好的。

如果您尝试同时从实际的不同连接中写入数据库,则连接将失败。它不会等到第一个完成后再写。它根本不会写您的更改。更糟糕的是,如果您没有在SQLiteDatabase上调用正确版本的insert / update,您将不会获得异常。您只会在LogCat中收到一条消息,仅此而已。

那么,多个线程?使用一个助手。期。如果您仅知道将要写入一个线程,则可以使用多个连接,并且读取速度会更快,但请注意。我还没测试那么多。

这是一篇博客文章,其中包含更多详细信息和示例应用程序。

Gray和我实际上是在基于Ormlite的基础上包装一个ORM工具,该工具可与Android数据库实现本地兼容,并遵循我在博客文章中描述的安全创建/调用结构。那应该很快出来。看一看。


同时,有一个后续博客文章:

还要通过前面提到的锁定示例的2point0来检查fork :


2
顺便说一句,可以在ormlite.sourceforge.net/sqlite_java_android_orm.html上找到Ormlite的android支持。有示例项目,文档和jar。
灰色

1
一会儿。Ormlite代码具有可用于管理dbhelper实例的帮助程序类。您可以使用ormlite的东西,但这不是必需的。您可以使用帮助程序类进行连接管理。
凯文·加利根

31
卡吉,谢谢你的详细解释。您能否澄清一件事-我理解您应该只有一个助手,但您也应该只有一个连接(即一个SqliteDatabase对象)?换句话说,应该多久调用一次getWritableDatabase?同样重要的是,何时调用close()?
Artem

3
我更新了代码。当我切换博客主机时,原始文档丢失了,但是我添加了一些精简示例代码来演示该问题。另外,如何管理单个连接?最初,我有一个更为复杂的解决方案,但此后我对此进行了修改。在这里看看:touchlab.co/uncategorized/single-sqlite-connection
Kevin Galligan

是否需要布置连接以及在哪里进行连接?
tugce 2012年

187

并发数据库访问

同一篇文章在我的博客上(我喜欢格式化)

我写了一篇小文章,描述了如何确保对您的android数据库线程的访问安全。


假设您有自己的SQLiteOpenHelper

public class DatabaseHelper extends SQLiteOpenHelper { ... }

现在,您要在单独的线程中将数据写入数据库。

 // 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中收到以下消息,并且其中一项更改不会被写入。

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

发生这种情况是因为每次创建新的SQLiteOpenHelper对象时,实际上都是在建立新的数据库连接。如果您尝试同时从实际的不同连接中写入数据库,则连接将失败。(从上面的答案)

要使用具有多个线程的数据库,我们需要确保我们正在使用一个数据库连接。

让我们创建单例类数据库管理器,该类将保存并返回单个SQLiteOpenHelper对象。

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

}

将数据写入单独线程中的数据库的更新代码将如下所示。

 // 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();

这将给您带来另一次崩溃。

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

由于我们只使用一个数据库连接,方法getDatabase()返回同一个实例SQLiteDatabase的对象线程1线程2。发生了什么,Thread1可能关闭了数据库,而Thread2仍在使用它。这就是为什么我们有IllegalStateException崩溃的原因。

我们需要确保没有人正在使用数据库,然后才关闭它。一些使用stackoveflow的人建议不要关闭SQLiteDatabase。这将导致出现以下logcat消息。

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

工作样本

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

        }
    }

}

如下使用。

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

每次需要数据库时,都应调用DatabaseManager类的openDatabase()方法。在此方法内部,我们有一个计数器,该计数器指示打开数据库的次数。如果等于1,则意味着我们需要创建新的数据库连接,否则,我们已经创建了数据库连接。

closeDatabase()方法中也会发生同样的情况。每次调用此方法时,counter都会减少,只要计数器为零,我们就会关闭数据库连接。


现在,您应该能够使用数据库,并确保它是线程安全的。


10
我是那些建议您不要关闭数据库的人之一。如果您打开数据库,不关闭它,然后尝试再次打开,则只会收到“发现泄漏”错误。如果只使用一个打开的帮助程序,而从不关闭数据库,则不会收到该错误。如果您发现其他情况,请告诉我(带有代码)。我在某处有更长的帖子,但是找不到。常见问题解答在这里回答的问题,谁在SO点部门中都胜过我们:stackoverflow.com/questions/7211941/…–
Kevin Galligan

4
更多的想法。#1,我将在您的经理内部创建帮助程序。要求将其放在外面的问题。新开发人员可能出于某种疯狂的原因而直接致电帮助程序。另外,如果您需要使用init方法,请在实例已存在的情况下引发异常。多数据库应用程序显然会按原样失败。#2,为什么要使用mDatabase字段?它可以从助手那里获得。#3,作为迈向“永不关闭”的第一步,当应用崩溃且未“关闭”时,数据库会发生什么?提示,没事。很好,因为SQLite非常稳定。这是弄清为什么不需要关闭它的第一步。
凯文·加利根2014年

1
为何在获取实例之前使用公共的initialize方法进行调用有什么原因?为什么不拥有一个称为的私有构造函数if(instance==null)?您别无选择,只能每次调用initialize。您还怎么知道它是否已在其他应用程序等中初始化?
ChiefTwoPencils 2014年

1
initializeInstance()的参数类型为SQLiteOpenHelper,但在您的注释中您提到要使用DatabaseManager.initializeInstance(getApplicationContext());。到底是怎么回事?这怎么可能工作?
faizal

2
@DmytroDanylyk“ DatabaseManager是线程安全的单例,因此可以在任何地方使用”,对于virsir的问题,这是不正确的。对象不是共享的跨进程。您的DatabaseManager在其他进程中将没有状态,例如在同步适配器(android:process =“:sync”)中
2015年

17
  • ThreadAsyncTask用于长时间运行的操作(50毫秒以上)。测试您的应用以查看其位置。大多数操作(可能)不需要线程,因为大多数操作(可能)仅涉及几行。使用线程进行批量操作。
  • SQLiteDatabase在线程之间为磁盘上的每个DB 共享一个实例,并实现计数系统以跟踪打开的连接。

这些方案是否有最佳实践?

在所有类之间共享一个静态字段。我过去经常为这个和其他需要共享的东西保持单身。还应使用计数方案(通常使用AtomicInteger)来确保您永远不会过早关闭数据库或使其保持打开状态。

我的解决方案:

有关最新版本,请参见https://github.com/JakarCo/databasemanager,但在这里我还将尝试使代码保持最新。如果您想了解我的解决方案,请查看代码并阅读我的注释。我的笔记通常很有帮助。

  1. 将代码复制/粘贴到名为的新文件中DatabaseManager。(或从github下载)
  2. 扩展DatabaseManager和实施,onCreate并且onUpgrade像通常一样。您可以创建一个DatabaseManager类的多个子类,以便在磁盘上具有不同的数据库。
  3. 实例化您的子类并调用getDb()以使用SQLiteDatabase该类。
  4. 调用close()您实例化的每个子类

复制/粘贴的代码:

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
当您调用“关闭”然后尝试重用该类时,会发生什么?会崩溃吗?还是会自动重新初始化自身以再次使用数据库?
Android开发人员

1
@androiddeveloper,如果调用close,则必须open在使用类的相同实例之前再次调用,否则您可以创建一个新实例。因为在close代码中,我设置了db=null,所以您将无法使用from的返回值getDb(因为它将为null),所以NullPointerException如果执行类似的操作,您将得到一个myInstance.close(); myInstance.getDb().query(...);
-Reed

为什么不结合getDb()open()成一个单一的方法?
Alex Burdusel 2015年

@Burdu,混合提供对数据库计数器和错误设计的额外控制。不过,这绝对不是最好的方法。我将在几天后进行更新。
Reed 2015年

@Burdu,我刚刚更新了它。您可以从此处获取新代码。我还没有测试过,所以请告诉我是否要提交更改。
Reed

11

数据库具有多线程功能,非常灵活。我的应用程序同时从许多不同的线程访问他们的数据库,效果很好。在某些情况下,我有多个进程同时命中数据库,也可以正常工作。

您的异步任务-可以使用相同的连接,但如果需要,可以从不同任务访问数据库。


另外,您的读者和作家是否处于不同的连接中,还是应该共享一个连接?谢谢。
灰色

@灰色-正确,我应该明确提到。至于连接,我将尽可能使用相同的连接,但是由于锁定是在文件系统级别处理的,因此您可以在代码中多次打开它,但我将尽可能使用单个连接。Android sqlite DB非常灵活且宽容。
布莱德·海因

3
@Gray,只是想为可能正在使用此方法的人发布更新的信息。文档说:此方法现在不起作用。不使用。
Pijusn 2012年

3
我发现此方法严重失败,我们正在切换到ContentProvider以便从多个应用程序访问。我们将必须在我们的方法上进行一些并发,但这应该解决所有同时访问数据的进程的任何问题。
JPM 2012年

2
我知道这很旧,但这是不正确的。它可能来自不同制定出好的访问DB SQLiteDatabase不同对象AsyncTaskS / ThreadS,但有时会导致错误,这就是为什么SQLiteDatabase(线1297)使用Lock小号
里德

7

Dmytro的答案适合我的情况。我认为最好将函数声明为已同步。至少对于我来说,否则将调用null指针异常,例如,一个线程中尚未返回getWritableDatabase,而另一线程中同时调用了openDatabse。

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

mDatabaseHelper.getWritableDatabase(); 这不会创建新的数据库对象
2014年

5

经过几个小时的努力后,我发现每个数据库执行只能使用一个数据库帮助对象。例如,

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

适用于:

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

每次循环迭代时都创建一个新的DBAdapter是我可以通过助手类将字符串输入数据库的唯一方法。


4

我对SQLiteDatabase API的理解是,如果您有一个多线程应用程序,则指向一个数据库的SQLiteDatabase对象不能超过1个。

绝对可以创建对象,但是如果不同的线程/进程(也)开始使用不同的SQLiteDatabase对象(例如我们在JDBC Connection中的使用方式),则插入/更新将失败。

这里唯一的解决方案是坚持使用1个SQLiteDatabase对象,并且每当在1个以上线程中使用startTransaction()时,Android就会管理不同线程之间的锁定,并且一次仅允许1个线程具有独占更新访问权限。

另外,您可以从数据库中执行“读取”操作,并在不同的线程中使用相同的SQLiteDatabase对象(而另一个线程进行写入),并且永远不会发生数据库损坏,即,“读取线程”直到“写线程”将提交数据,尽管两者都使用相同的SQLiteDatabase对象。

这与JDBC中连接对象的方式不同,在JDBC中,如果您在读取和写入线程之间传递(使用相同的方式)连接对象,那么我们很可能也会打印未提交的数据。

在我的企业应用程序中,我尝试使用条件检查,以使UI线程永远不必等待,而BG线程持有SQLiteDatabase对象(排他性)。我尝试预测UI动作并将BG线程推迟运行“ x”秒。也可以维护PriorityQueue来管理分发SQLiteDatabase Connection对象,以便UI线程首先获取它。


您在那个PriorityQueue中放了什么-侦听器(想要获取数据库对象)或SQL查询?
Pijusn 2012年

我没有使用优先级队列方法,而是使用了“调用方”线程。
Swaroop,2012年

@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。并非总是如此,如果在“写线程”之后立即启动“读线程”,则可能不会获得新近更新的数据(在写线程中插入或更新)。读线程可能在启动写线程之前先读取数据。发生这种情况是因为写操作最初启用了保留锁而不是排他锁。
阿米特·维克拉姆·辛格

4

您可以尝试应用Google I / O 2017 宣布的新架构方法。

它还包括名为Room的新ORM库

它包含三个主要组件:@ Entity,@ Dao和@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();
}

我不建议向具有多个N对N关系的数据库分配空间,因为它不能很好地处理这种方法,并且您必须编写大量代码才能找到这些关系的解决方法。
AlexPad

3

遇到一些问题后,我想我已经理解为什么我做错了。

我已经编写了一个数据库包装器类,其中包括一个close()名为helper close的镜像,作为一个open()名为getWriteableDatabase 的镜像,然后迁移到ContentProvider。我认为该模型ContentProvider没有使用SQLiteDatabase.close(),因为代码确实使用了它,这是一个大线索getWriteableDatabase

我使用单例,并且关闭文档中有一点不祥的注释

关闭任何打开的数据库对象

(我的粗体)。

因此,我发生了间歇性崩溃,在该崩溃中,我使用后台线程访问数据库,并且它们与前台同时运行。

因此,我认为close()无论其他引用引用的线程如何,都强制关闭数据库-因此,close()它本身并不仅仅是取消匹配,getWriteableDatabase而是强制关闭任何打开的请求。在大多数情况下,这不是问题,因为代码是单线程的,但是在多线程情况下,总是有打开和关闭同步的机会。

阅读了其他地方的注释,它们解释了SqLiteDatabaseHelper代码实例很重要,那么,您唯一想关闭的地方就是想要备份的情况,并且您要强制关闭所有连接并强制SqLite执行该操作。取消所有可能闲逛的缓存内容-换句话说,停止所有应用程序数据库活动,以防万一Helper失去跟踪,关闭该文件,执行任何文件级活动(备份/还原),然后重新开始。

尽管尝试以受控方式关闭是一个好主意,但现实是Android保留对您的VM进行垃圾处理的权利,因此任何关闭都减少了无法写入缓存的更新的风险,但是如果设备能够保证,则无法保证压力很大,如果您正确释放了游标和对数据库的引用(不应是静态成员),则该助手将仍然关闭数据库。

所以我认为这种方法是:

使用getWriteableDatabase从单例包装器中打开。(我使用派生的应用程序类从静态提供应用程序上下文,以解决对上下文的需求)。

切勿直接致电关闭。

切勿将结果数据库存储在没有明显作用域的任何对象中,并依靠引用计数来触发隐式close()。

如果进行文件级处理,请停止所有数据库活动,然后关闭,以防万一有一个失控线程(假设您编写了正确的事务),那么失控线程将失败,并且关闭的数据库至少具有适当的事务,而不是部分事务的文件级副本。


0

我知道响应晚了,但是在android中执行sqlite查询的最佳方法是通过自定义内容提供程序。这样,UI便与数据库类(扩展SQLiteOpenHelper类的类)分离。查询也在后台线程(Cursor Loader)中执行。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.