您的位置:首页 > 产品设计 > UI/UE

Handler官方范例AsyncQueryHandler源码解析

2016-02-18 20:27 288 查看
在阅读本文之前,你需要了解Handler作为Android中的线程间通信机制究竟是如何运作的,可以参考Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系

源码

/**
* A helper class to help make handling asynchronous {@link ContentResolver}
* queries easier.
*/
public abstract class AsyncQueryHandler extends Handler {
private static final String TAG = "AsyncQuery";
private static final boolean localLOGV = false;

private static final int EVENT_ARG_QUERY = 1;
private static final int EVENT_ARG_INSERT = 2;
private static final int EVENT_ARG_UPDATE = 3;
private static final int EVENT_ARG_DELETE = 4;

/* package */ final WeakReference<ContentResolver> mResolver;

private static Looper sLooper = null;

private Handler mWorkerThreadHandler;

protected static final class WorkerArgs {
public Uri uri;
public Handler handler;
public String[] projection;
public String selection;
public String[] selectionArgs;
public String orderBy;
public Object result;
public Object cookie;
public ContentValues values;
}

protected class WorkerHandler extends Handler {
public WorkerHandler(Looper looper) {
super(looper);
}

@Override
public void handleMessage(Message msg) {
final ContentResolver resolver = mResolver.get();
if (resolver == null) return;

WorkerArgs args = (WorkerArgs) msg.obj;

int token = msg.what;
int event = msg.arg1;

switch (event) {
case EVENT_ARG_QUERY:
Cursor cursor;
try {
cursor = resolver.query(args.uri, args.projection,
args.selection, args.selectionArgs,
args.orderBy);
// Calling getCount() causes the cursor window to be filled,
// which will make the first access on the main thread a lot faster.
if (cursor != null) {
cursor.getCount();
}
} catch (Exception e) {
Log.w(TAG, "Exception thrown during handling EVENT_ARG_QUERY", e);
cursor = null;
}

args.result = cursor;
break;

case EVENT_ARG_INSERT:
args.result = resolver.insert(args.uri, args.values);
break;

case EVENT_ARG_UPDATE:
args.result = resolver.update(args.uri, args.values, args.selection,
args.selectionArgs);
break;

case EVENT_ARG_DELETE:
args.result = resolver.delete(args.uri, args.selection, args.selectionArgs);
break;
}

// passing the original token value back to the caller
// on top of the event values in arg1.
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();
}
}

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);
}

protected Handler createHandler(Looper looper) {
return new WorkerHandler(looper);
}

/**
* This method begins an asynchronous query. When the query is done
* {@link #onQueryComplete} is called.
*
* @param token A token passed into {@link #onQueryComplete} to identify
*  the query.
* @param cookie An object that gets passed into {@link #onQueryComplete}
* @param uri The URI, using the content:// scheme, for the content to
*         retrieve.
* @param projection A list of which columns to return. Passing null will
*         return all columns, which is discouraged to prevent reading data
*         from storage that isn't going to be used.
* @param selection A filter declaring which rows to return, formatted as an
*         SQL WHERE clause (excluding the WHERE itself). Passing null will
*         return all rows for the given URI.
* @param selectionArgs You may include ?s in selection, which will be
*         replaced by the values from selectionArgs, in the order that they
*         appear in the selection. The values will be bound as Strings.
* @param orderBy How to order the rows, formatted as an SQL ORDER BY
*         clause (excluding the ORDER BY itself). Passing null will use the
*         default sort order, which may be unordered.
*/
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

f6f4
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);
}

/**
* Attempts to cancel operation that has not already started. Note that
* there is no guarantee that the operation will be canceled. They still may
* result in a call to on[Query/Insert/Update/Delete]Complete after this
* call has completed.
*
* @param token The token representing the operation to be canceled.
*  If multiple operations have the same token they will all be canceled.
*/
public final void cancelOperation(int token) {
mWorkerThreadHandler.removeMessages(token);
}

/**
* This method begins an asynchronous insert. When the insert operation is
* done {@link #onInsertComplete} is called.
*
* @param token A token passed into {@link #onInsertComplete} to identify
*  the insert operation.
* @param cookie An object that gets passed into {@link #onInsertComplete}
* @param uri the Uri passed to the insert operation.
* @param initialValues the ContentValues parameter passed to the insert operation.
*/
public final void startInsert(int token, Object cookie, Uri uri,
ContentValues initialValues) {
// Use the token as what so cancelOperations works properly
Message msg = mWorkerThreadHandler.obtainMessage(token);
msg.arg1 = EVENT_ARG_INSERT;

WorkerArgs args = new WorkerArgs();
args.handler = this;
args.uri = uri;
args.cookie = cookie;
args.values = initialValues;
msg.obj = args;

mWorkerThreadHandler.sendMessage(msg);
}

/**
* This method begins an asynchronous update. When the update operation is
* done {@link #onUpdateComplete} is called.
*
* @param token A token passed into {@link #onUpdateComplete} to identify
*  the update operation.
* @param cookie An object that gets passed into {@link #onUpdateComplete}
* @param uri the Uri passed to the update operation.
* @param values the ContentValues parameter passed to the update operation.
*/
public final void startUpdate(int token, Object cookie, Uri uri,
ContentValues values, String selection, String[] selectionArgs) {
// Use the token as what so cancelOperations works properly
Message msg = mWorkerThreadHandler.obtainMessage(token);
msg.arg1 = EVENT_ARG_UPDATE;

WorkerArgs args = new WorkerArgs();
args.handler = this;
args.uri = uri;
args.cookie = cookie;
args.values = values;
args.selection = selection;
args.selectionArgs = selectionArgs;
msg.obj = args;

mWorkerThreadHandler.sendMessage(msg);
}

/**
* This method begins an asynchronous delete. When the delete operation is
* done {@link #onDeleteComplete} is called.
*
* @param token A token passed into {@link #onDeleteComplete} to identify
*  the delete operation.
* @param cookie An object that gets passed into {@link #onDeleteComplete}
* @param uri the Uri passed to the delete operation.
* @param selection the where clause.
*/
public final void startDelete(int token, Object cookie, Uri uri,
String selection, String[] selectionArgs) {
// Use the token as what so cancelOperations works properly
Message msg = mWorkerThreadHandler.obtainMessage(token);
msg.arg1 = EVENT_ARG_DELETE;

WorkerArgs args = new WorkerArgs();
args.handler = this;
args.uri = uri;
args.cookie = cookie;
args.selection = selection;
args.selectionArgs = selectionArgs;
msg.obj = args;

mWorkerThreadHandler.sendMessage(msg);
}

/**
* Called when an asynchronous query is completed.
*
* @param token the token to identify the query, passed in from
*            {@link #startQuery}.
* @param cookie the cookie object passed in from {@link #startQuery}.
* @param cursor The cursor holding the results from the query.
*/
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
// Empty
}

/**
* Called when an asynchronous insert is completed.
*
* @param token the token to identify the query, passed in from
*        {@link #startInsert}.
* @param cookie the cookie object that's passed in from
*        {@link #startInsert}.
* @param uri the uri returned from the insert operation.
*/
protected void onInsertComplete(int token, Object cookie, Uri uri) {
// Empty
}

/**
* Called when an asynchronous update is completed.
*
* @param token the token to identify the query, passed in from
*        {@link #startUpdate}.
* @param cookie the cookie object that's passed in from
*        {@link #startUpdate}.
* @param result the result returned from the update operation
*/
protected void onUpdateComplete(int token, Object cookie, int result) {
// Empty
}

/**
* Called when an asynchronous delete is completed.
*
* @param token the token to identify the query, passed in from
*        {@link #startDelete}.
* @param cookie the cookie object that's passed in from
*        {@link #startDelete}.
* @param result the result returned from the delete operation
*/
protected void onDeleteComplete(int token, Object cookie, int result) {
// Empty
}

@Override
public void handleMessage(Message msg) {
WorkerArgs args = (WorkerArgs) msg.obj;

if (localLOGV) {
Log.d(TAG, "AsyncQueryHandler.handleMessage: msg.what=" + msg.what
+ ", msg.arg1=" + msg.arg1);
}

int token = msg.what;
int event = msg.arg1;

// pass token back to caller on each callback.
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;
}
}
}


背景

/**
* A helper class to help make handling asynchronous {@link ContentResolver}
* queries easier.
*/


这是07年的代码,从最开头的类注释来看,写这个helper类的目的是用于简化ContentResolver异步查询。出于历史因素,你可以在某些Android应用——比如Dialer——源码中看到它的身影。不过就实际而言,后来出现的Loader在可代替的情况下比它更好用。

虽然如此,作为官方范例,在Handler的使用上还是有着不少学习之处。

解读

先看构造方法:

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);
}


它做了这样几件事:

1. super()调用父亲构造方法,令此AsyncQueryHandler工作在主线程中(参见Handler源码,其中有mLooper = Looper.myLooper();)。

2. 将ContentResolver作为弱引用持有

3. 在类同步语句块中,如果由static修饰的sLooper为null,创建一个工作线程thread,然后得到sLooper的值。

参考资料:Java 多线程(六) synchronized关键字详解

4. 通过sLooper,创建在工作线程中工作的mWorkerThreadHandler 。因为是同一个sLooper,所以不同AsyncQueryHandler实例的mWorkerThreadHandler 工作在同一个工作线程中

HandlerThread专门设计用于与Handler协同工作,从设计角度而言,这样关系紧密的两个类,要么用A持有B的方式扩展使用,要么就反过来。这里是由Handler持有HandlerThread,与HandlerThread持有Handler的方式相比各有优缺点。

这里封装的具体操作无非是増删改查,我们看一种就好:

/**
* This method begins an asynchronous query. When the query is done
* {@link #onQueryComplete} is called.
*
* @param token A token passed into {@link #onQueryComplete} to identify
*  the query.
* @param cookie An object that gets passed into {@link #onQueryComplete}
* @param uri The URI, using the content:// scheme, for the content to
*         retrieve.
* @param projection A list of which columns to return. Passing null will
*         return all columns, which is discouraged to prevent reading data
*         from storage that isn't going to be used.
* @param selection A filter declaring which rows to return, formatted as an
*         SQL WHERE clause (excluding the WHERE itself). Passing null will
*         return all rows for the given URI.
* @param selectionArgs You may include ?s in selection, which will be
*         replaced by the values from selectionArgs, in the order that they
*         appear in the selection. The values will be bound as Strings.
* @param orderBy How to order the rows, formatted as an SQL ORDER BY
*         clause (excluding the ORDER BY itself). Passing null will use the
*         default sort order, which may be unordered.
*/
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);
}


注释已经十分详细,做的事情也十分简单,用
obtainMessage()
从循环池中拿出一个Message,放上表示动作的标志位
msg.arg1 = EVENT_ARG_QUERY;
,然后构造一个专门用于放数据的静态内部类WorkerArgs ,传入的参数全部放入其中,再作为Object传给Message
msg.obj = args;
,最后是发出Message
mWorkerThreadHandler.sendMessage(msg);


再看下WorkerThreadHandler是如何工作的:

protected class WorkerHandler extends Handler {
public WorkerHandler(Looper looper) {
super(looper);
}

@Override
public void handleMessage(Message msg) {
final ContentResolver resolver = mResolver.get();
if (resolver == null) return;

WorkerArgs args = (WorkerArgs) msg.obj;

int token = msg.what;
int event = msg.arg1;

switch (event) {
case EVENT_ARG_QUERY:
Cursor cursor;
try {
cursor = resolver.query(args.uri, args.projection,
args.selection, args.selectionArgs,
args.orderBy);
// Calling getCount() causes the cursor window to be filled,
// which will make the first access on the main thread a lot faster.
if (cursor != null) {
cursor.getCount();
}
} catch (Exception e) {
Log.w(TAG, "Exception thrown during handling EVENT_ARG_QUERY", e);
cursor = null;
}

args.result = cursor;
break;

case EVENT_ARG_INSERT:
args.result = resolver.insert(args.uri, args.values);
break;

case EVENT_ARG_UPDATE:
args.result = resolver.update(args.uri, args.values, args.selection,
args.selectionArgs);
break;

case EVENT_ARG_DELETE:
args.result = resolver.delete(args.uri, args.selection, args.selectionArgs);
break;
}

// passing the original token value back to the caller
// on top of the event values in arg1.
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();
}
}


根据传来的
msg.arg1;
判定是什么事件,处理后通过args.handler即AsyncQueryHandler本身构造回复用的Message,传入数据的同时
reply.obj = args;
继续传入标志位
reply.arg1 = msg.arg1;
,然后回复即可。

注意这里告诉了我们一个使用Cursor时提速的技巧:

// Calling getCount() causes the cursor window to be filled,
// which will make the first access on the main thread a lot faster.
if (cursor != null) {
cursor.getCount();
}


最后就是主线程中的处理了:

@Override
public void handleMessage(Message msg) {
WorkerArgs args = (WorkerArgs) msg.obj;

if (localLOGV) {
Log.d(TAG, "AsyncQueryHandler.handleMessage: msg.what=" + msg.what
+ ", msg.arg1=" + msg.arg1);
}

int token = msg.what;
int event = msg.arg1;

// pass token back to caller on each callback.
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;
}
}


通过标志位判定,来进行増删改查后的回调处理。

本篇至此结束,下次见。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  源码 android 异步 线程