假设你已经有一个自己的SQLiteOpenHelper。
public class DatabaseHelper extends SQLiteOpenHelper { ... }
现在你想要在不同的线程中向数据库写数据
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 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,实际上你每次都创建了一个数据库的连接。如果你在同一时间用不同的数据库连接来对同一的数据库进行写操作的话,那么其中一个会失败。
在Android平台上,如果你想在多线程环境下安全的使用数据库的话,那么你得确保所有的线程使用的都是同一个数据库连接。
确保只有一个数据库连接存在,我们可以使用单例模式,代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public
class
DatabaseManager {
private
static
DatabaseManager instance;
private
static
SQLiteOpenHelper mDatabaseHelper;
public
static
synchronized
void
initialize(Context context, 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
synchronized
SQLiteDatabase getDatabase() {
return
new
mDatabaseHelper.getWritableDatabase();
}
}
|
现在我们来看看执行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// In your application class
DatabaseManager.initializeInstance(getApplicationContext());
// 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
因为我们只使用一个数据库连接,Thread1和Thread2的都是由getDatabase()方法返回的相同连接。发生的什么事呢,在Thread2还在使用数据库连接时,Thread1可能已经把它给关闭了,那就是为什么你会得到崩溃异常。
我们需要确保在没有任何一个人在使用数据库时,才去关闭它。在StackOverflow上推荐的做法是永远不要关闭数据库。Android会尊重你这种做法,但会给你如下的提示。所以我一点也不推荐这种做法。
Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
下面是一个样例程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
public
class
DatabaseManager {
private
AtomicInteger mOpenCounter =
new
AtomicInteger();
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() {
if
(mOpenCounter.incrementAndGet() ==
1
) {
// Opening new database
mDatabase = mDatabaseHelper.getWritableDatabase();
}
return
mDatabase;
}
public
synchronized
void
closeDatabase() {
if
(mOpenCounter.decrementAndGet() ==
0
) {
// Closing database
mDatabase.close();
}
}}
|
使用方式:
1
2
3
4
|
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase();
// correct way
|
每当你需要使用数据库时,你需要使用DatabaseManager的openDatabase()方法来取得数据库。我们会使用一个引用计数来判断是否要创建数据库对象。如果引用计数为1,则需要创建一个数据库,如果不为1,说明我们已经创建过了。
在closeDatabase()方法中我们同样通过判断引用计数的值,如果引用计数降为0,则说明我们需要close数据库。