android - 安卓SQLite的最佳实践是什么?

  显示原文与译文双语对照的内容

在 Android 应用 中对SQLite数据库执行查询时的最佳实践?

是否安全运行插入,删除和从doInBackground的中选择查询? 还是应该使用用户界面线程? 我认为数据库查询可以是"重",不应该使用UI线程,因为它可以锁定应用程序- 导致一个错误。

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

这些场景有最佳实践?

时间:

插入,更新,删除和读取通常来自多个线程,但brad答案的答案不正确。 你必须谨慎地创建连接并使用它们。 在某些情况下,更新调用会失败,即使数据库没有被破坏。

基本答案的

SqliteOpenHelper对象持有一个数据库连接。 它似乎给你提供了一个读和写连接,但实际上它并没有。 调用 read-only,你就会得到写数据库连接而不管。

因此,一个 helper 实例,一个数据库连接。 即使你从多个线程使用它,一次一个连接。 SQLite数据库 对象使用java锁来保持访问序列化。 因此,如果 100个线程有一个db实例,对实际on-disk数据库的调用被序列化。

因此,一个 helper,一个db连接,它被序列化为java代码。 一个线程,1000个线程,如果你使用它们之间共享的一个 helper 实例,所有的数据库访问代码都是串行的。 生命是好的( ish ) 。

如果尝试从实际的不同连接写入数据库,则会失败。 它不会等到第一个完成之后再写。 它不会写你的更改。 更糟糕的是,如果不调用 SQLite数据库 上的插入/更新的正确版本,则不会得到异常。 你将在你的试用版中得到一个消息,这就是它。

那么,多个线程使用一个 helper 。 句号。如果你只知道一个线程将被写入,你可以使用多个连接,并且你的阅读速度会更快,但是买家注意。 我还没有测试过这么多。

这里是一个博客文章,详细的细节和示例应用。

Gray和我实际上正在打包一个基于Ormlite数据库的ORM工具,它可以与Android数据库实现一起工作,并遵循我在博客文章中描述的安全创建/调用结构。 应该很快就会出来的。 举个例子,如果你只有唯一的一次生命


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

也签出叉子 2point0前面提到的锁的例子:

并发数据库访问

blog(I like formatting more) 上的相同文章

我写了一个小文章,描述如何访问你的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();

你将在试用版中收到以下消息,其中一个更改将不会被写入。


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() 返回同一实例 SQLite数据库 对象 Thread1和 Thread2 。 发生了什么, Thread1可能会关闭数据库,而 Thread2仍在使用它。 这就是为什么我们有 crash的原因。

我们需要确保没有人在使用数据库,然后才关闭它。 stackoveflow上的一些人建议不要关闭你的SQLite数据库 。 它不仅听起来很傻,而且遵循下面的邮件消息。


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

每次你需要数据库你应该叫 openDatabase() 方法 DatabaseManager类。 在这里方法中,我们有一个计数器,它指示数据库打开的次数。 如果它等于一个,就意味着我们需要创建新数据库,如果不是,数据库已经创建。

closeDatabase() 方法也同样发生。 每次调用这里方法时,计数器都会减少,当它到达零时,我们正在关闭数据库。


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

在挣扎了几个小时之后,我发现每个数据库执行只能使用一个 db helper 对象。 比如,


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是我通过 helper 类将字符串放入数据库的唯一方法。

dmytro的答案对于我的情况来说很好。 我认为最好将函数声明为 synchronized 。 至少对于我的情况,它将调用空指针异常,否则,比如 getWritableDatabase还不能在一个线程中返回,openDatabse在另一个线程中调用。


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

...