Android 异步查询框架AsyncQueryHandler分析

Android 异步查询框架AsyncQueryHandler分析

在一般的应用中可以使用ContentProvider去操作数据库。
这在数据量很小的时候是没有问题的,但是如果数据量大了,可能导致UI线程发生ANR异常(超过5秒)。
当然你也可以写个Handler去做这些操作,只是你每次使用ContentProvider时都要再写个Handler,必然降低了效率。
因此当数据量较大时,最好还是使用android已经封装好的异步查询框架AsyncQueryHandler,优化我们的代码.AsyncQueryHandler类封装了两个线程的交互过程。它们是调用者线程与工作线程。这两个线程里都维护着一个handler,通过提供onXXXComplete的回调接口,实现事件的完成处理。在处理读取联系人、通话记录相关,也解决的部分山寨手机兼容问题。

基本用法

自定义MyQueryHandler继承AsyncQueryHandler
实现里面的两个方法,构造方法和onQueryComplete方法

private class MyAsyncQueryHandler extends AsyncQueryHandler {

    public MyAsyncQueryHandler(ContentResolver cr) {
        super(cr);
    }

    @Override
    public void startQuery(int tag, Object cookie, Uri uri, String[] projection, String selection,
            String[] selectionArgs, String orderBy) {
        super.startQuery(tag, cookie, uri, projection, selection, selectionArgs, orderBy);
        Log.e("TAG", "startQuery");
    }

    protected void onQueryComplete(final int tag, final Object cookie, final Cursor cursor) {
        Log.e("TAG", "onQueryComplete");
        super.onQueryComplete(tag, cookie, cursor);
    }

}
}

一般开始调用的方法: myAsyncQueryHandler.startQuery(token, cookie, uri, projection, selection, selectionArgs, orderBy);

以上方法参数:

  • token
    令牌,主要用来标识查询,保证唯一

  • cookie
    你想传给onXXXComplete方法使用的一个对象。(没有的话传递null即可)

  • Uri uri(进行查询的通用资源标志符):

  • projection 查询的列

  • selection 限制条件

  • selectionArgs 查询参数

  • orderBy 排序条件

参考代码:

Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI; // 联系人Uri;
    // 查询的字段
    String[] projection = { ContactsContract.CommonDataKinds.Phone._ID,
            ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.DATA1,
            "sort_key", ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
            ContactsContract.CommonDataKinds.Phone.PHOTO_ID, ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY };
    // 按照sort_key升序查詢
    asyncQueryHandler.startQuery(tag, cookie, uri, projection, null, null, "sort_key COLLATE LOCALIZED asc");

AsyncQueryHandler类的源码

从整体上把握这个类:每个AsyncQueryHandler对象都会开启一个后台线程,在线程中执行与ContentProvider组件的数据交互,进行增删改查。调用时,可以通过AsyncQueryHandler.startXXX系列方法将请求打包发送到后台线程,当相关处理完成后,会将结果异步回传给主线程并调用AsyncQueryHandler.onXXXComplete方法通知调用者。调用者每次请求时,需要传入一个整型值token作为这次请求的标识,当该请求完成后进行回调时,会将token传回,帮助调用者确定这是哪一次请求。参考博文图

源码如下:

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

值得注意的是AsyncQueryHandler是个抽象类,不能直接被实例化;


争取每日把之前印象笔记中的碎片点,总结出来分享给各位大佬,相互督促相互学习进步,欢迎各位老铁留言……

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,295评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,928评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,682评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,209评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,237评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,965评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,586评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,487评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,016评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,136评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,271评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,948评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,619评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,139评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,252评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,598评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,267评论 2 358

推荐阅读更多精彩内容