在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具有以下特性:
- 他们对于每一个
Activity
和Fragment
都是有效的。 - 他们可以提供异步加载数据的能力。
- 他们监视数据源,并当内容改变时传递当前最新的结果。
- 当他们因为配置的改变而重新连接的时候,他们会自动地重连到上一个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()来更新数据。