前提
很多时候我们都是在主线程下直接打开数据库并对数据库操作的,但如果遇到加密型的数据库,例如SQLCipher加密数据库,或者是对字段进行加密的数据库;你就不得不在线程里面操作数据库了!
解决
大家都知道,在多线程操作数据库的时候,如果在各自线程中都利用之前的方法:实例化一个SQLiteOpenHelper类,然后在调用其中的方法。后面的就会遇到android.database.sqlite.SQLiteException: database is locked这样的异常。原因当然就是多线程的时候,导致的关闭异常,例如A线程的一个查询操作,在打开了数据库后,被B线程close了,导致A线程报错。要解决一样的问题,安全一点的方法是利用单例的形式创建SQLiteOpenHelper类或者是实现是具体数据库增删改查的Dao类,并且对方法添加synchronizaed关键字。
关键代码:StuDao 包含了对数据库的操作语句,在方法上都加上了锁
package com.lingdududu.testSQLiteDao;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.util.Log;
import com.lingdududu.testSQLiteDb.StuDBHelper;
public class StuDao {
// 创建StuDBHelper对象
StuDBHelper dbHelper = null;
// 得到一个可读的SQLiteDatabase对象
SQLiteDatabase db = null;
Cursor cursor = null;
static StuDao stuDao = null;
private String TABLE = "stu_table";
private String TAG = "StuDao";
static Object lock = new Object();
public static StuDao getInstance(Context context) {
if (stuDao == null) {
synchronized (lock) {
if (stuDao == null) {
stuDao = new StuDao(context);
}
}
}
return stuDao;
}
public void destoryDB() {
// 关闭数据库
close();
stuDao = null;
}
private StuDao(Context context) {
Log.e(TAG, "--->>> StuDao");
dbHelper = new StuDBHelper(context, "stu_db", null, 1);
if (Build.VERSION.SDK_INT >= 11) {
dbHelper.getWritableDatabase().enableWriteAheadLogging();
}
}
Object queryLock = new Object();
public synchronized void queryTable() {
// 得到一个可写的数据库
db = dbHelper.getWritableDatabase();
// 参数1:表名
// 参数2:要想显示的列
// 参数3:where子句
// 参数4:where子句对应的条件值
// 参数5:分组方式
// 参数6:having条件
// 参数7:排序方式
cursor = db.query(TABLE,
new String[] { "id", "sname", "sage", "ssex" }, null, null,
null, null, null);
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex("sname"));
String age = cursor.getString(cursor.getColumnIndex("sage"));
String sex = cursor.getString(cursor.getColumnIndex("ssex"));
int id = cursor.getInt(cursor.getColumnIndex("id"));
System.out.println("query------->" + "id:" + id + " " + "姓名:"
+ name + " " + "年龄:" + age + " " + "性别:" + sex);
}
// 关闭数据库
this.close();
}
public synchronized void queryTable(int id) {
// 得到一个可写的数据库
db = dbHelper.getWritableDatabase();
// 参数1:表名
// 参数2:要想显示的列
// 参数3:where子句
// 参数4:where子句对应的条件值
// 参数5:分组方式
// 参数6:having条件
// 参数7:排序方式
cursor = db.query(TABLE,
new String[] { "id", "sname", "sage", "ssex" }, "id=?",
new String[] { Integer.toString(id) }, null, null, null);
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex("sname"));
String age = cursor.getString(cursor.getColumnIndex("sage"));
String sex = cursor.getString(cursor.getColumnIndex("ssex"));
int sid = cursor.getInt(cursor.getColumnIndex("id"));
Log.d(TAG, "id:" + sid + " " + "姓名:" + name + " " + "年龄:" + age
+ " " + "性别:" + sex);
}
// 关闭数据库
this.close();
}
public synchronized void updateTable(String id, String sage) {
// 得到一个可写的数据库
db = dbHelper.getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put("sage", sage);
cv.put("id", id);
// where 子句 "?"是占位符号,对应后面的"1",
String whereClause = "id=?";
String[] whereArgs = { String.valueOf(id) };
// 参数1 是要更新的表名
// 参数2 是一个ContentValeus对象
// 参数3 是where子句
db.update(TABLE, cv, whereClause, whereArgs);
// 关闭数据库
this.close();
}
public synchronized void insertTable(String id, String sage) {
// 得到一个可写的数据库
db = dbHelper.getWritableDatabase();
// 生成ContentValues对象 //key:列名,value:想插入的值
ContentValues cv = new ContentValues();
// 往ContentValues对象存放数据,键-值对模式
cv.put("id", id);
cv.put("sname", "xiaoming");
cv.put("sage", sage);
cv.put("ssex", "male");
// 调用insert方法,将数据插入数据库
db.insert("stu_table", null, cv);
// 关闭数据库
// 关闭数据库
this.close();
}
public synchronized void deleteTable(int id) {
// 得到一个可写的数据库
db = dbHelper.getWritableDatabase();
String whereClauses = "id=?";
String[] whereArgs = { String.valueOf(id) };
// 调用delete方法,删除数据
db.delete(TABLE, whereClauses, whereArgs);
// 关闭数据库
this.close();
}
public synchronized void deleteTable() {
// db = dbHelper.getWritableDatabase();
// String sql = "drop table if exists "+TABLE;
// db.execSQL(sql);
// 得到一个可写的数据库
db = dbHelper.getWritableDatabase();
// 调用delete方法,删除数据
db.delete(TABLE, null, null);
// 关闭数据库
this.close();
}
public void close() {
if (db != null) {
db.close();
}
if (cursor != null) {
cursor.close();
}
}
}
在SQLiteActivityMulThread开启线程操作数据库:
package com.lingdududu.testSQLite;
import com.lingdududu.MyThreadPoolExecutor;
import com.lingdududu.testSQLiteDao.StuDao;
import com.lingdududu.testSQLiteDb.StuDBHelper;
import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
/*
* @author lingdududu
*/
public class SQLiteActivityMulThread extends Activity {
/** Called when the activity is first created. */
// 声明各个按钮
private Button insertDatabase;
private Button updateDatabase1;
private Button updateDatabase2;
private Button deleteDatabase;
int age;
int id = 100;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_mul);
// 调用creatView方法
creatView();
setListener();
// MyThreadPoolExecutor.getInstance().execute(new Runnable() {
//
// @Override
// public void run() {
// StuDao.getInstance(SQLiteActivityMulThread.this).queryTable();
// }
// });
// for (age = 0; age < 10; age++) {
// MyThreadPoolExecutor.getInstance().execute(new Runnable() {
// @Override
// public void run() {
// StuDao.getInstance(SQLiteActivityMulThread.this)
// .updateTable(Integer.toString(age));
// }
// });
// }
}
@Override
protected void onDestroy() {
StuDao.getInstance(SQLiteActivityMulThread.this).destoryDB();
super.onDestroy();
}
// 通过findViewById获得Button对象的方法
private void creatView() {
insertDatabase = (Button) findViewById(R.id.insertDatabase);
updateDatabase1 = (Button) findViewById(R.id.updateDatabase1);
updateDatabase2 = (Button) findViewById(R.id.updateDatabase2);
deleteDatabase = (Button) findViewById(R.id.deleteDatabase);
}
// 为按钮注册监听的方法
private void setListener() {
insertDatabase.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
// insertTable
id = 100;
for (; id < 120; id++) {
final int sid = id;
// System.out.println("------sid----"+sid);
// System.out.println("------id----"+id);
MyThreadPoolExecutor.getInstance().execute(new Runnable() {
@Override
public void run() {
StuDao.getInstance(SQLiteActivityMulThread.this)
.insertTable(Integer.toString(sid),
Integer.toString(sid));
StuDao.getInstance(SQLiteActivityMulThread.this)
.queryTable(sid);
}
});
}
}
});
updateDatabase1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
id = 100;
// updateTable
for (; id < 120; id++) {
final int sid = id;
// System.out.println("------sid----"+sid);
// System.out.println("------id----"+id);
MyThreadPoolExecutor.getInstance().execute(new Runnable() {
@Override
public void run() {
StuDao.getInstance(SQLiteActivityMulThread.this)
.updateTable(Integer.toString(sid),
Integer.toString(sid * 2));
StuDao.getInstance(SQLiteActivityMulThread.this)
.queryTable(sid);
}
});
// MyThreadPoolExecutor.getInstance().execute(new Runnable()
// {
//
// @Override
// public void run() {
// StuDao.getInstance(SQLiteActivityMulThread.this).deleteTable(id);
// }
// });
}
}
});
updateDatabase2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
id = 100;
for (; id < 120; id++) {
final int sid = id;
// System.out.println("------sid----"+sid);
// System.out.println("------id----"+id);
MyThreadPoolExecutor.getInstance().execute(new Runnable() {
@Override
public void run() {
StuDao.getInstance(SQLiteActivityMulThread.this)
.updateTable(Integer.toString(sid),
Integer.toString(sid * 3));
StuDao.getInstance(SQLiteActivityMulThread.this)
.queryTable(sid);
}
});
// MyThreadPoolExecutor.getInstance().execute(new Runnable()
// {
//
// @Override
// public void run() {
// StuDao.getInstance(SQLiteActivityMulThread.this).deleteTable(id);
// }
// });
}
}
});
deleteDatabase.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
MyThreadPoolExecutor.getInstance().execute(new Runnable() {
@Override
public void run() {
StuDao.getInstance(SQLiteActivityMulThread.this)
.deleteTable();
}
});
}
});
}
// 创建数据库的方法
class CreateListener implements OnClickListener {
@Override
public void onClick(View v) {
// 创建StuDBHelper对象
StuDBHelper dbHelper = new StuDBHelper(
SQLiteActivityMulThread.this, "stu_db", null, 1);
// 得到一个可读的SQLiteDatabase对象
SQLiteDatabase db = dbHelper.getReadableDatabase();
}
}
// 更新数据库的方法
class UpdateListener implements OnClickListener {
@Override
public void onClick(View v) {
// 数据库版本的更新,由原来的1变为2
StuDBHelper dbHelper = new StuDBHelper(
SQLiteActivityMulThread.this, "stu_db", null, 2);
SQLiteDatabase db = dbHelper.getReadableDatabase();
}
}
// 插入数据的方法
class InsertListener implements OnClickListener {
@Override
public void onClick(View v) {
StuDBHelper dbHelper = new StuDBHelper(
SQLiteActivityMulThread.this, "stu_db", null, 1);
// 得到一个可写的数据库
SQLiteDatabase db = dbHelper.getWritableDatabase();
// 生成ContentValues对象 //key:列名,value:想插入的值
ContentValues cv = new ContentValues();
// 往ContentValues对象存放数据,键-值对模式
cv.put("id", 1);
cv.put("sname", "xiaoming");
cv.put("sage", 21);
cv.put("ssex", "male");
// 调用insert方法,将数据插入数据库
db.insert("stu_table", null, cv);
// 关闭数据库
db.close();
}
}
// 查询数据的方法
class QueryListener implements OnClickListener {
@Override
public void onClick(View v) {
StuDBHelper dbHelper = new StuDBHelper(
SQLiteActivityMulThread.this, "stu_db", null, 1);
// 得到一个可写的数据库
SQLiteDatabase db = dbHelper.getReadableDatabase();
// 参数1:表名
// 参数2:要想显示的列
// 参数3:where子句
// 参数4:where子句对应的条件值
// 参数5:分组方式
// 参数6:having条件
// 参数7:排序方式
Cursor cursor = db.query("stu_table", new String[] { "id", "sname",
"sage", "ssex" }, "id=?", new String[] { "1" }, null, null,
null);
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex("sname"));
String age = cursor.getString(cursor.getColumnIndex("sage"));
String sex = cursor.getString(cursor.getColumnIndex("ssex"));
System.out.println("query------->" + "姓名:" + name + " " + "年龄:"
+ age + " " + "性别:" + sex);
}
// 关闭数据库
db.close();
}
}
// 修改数据的方法
class ModifyListener implements OnClickListener {
@Override
public void onClick(View v) {
StuDBHelper dbHelper = new StuDBHelper(
SQLiteActivityMulThread.this, "stu_db", null, 1);
// 得到一个可写的数据库
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put("sage", "100");
// where 子句 "?"是占位符号,对应后面的"1",
String whereClause = "id=?";
String[] whereArgs = { String.valueOf(1) };
// 参数1 是要更新的表名
// 参数2 是一个ContentValeus对象
// 参数3 是where子句
db.update("stu_table", cv, whereClause, whereArgs);
}
}
// 删除数据的方法
class DeleteListener implements OnClickListener {
@Override
public void onClick(View v) {
StuDBHelper dbHelper = new StuDBHelper(
SQLiteActivityMulThread.this, "stu_db", null, 1);
// 得到一个可写的数据库
SQLiteDatabase db = dbHelper.getReadableDatabase();
String whereClauses = "id=?";
String[] whereArgs = { String.valueOf(2) };
// 调用delete方法,删除数据
db.delete("stu_table", whereClauses, whereArgs);
}
}
}
实现自己的线程池:
package com.lingdududu;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
// ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 200, TimeUnit.MILLISECONDS,
// new LinkedBlockingQueue<Runnable>() );
public class MyThreadPoolExecutor implements Executor {
private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 256;
private static final int KEEP_ALIVE = 1;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "MyThreadPoolExecutor #"
+ mCount.getAndIncrement());
}
};
private final ThreadPoolExecutor mThreadPoolExecutor;
private static MyThreadPoolExecutor mExecutor;
public static MyThreadPoolExecutor getInstance() {
if (mExecutor == null) {
return new MyThreadPoolExecutor(CORE_POOL_SIZE);
}
return mExecutor;
}
public MyThreadPoolExecutor getInstance(int poolSize) {
if (mExecutor == null) {
return new MyThreadPoolExecutor(poolSize);
}
return mExecutor;
}
private MyThreadPoolExecutor() {
this(CORE_POOL_SIZE);
}
private MyThreadPoolExecutor(int poolSize) {
mThreadPoolExecutor = new ThreadPoolExecutor(poolSize,
MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(), sThreadFactory);
}
public int getPoolSize() {
return mThreadPoolExecutor.getCorePoolSize();
}
public void setPoolSize(int poolSize) {
if (poolSize > 0) {
mThreadPoolExecutor.setCorePoolSize(poolSize);
}
}
public boolean isBusy() {
return mThreadPoolExecutor.getActiveCount() >= mThreadPoolExecutor
.getCorePoolSize();
}
@Override
public void execute(final Runnable r) {
mThreadPoolExecutor.execute(r);
}
}
不知道大家有没发现在上述的获取数据库的方法中,我都用统一用了 getWritableDatabase(),原因是如果在升级了数据库后,如果删除了某一张表,又恰好是利用 getReadableDatabase()的方式打开数据库的话,将会报错因为没权限创建修改数据库。详情看我的博客 http://blog.csdn.net/u011484134/article/details/49795991
对于后面这个问题,我暂时也没想到好的办法解决,欢迎大家讨论!