Android联系人搜索之LoaderCallbacks

    在android中,我们知道对于耗时操作要放到子线程中去处理,否则可能会阻塞主线程导致ANR,比较常见的耗时操作有数据库操作,网络通信等,对于此类操作我们需要进行异步处理。常见的异步处理方式有下面几种:Thread+Handler(Looper),AsyncTask,AsyncQueryHandler,HandlerThread以及LoaderCallbacks。

    对于Thread+Handler,如果hander不是在子线程中定义,那么要注意的是handler.post()方法实际上还是在主线程中执行。AsyncQueryHandler,HandlerThread则都是另起一个子线程去进行数据处理(对于两者的使用区别,如果我们只需要进行一次异步查询,如加载通话记录,使用AsyncQueryHandler更为方便。如果需要一个工作者线程,则建议使用Handler。ThreadHandlerThread通过其mLooper对象实现了对消息队列处理逻辑的简易封装)。AsyncTask则适合在进行后台操作时需要更新UI时的场景。最后就是今天要讲的LoaderCallbacks,每个Activity和fragment都对应一个LoaderManager,我们可以通过LoaderManager去初始化一个Loader。
    下面是摘自网上的关于Loader的中文翻译
从Android 3.0开始引进了loader(加载器)技术, 在activity或者fragment中,loaders可以把异步地加载数据变得更简单。Loaders具有以下特性:
  • 他们对于每一个ActivityFragment都是有效的。
  • 他们可以提供异步加载数据的能力。
  • 他们监视数据源,并当内容改变时传递当前最新的结果。
  • 当他们因为配置的改变而重新连接的时候,他们会自动地重连到上一个loader的游标。因此,他们不需要重新查询数据。  

     什么意思呢,在我看来,Loader的这些特性很适合处理下面这种情况,例如要对数据进行多次查询的场景,比如对通讯录里的联系人进行搜索。通常我们查询联系人可以根据中文拼音(全拼或简拼)或者联系人号码进行搜索。比如搜索姓名为周星星,号码为15812345678的联系人,我们通常需要在进行多次输入查询才能找到该联系人(如通过拼音zhouxingxing,zxx或者号码15812...)。

    上面简单介绍了相关机制,下面我们从代码入手,对MTK M平台下联系人搜索功能进行简单分析(不同公司代码会有所出入)。

    首先我们找到联系人搜索功能的入口,搜索栏对应的布局在联系人主界面PoepleActivity顶部,对应的布局通过Actionbar设置CustomView来自定义。通过搜索ActionBar我们很容易找到它初始化的地方
mActionBar = getAmigoActionBar();
mActionBarAdapter = new ActionBarAdapter(this, this, mActionBar, showAsText);
我们发现mActionBar在声明ActionBarAdapter用到了,查看发现它实现了OnQueryTextListener这个接口,
public class ActionBarAdapter implements OnQueryTextListener, OnCloseListener {......}
OnQueryTextListener这个接口我们应该很熟悉了,在其中定义了onQueryTextChange这个方法,它是用户输入文字改变时的回调方法。
public interface OnQueryTextListener {
    boolean onQueryTextSubmit(String var1);
    boolean onQueryTextChange(String var1);
}
我们看看在onQueryTextChange时做了什么
@Override
public boolean onQueryTextChange(String queryString) {   
    if (queryString.equals(mQueryString)) {
        return false;
    }
    mQueryString = queryString;
    if (!mSearchMode) {
        if (!TextUtils.isEmpty(queryString)) {
            setSearchMode(true);
        }
    } else if (mListener != null) {
        mListener.onAction(Action.CHANGE_SEARCH_QUERY);
    }    
    return true;
}
这几句代码很简单,我们只看mListener != null这种情况,里面调用了mListener.onAction()方法,传递Action.CHANGE_SEARCH_QUERY这个参数。mListener是个接口,我们看一下它的定义
public interface Listener {
    public enum Action {
        CHANGE_SEARCH_QUERY, START_SEARCH_MODE, STOP_SEARCH_MODE
    }  
    void onAction(Action action);......}
在ActionBarAdapter初始化时传入listener,因此我们回到PeopleActivity查看onAction方法
@Override
public void onAction(Action action) {
    switch (action) {
	......
        case CHANGE_SEARCH_QUERY:
            setQueryTextToFragment(mActionBarAdapter.getQueryString());
            break;
        default:
            throw new IllegalStateException("Unkonwn ActionBarAdapter action: " + action);
    }
}
private void setQueryTextToFragment(String query) {
    if (mActionBarAdapter.isSearchMode()) {
        if (query == null || TextUtils.isEmpty(query)) {
            onSearchMaskModeChange(true);
        } else {
            onSearchMaskModeChange(false);
        }
    }
    mAllFragment.setQueryString(query, true);
    mAllFragment.setVisibleScrollbarEnabled(!mAllFragment.isSearchMode());
}
onSearchMaskModeChange方法用来设置搜索栏是否可见,重点看setQueryString方法
public void setQueryString(String queryString, boolean delaySelection) {
    // Normalize the empty query.
    if (TextUtils.isEmpty(queryString))
        queryString = null; 
    if (!TextUtils.equals(mQueryString, queryString)) {
        mQueryString = queryString;
        setSearchMode(!TextUtils.isEmpty(mQueryString));       
        if (mAdapter != null) {
            mAdapter.setQueryString(queryString);
            reloadData();
        }
    }
}
通过mAdapter.setQueryString设置当前用户查询内容,然后调用reloadData方法,看来reloadData()就是我们想要的了。
protected void reloadData() {
    removePendingDirectorySearchRequests();
    mAdapter.onDataReload();
    mLoadPriorityDirectoriesOnly = true;
    mForceLoad = true;
    startLoading();
}
略过一些中间过程......最终发现调用的是getLoaderManager().restartLoader()方法
protected void loadDirectoryPartition(int partitionIndex, DirectoryPartition partition) {
    Bundle args = new Bundle();
    args.putLong(DIRECTORY_ID_ARG_KEY, partition.getDirectoryId());
    getLoaderManager().restartLoader(partitionIndex, args, this);
}
restartLoader方法会重新调用onCreateLoader方法(从字面不太好理解restart为什么会重新调用onCreate,不过反过来想onCreateLoader是唯一可以将用户查询的信息传给loader的地方,要重新查询数据也只能通过调用该方法来实现重新加载,查看源码发现其内部确实调用了onCreateLoader方法),我们来看创建loader的onCreateLoader方法
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    if (id == DIRECTORY_LOADER_ID) {
        DirectoryListLoader loader = new DirectoryListLoader(mContext);
        mAdapter.configureDirectoryLoader(loader);
        return loader;
    } else {
        CursorLoader loader = createCursorLoader();
        long directoryId = args != null && args.containsKey(DIRECTORY_ID_ARG_KEY) ? args.getLong(DIRECTORY_ID_ARG_KEY)
                : Directory.DEFAULT;
        mAdapter.configureLoader(loader, directoryId);
        return loader;
    }
}
DIRECTORY_LOADER_ID值等于-1为无效ID,通常我们不会用到,看else下半部分通过createCursorLoader()新建了一个CursorLoader对象,然后通过调用configureLoader方法去重新设置CursorLoader。
@Override
public void configureLoader(CursorLoader loader, long directoryId) {
    if (loader instanceof ProfileAndContactsLoader) {
        ((ProfileAndContactsLoader) loader).setLoadProfile(shouldIncludeProfile());
    }   
    ContactListFilter filter = getFilter();
    if (isSearchMode()) {
        String query = getQueryString();
        if (query == null) {
            query = "";
        }
        query = query.trim();
        if (TextUtils.isEmpty(query)) {
            // Regardless of the directory, we don't want anything returned,
            // so let's just send a "nothing" query to the local directory.
            loader.setUri(Contacts.CONTENT_URI);
            loader.setProjection(getProjection(false));
            loader.setSelection("0");
        } else {
            Builder builder = Contacts.CONTENT_FILTER_URI.buildUpon();
            builder.appendPath(query); // Builder will encode the query
            builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId));
            if (directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE) {
                builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, String.valueOf(getDirectoryResultLimit()));
            }
            loader.setUri(builder.build());
            loader.setProjection(getProjection(true));
        }
    } else {
	......
    }
    ......
}
通过Contacts.CONTENT_FILTER_URI这个URI加上用户输入查询内容去进行匹配查询。对于
Contacts.CONTENT_FILTER_URI的查询操作在ContactsProvider中处理,此处我们略过。
matcher.addURI(ContactsContract.AUTHORITY, "contacts/filter", CONTACTS_FILTER);......
查询结束后我们在onLoadFinished方法中处理得到的数据,在这里要结合CursorAdapter进行使用,到此查询结束。
总结:对于类似搜索查询,我们可以在自己的Activity或Fragment中通过getLoaderManager方法得到一个LoaderManager对象。通过LoaderManager的initLoader()方法创建一个loader。如果标志loader的id不存在,此时会回调onCreateLoader方法创建一个新的loader。如果标志loader的id已存在,则返回已存在的最近创建的loader。由于LoaderManager会自动管理loader的生命周期,所以我们几乎不需要考虑与loader之间的交互,只需要在特定事件发生时回调LoaderCallbacks中的方法即可,这种处理方式简单且易管理。比如在onQueryTextChange时,通过调用restartLoader()方法即可重新加载数据。而在查询结束之后,在onLoadFinished方法中获取到查询后的cursor,通过CursorAdapter.swapCursor()来更新数据。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值