android系统封装了一些很好用的帮助类辅助应用程序开发,比如AsyncQueryHandler 可以用来异步操作数据库、
AsyncQueryHandler 在多数情况下使用都不会有问题,但是在一些特殊场景下使用却是有局限的。
这与AsyncQueryHandler 本身的代码设计师有关系的,下面我们从代码来看,AsyncQueryHandler 到底是怎么一回事?
先看AsyncQueryHandler的实现:
public AsyncQueryHandler(ContentResolver cr) {
super();
mResolver = new WeakReference<ContentResolver>(cr);
synchronized (AsyncQueryHandler.class) {
if (sLooper == null) {
HandlerThread thread = new HandlerThread("AsyncQueryWorker");
thread.start();
sLooper = thread.getLooper();
}
}
mWorkerThreadHandler = createHandler(sLooper);
}
从AsyncQueryHandler构造函数看到,在应用程序中初始化AsyncQueryHandler的子类对象的时候会新启动一个新线程,这个线程的生命周期与应用程序自身生命周期是一致的,这个线程的作用是负责异步操作数据库。这里面有个细节是sLooper 类型
private static Looper sLooper = null;
从代码看到sLooper 是静态变量,这意味着在同一个应用程序中,无论构造了多少继承自AsyncQueryHandler的子类对象,
HandlerThread thread = new HandlerThread("AsyncQueryWorker")
只会被执行一次,通过AsyncQueryHandler的子类对象异步操作数据库的时候最终都是通过第一次初始化AsyncQueryHandler子类对象时候创建的线程来完成的。这说明在一个应用程序中,通过AsyncQueryHandler帮助类操作数据库的动作只能够顺序执行。假如一个删除数据库的动作没完成,这个时候再请求一个查询数据库的动作只能排队等待上一个删除动作完成后才能开始执行。为什么会这样呢,因为AsyncQueryHandler的异步操作是通过消息传递的,在主线程中,通过子线程的handler把请求操作数据库的动作封装成消息然后发送到子线程的消息队列然后开始真正操作数据库,而在同一个线程的消息队列中,消息必须顺序执行,一个消息执行完成以后才可以接着执行下一个消息。比如查询:
public void startQuery(int token, Object cookie, Uri uri,
String[] projection, String selection, String[] selectionArgs,
String orderBy) {
// Use the token as what so cancelOperations works properly
Message msg = mWorkerThreadHandler.obtainMessage(token);
msg.arg1 = EVENT_ARG_QUERY;
WorkerArgs args = new WorkerArgs();
args.handler = this;
args.uri = uri;
args.projection = projection;
args.selection = selection;
args.selectionArgs = selectionArgs;
args.orderBy = orderBy;
args.cookie = cookie;
msg.obj = args;
//将请求操作数据库的动作以消息的形式发到子线程的消息队列
mWorkerThreadHandler.sendMessage(msg);
}
mWorkerThreadHandler是属于子线程的handler,会把查询数据库的消息发送到子线程的消息队列,删除、更新、增加数据库的操作也是一样。如下代码所示:
//子线程线程的handleMessage
public void handleMessage(Message msg) {
...
switch (event) {
//执行查询数据库的操作
case EVENT_ARG_QUERY:
...
break;
...
Message reply = args.handler.obtainMessage(token);
reply.obj = args;
reply.arg1 = msg.arg1;
if (localLOGV) {
Log.d(TAG, "WorkerHandler.handleMsg: msg.arg1=" + msg.arg1
+ ", reply.what=" + reply.what);
}
//将处理结果以消息的形式发到主线程
reply.sendToTarget();
}
当子线程完成数据库的操作后,会向主线程发送消息通知数据库的操作已经完成,主线程在HandleMessage中会调用回调函数,应用程序在继承了AsyncQueryHandler的子类中重写了这些回调函数,可以在回调函数中完成更新UI等操作,如下代码所示:
//UI线程的handleMessage
public void handleMessage(Message msg) {
...
switch (event) {
case EVENT_ARG_QUERY:
onQueryComplete(token, args.cookie, (Cursor) args.result);
break;
case EVENT_ARG_INSERT:
onInsertComplete(token, args.cookie, (Uri) args.result);
break;
case EVENT_ARG_UPDATE:
onUpdateComplete(token, args.cookie, (Integer) args.result);
break;
case EVENT_ARG_DELETE:
onDeleteComplete(token, args.cookie, (Integer) args.result);
break;
}
}
从以上代码分析看到AsyncQueryHandler主要是通过新启动一个线程加上消息机制完成异步操作数据库的整个过程。
三.案例分析:
描述:删除数据库数据,在删除未结束前,进入该应用程序其他界面,结果发现界面空白,等到删除会话结束后,之前的空白界面才有内容显示。
案例分析:删除操作和进入该应用另外一个界面的查询操作都是通过写一 个继承了AsyncQueryHandler的帮助类来操作数据库,按照我们上面的分析,这两个帮助类实际上通过发送消息到同一个线程的消息队列来操作数据库,因此当上一个消息未执行完毕,下一个消息只能等待。打印log,发现实际上在第一个操作未完毕前,第二个消息根本就没能够发送到消息队列,这一点和之前的推测消息已经发送到了消息队列但是没有被取出执行是有些出入,为什么消息会被阻塞发送到消息队列,这一点还需要深入研究。
解决方案:使用AsyncTask来完成常用回复信息的查询。AsyncTask和AsyncQueryHandler的一个很大区别是AsyncTask在每一次构造的时候都会新启动一个线程,直到达到AsyncTask中线程池的阈值。这样通过AsyncTask查询操作与之前的删除操作就是在两个线程中进行,互不干扰,不会存在消息阻塞的问题了。