問題場景描述:
在頁面中用到了ViewPager控件,ViewPager中的內容分別是兩個ListView,兩個ListView的數據都來自本地數據庫(先從網絡下載數據,然後更新本地數據庫),在實際的使用過程中發現會出現SQLiteDatabaseLockedException: database is locked的問題。
經網上搜索資料,發現是讀寫數據庫時存在的同步問題,所以採用單例+同步鎖的方法,並且在每次數據庫操作後都關閉數據庫,經測試後發現沒有在出現上述問題。
以下是兩個主類
DBHelper.java(這個類用來管理數據庫)
public class DBHelper extends SQLiteOpenHelper {
private final String TAG = this.getClass().getSimpleName();
public final static String DATABASE_NAME = "test.db";
public final static String TABLE = "table";
public final static int DATABASE_VERSION = 2;
public DBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
private static DBHelper mInstance;
public synchronized static DBHelper getInstance(Context context) {
if (mInstance == null) {
mInstance = new DBHelper(context);
}
return mInstance;
};
@Override
public void onCreate(SQLiteDatabase db) {
try {
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE
+ "(id INTEGER PRIMARY KEY ,data BLOB)");
} catch (SQLiteException e) {
e.printStackTrace();
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 刪除原來的數據表
db.execSQL("DROP TABLE IF EXISTS " + TABLE);
// 重新創建
onCreate(db);
}
public static byte[] objectToBytes(Object obj) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream sOut = new ObjectOutputStream(out);
sOut.writeObject(obj);
sOut.flush();
byte[] bytes = out.toByteArray();
return bytes;
}
public static Object bytesToObject(byte[] bytes) throws Exception {
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
ObjectInputStream sIn = new ObjectInputStream(in);
return sIn.readObject();
}
}
DBStudentManager類(這裏可以定義自己的管理類)
public class DBStudentManager {
private DBHelper helper;
private SQLiteDatabase db;
public DBStudentManager(Context context) {
helper = DBHelper.getInstance(context);
db = helper.getWritableDatabase();
}
// 插入
private void insert(Student student) {
synchronized (helper) {
// 看數據庫是否關閉
if (!db.isOpen()) {
db = helper.getWritableDatabase();
}
// 開始事務
db.beginTransaction();
try {
db.execSQL(
"INSERT INTO " + DBHelper.TABLE + " VALUES(?,?)",
new Object[] { student.mID,
DBHelper.objectToBytes(student) });
db.setTransactionSuccessful(); // 設置事務成功完成
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
db.endTransaction();
db.close();
}
}
}
// 更新
private void update(Student student) {
synchronized (helper) {
if (!db.isOpen()) {
db = helper.getWritableDatabase();
}
db.beginTransaction();
try {
db.execSQL("UPDATE " + DBHelper.TABLE
+ "SET data = ? WHERE id = ?",
new Object[] { DBHelper.objectToBytes(student),
student.mID });
db.setTransactionSuccessful(); // 設置事務成功完成
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
db.endTransaction();
db.close();
}
}
}
// 同步
public void synchronous(List<Student> students) {
if (students == null) {
return;
}
for (Student student : students) {
if (query(student.mID) == null) {
insert(student);
} else {
update(student);
}
}
}
// 刪除指定數據
public void delete(String id) {
synchronized (helper) {
if (!db.isOpen()) {
db = helper.getWritableDatabase();
}
db.beginTransaction();
try {
db.execSQL("DELETE FROM " + DBHelper.TABLE + " WHERE id = ? ",
new String[] { id });
db.setTransactionSuccessful();
} catch (Exception e) {
e.printStackTrace();
} finally {
db.endTransaction();
db.close();
}
}
}
// 刪除所有數據
public void delete() {
synchronized (helper) {
if (!db.isOpen()) {
db = helper.getWritableDatabase();
}
db.beginTransaction();
try {
db.execSQL("DELETE * FROM " + DBHelper.TABLE);
db.setTransactionSuccessful();
} catch (Exception e) {
e.printStackTrace();
} finally {
db.endTransaction();
db.close();
}
}
}
// 查找所有的Students
public List<Student> query() {
List<Student> students = new ArrayList<Student>();
synchronized (helper) {
if (!db.isOpen()) {
db = helper.getWritableDatabase();
}
Cursor c = queryTheCursor();
Student student = null;
try {
while (c.moveToNext()) {
byte[] bytes = c.getBlob((c.getColumnIndex("data")));
student = (Student) DBHelper.bytesToObject(bytes);
students.add(student);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
c.close();
}
}
return students;
}
// 查找指定ID的Student
public Student query(String id) {
Student student = null;
synchronized (helper) {
if (!db.isOpen()) {
helper.getWritableDatabase();
}
Cursor c = queryTheCursor(id);
try {
while (c.moveToNext()) {
byte[] bytes = c.getBlob((c.getColumnIndex("data")));
student = (Student) DBHelper.bytesToObject(bytes);
break;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
c.close();
}
}
return student;
}
// 獲取遊標
public Cursor queryTheCursor(String id) {
Cursor c = db.rawQuery("SELECT FROM " + DBHelper.TABLE
+ " WHERE id = ?", new String[] { id });
return c;
}
// 獲取遊標
public Cursor queryTheCursor() {
Cursor c = db.rawQuery("SELECT * FROM " + DBHelper.TABLE);
return c;
}
class Student {
String mID;
String mName;
int mAge;
}
}
使用繼承ContentProvider的方式,也可以解決該異常。
但是這種方式有2個弊端:
1.使用繼承ContentProvider方式,會將項目的數據庫暴露給第三方。一般系統級應用,如存儲聯繫人, 存儲短信的數據庫纔有必要暴露給第三方。
2.使用方式不如直接繼承SQLiteOpenHelper對象簡單。