Android Loader机制全面详解及源码浅析
原文出处:csdn@工匠若水,http://blog.csdn.net/yanbober/article/details/48861457
一、概述
在Android中任何耗时的操作都不能放在UI主线程中,所以耗时的操作都需要使用异步实现。同样的,在ContentProvider中也可能存在耗时操作,这时也该使用异步操作,而3.0之后最推荐的异步操作就是Loader。它可以方便我们在Activity和Fragment中异步加载数据,而不是用线程或AsyncTask,他的优点如下:
- 提供异步加载数据机制;
- 对数据源变化进行监听,实时更新数据;
- 在Activity配置发生变化(如横竖屏切换)时不用重复加载数据;
- 适用于任何Activity和Fragment;
注意:由于在我们现在的多个项目中都大量的使用了Loader来处理数据加载(而且由于粗心跳过几个坑,譬如Loader ID重复导致数据逻辑异常、多线程中restartLoader导致Loader抛出异常(最后保证都在UI线程中执行即可)等),所以接下来我们进行下使用及源码浅析。
PPPS:前方高能,文章巨长,请做好心理准备(您可以选择通过左上角目录点击索引到感兴趣的章节直接查看,或者,或者,或者直接高能往下看)。
二、基础使用实例
该基础实例讲解完全来自于官方文档,详细可以点击我查看英文原文。
既然接下来准备要说说他的使用强大之处了,那不妨我们先来一张图直观的感性认识下不用Loader(左)与用Loader(右)对我们开发者及代码复杂度和框架的影响吧,如下:
Loader API概述说明
如下是我们开发中常用的一些Loader相关接口:
| LoaderManager | 一个与Activity、Fragment关联的抽象类,用于管理一个或多个Loader实例。每个Activity或Fragment只能有一个LoaderManager,而一个LoaderManager可以有多个Loader。 |
| LoaderManager.LoaderCallbacks | 用于和LoaderManager交互的回调接口。譬如,可以使用onCreateLoader()创建一个新的Loader。 |
| AsyncTaskLoader | 抽象的Loader,提供一个AsyncTask继承实现。 |
| CursorLoader | AsyncTaskLoader的子类,用于向ContentResover请求返回一个Cursor。该类以标准游标查询实现了Loader协议,使用后台线程进行查询,使用这个Loader是从ContentProvider加载异步数据最好的方式。 |
在应用中使用Loader
在我们开发的一个App里,使用Loader时常规的步骤包含如下一些操作需求:
- 一个Activity或Fragment;
- 一个LoaderManager实例;
- 一个CursorLoader,从ContentProvider加载数据;
- 一个LoaderManager.LoaderCallbacks实现,创建新Loader及管理已存在Loader;
- 一个组织Loader数据的Adapter,如SimpleCursorAdapter;
下面我们看下具体流程。
启动一个Loader(initLoader)
一个Activity或Fragment中LoaderManager管理一个或多个Loader实例,每个Activity或Fragment只有一个LoaderManager,我们可以在Activity的onCreate()或Fragment的onActivityCreated()里初始化一个Loader。例如:
// Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this);可以看见上面的initLoader()方法有三个参数:
- 第一个参数代表当前Loader的ID;
- 第二个参数代表提供给Loader构造函数的参数,可选;
- 第三个参数代表LoaderManager.LoaderCallbacks的回调实现;
上面initLoader()方法的调用确保了一个Loader被初始化和激活的状态,该方法的调运有如下两种结果:
- 如果代表该Loader的ID已经存在,则后面创建的Loader将直接复用已经存在的;
- 如果代表该Loader的ID不存在,initLoader()会触发LoaderManager.LoaderCallbacks回调的onCreateLoader()方法创建一个Loader;
可以看见通过initLoader()方法可以将LoaderManager.LoaderCallbacks实例与Loader进行关联,且当Loader的状态变化时就被回调。所以说,如果调用者正处于其开始状态并且被请求的Loader已经存在,且已产生了数据,那么系统会立即调用onLoadFinished()(在initLoader()调用期间),所以你必须考虑到这种情况的发生。
当然了,intiLoader()会返回一个创建的Loader,但是你不用获取它的引用,因为LoadeManager会自动管理该Loader的生命周期,你只用在它回调提供的生命周期方法中做自己数据逻辑的处理即可。
重启一个Loader(restartLoader)
通过上面initLoader()方法介绍我们可以知道initLoader调运后要么得到一个ID已存在的Loader,要么创建一个新的Loader;但是有时我们想丢弃旧数据然后重新开始创建一个新Loader,这可怎么办呢?别担心,要丢弃旧数据调用restartLoader()即可。例如,SearchView.OnQueryTextListener的实现重启了Loader,当用户查询发生变化时Loader需要重启,如下:
public boolean onQueryTextChanged(String newText) {// Called when the action bar search text has changed. Update// the search filter, and restart the loader to do a new query// with this filter.mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;getLoaderManager().restartLoader(0, null, this);return true; }上面方法的参数啥的和再上面的init方法类似,就不再罗嗦了。
使用LoaderManager Callbacks
LoaderManager.LoaderCallbacks是LoaderManager的回调交互接口。LoaderManager.LoaderCallbacks包含如下三个方法:
- onCreateLoader()
实例化并返回一个新创建给定ID的Loader对象; - onLoadFinished()
当创建好的Loader完成了数据的load之后回调此方法; - onLoaderReset()
当创建好的Loader被reset时调用此方法,这样保证它的数据无效;
onCreateLoader说明
当你尝试使用一个Loader(譬如通过initLoader()方法),它会检查给定Loader的ID是否存在,如果不存在就触发LoaderManager.LoaderCallbacks里的onCreateLoader()方法创建一个新Loader。创建新Loader实例典型的做法就是通过CursorLoader类创建,不过你也可以自定义一个继承自Loader的子类来实现自己的Loader。
下面的例子中我们通过onCreateLoader()回调创建一个CursorLoader实例,使用CursorLoader的构造方法创建实例时需要一些参数去查询一个ContentProvider。具体参数如下:
| uri | 准备获取内容的URI |
| projection | 要返回的列key list,null表示返回所有列,但是返回所有列很多时候会降低性能 |
| selection | 要返回的行过滤,也就是SQL中的WHERE语句,null代表返回uri指定的所有行 |
| selectionArgs | 用来替换上面selection中包含的"?" |
| sortOrder | 结果的行排序,也就是SQL中的ORDER BY,传递null则无序 |
.
// If non-null, this is the current filter the user has provided. String mCurFilter; ... public Loader<Cursor> onCreateLoader(int id, Bundle args) {// This is called when a new Loader needs to be created. This// sample only has one Loader, so we don't care about the ID.// First, pick the base URI to use depending on whether we are// currently filtering.Uri baseUri;if (mCurFilter != null) {baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,Uri.encode(mCurFilter));} else {baseUri = Contacts.CONTENT_URI;}// Now create and return a CursorLoader that will take care of// creating a Cursor for the data being displayed.String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("+ Contacts.HAS_PHONE_NUMBER + "=1) AND ("+ Contacts.DISPLAY_NAME + " != '' ))";return new CursorLoader(getActivity(), baseUri,CONTACTS_SUMMARY_PROJECTION, select, null,Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); }onLoadFinished说明
当创建好的Loader完成数据加载时回调此方法,我们要确保该方法在Loader释放现有维持的数据之前被调用。在这里我们应该移除所有对旧数据的使用(因为旧数据不久就会被释放),但是不用释放旧数据,因为Loader会帮我们完成旧数据的释放。
Loader一旦知道App不再使用旧数据就会释放掉。例如,如果数据来自CursorLoader里的一个Cursor,我们不应该自己在代码中调用close()方法;如果一个Cursor正在被放置到一个CursorAdapter时我们应当使用swapCursor()进行新数据交换,这样正在被放置的旧的Cursor就不会被关掉,也就不会导致Adapter的加载异常。
// This is the Adapter being used to display the list's data. SimpleCursorAdapter mAdapter; ...public void onLoadFinished(Loader<Cursor> loader, Cursor data) {// Swap the new cursor in. (The framework will take care of closing the// old cursor once we return.)mAdapter.swapCursor(data); }onLoaderReset说明
当实例化好的Loader被重启时该方法被回调,这里会让Loader的数据置于无效状态。这个回调方法其实就是为了告诉我们啥时候数据要被释放掉,所以我们应该在这个时候移除对它的引用。如下移除实例:
// This is the Adapter being used to display the list's data. SimpleCursorAdapter mAdapter; ...public void onLoaderReset(Loader<Cursor> loader) {// This is called when the last Cursor provided to onLoadFinished()// above is about to be closed. We need to make sure we are no// longer using it.mAdapter.swapCursor(null); }Loader使用实例实战
下面这个实例是一个Fragment,模拟的是用ListView显示通讯录的实时匹配查询结果,使用CursorLoader管理通讯录Provider查询。如下源码,比较简单,注释也很丰富了,所以不过多解释:
public static class CursorLoaderListFragment extends ListFragmentimplements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {// This is the Adapter being used to display the list's data.SimpleCursorAdapter mAdapter;// If non-null, this is the current filter the user has provided.String mCurFilter;@Override public void onActivityCreated(Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);// Give some text to display if there is no data. In a real// application this would come from a resource.setEmptyText("No phone numbers");// We have a menu item to show in action bar.setHasOptionsMenu(true);// Create an empty adapter we will use to display the loaded data.mAdapter = new SimpleCursorAdapter(getActivity(),android.R.layout.simple_list_item_2, null,new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },new int[] { android.R.id.text1, android.R.id.text2 }, 0);setListAdapter(mAdapter);// Prepare the loader. Either re-connect with an existing one,// or start a new one.getLoaderManager().initLoader(0, null, this);}@Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {// Place an action bar item for searching.MenuItem item = menu.add("Search");item.setIcon(android.R.drawable.ic_menu_search);item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);SearchView sv = new SearchView(getActivity());sv.setOnQueryTextListener(this);item.setActionView(sv);}public boolean onQueryTextChange(String newText) {// Called when the action bar search text has changed. Update// the search filter, and restart the loader to do a new query// with this filter.mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;getLoaderManager().restartLoader(0, null, this);return true;}@Override public boolean onQueryTextSubmit(String query) {// Don't care about this.return true;}@Override public void onListItemClick(ListView l, View v, int position, long id) {// Insert desired behavior here.Log.i("FragmentComplexList", "Item clicked: " + id);}// These are the Contacts rows that we will retrieve.static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {Contacts._ID,Contacts.DISPLAY_NAME,Contacts.CONTACT_STATUS,Contacts.CONTACT_PRESENCE,Contacts.PHOTO_ID,Contacts.LOOKUP_KEY,};public Loader<Cursor> onCreateLoader(int id, Bundle args) {// This is called when a new Loader needs to be created. This// sample only has one Loader, so we don't care about the ID.// First, pick the base URI to use depending on whether we are// currently filtering.Uri baseUri;if (mCurFilter != null) {baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,Uri.encode(mCurFilter));} else {baseUri = Contacts.CONTENT_URI;}// Now create and return a CursorLoader that will take care of// creating a Cursor for the data being displayed.String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("+ Contacts.HAS_PHONE_NUMBER + "=1) AND ("+ Contacts.DISPLAY_NAME + " != '' ))";return new CursorLoader(getActivity(), baseUri,CONTACTS_SUMMARY_PROJECTION, select, null,Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");}public void onLoadFinished(Loader<Cursor> loader, Cursor data) {// Swap the new cursor in. (The framework will take care of closing the// old cursor once we return.)mAdapter.swapCursor(data);}public void onLoaderReset(Loader<Cursor> loader) {// This is called when the last Cursor provided to onLoadFinished()// above is about to be closed. We need to make sure we are no// longer using it.mAdapter.swapCursor(null);} }到此整个Loader基础使用就介绍完了,关于Loader的高级功能,譬如自定义Loader等内容这里先不贴代码说明,因为在这里一下子说完都会觉得蒙圈,而且接受难度也比较大,所以我们在上面这些基础铺垫之后乘热先来源码浅析,有了源码浅析把持住全局结构后再去用Loader的高级用法就会觉得得心应手许多。
三、源码浅析
和上面的基本使用介绍一样,关于Loader的源码浅析过程会涉及到Activity、Fragment、LoaderManager、Loader、AsyncLoader、CursorLoader等类。所以我们分析的过程还是和以前一样,依据使用顺序进行分析。
我们在分析之前先来看一个Loader框架概要图,如下:
通过上面图和前面的基础实例你会发现Loader的框架和各个类的职责都很明确。Activity和Fragment管理LoaderManager,LoaderManager管理Loader,Loader得到数据后触发在LoaderManager中实现的Loader的callback接口,LoaderManager在接收到Loader的callback回传调运时触发我们Activity或Fragment中实现的LoaderManager回调callback接口,就这样就实现了Loader的所有功能,而我们平时写代码一般只用关心LoaderManager的callback实现即可;对于自定义Loader可能还需要关心AsyncTaskLoader子类的实现。
Activity及Fragment中LoadManager的管理浅析
首先我们都知道,在使用Loader的第一步就是在Activity或者Fragment中获取LoaderManager实例,所以我们先来看下Activity和Fragment是如何管理这些LoaderManager的。
先来看看Fragment中的LoaderManager,如下:
final class FragmentState implements Parcelable {......LoaderManagerImpl mLoaderManager;boolean mLoadersStarted;boolean mCheckedForLoaderManager;......//fragment中获取LoaderManager办法public LoaderManager getLoaderManager() {//可以看见,一个Fragment只有一个LoaderManagerif (mLoaderManager != null) {return mLoaderManager;}if (mActivity == null) {throw new IllegalStateException("Fragment " + this + " not attached to Activity");}mCheckedForLoaderManager = true;//从Activity中获取LoaderManager,传入的mWho为当前Fragment的识别key,然后create传入true表示创建!!!!!!mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, true);return mLoaderManager;}public void onStart() {mCalled = true;if (!mLoadersStarted) {mLoadersStarted = true;if (!mCheckedForLoaderManager) {mCheckedForLoaderManager = true;//如果还没调运过getLoaderManager,那就尝试获取LoaderManager,传入的create为false!!!!!mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false);}//生命周期依附上LoaderManagerif (mLoaderManager != null) {mLoaderManager.doStart();}}}public void onDestroy() {mCalled = true;if (!mCheckedForLoaderManager) {mCheckedForLoaderManager = true;//如果还没调运过getLoaderManager,那就尝试获取LoaderManager,传入的create为false!!!!!mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false);}//生命周期依附上LoaderManagerif (mLoaderManager != null) {mLoaderManager.doDestroy();}}void performStart() {......mCalled = false;onStart();......//生命周期依附上LoaderManagerif (mLoaderManager != null) {mLoaderManager.doReportStart();}}void performStop() {......mCalled = false;onStop();......if (mLoadersStarted) {mLoadersStarted = false;if (!mCheckedForLoaderManager) {mCheckedForLoaderManager = true;//如果还没调运过getLoaderManager,那就尝试获取LoaderManager,传入的create为false!!!!!mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false);}if (mLoaderManager != null) {//生命周期依附上LoaderManagerif (mActivity == null || !mActivity.mChangingConfigurations) {mLoaderManager.doStop();} else {mLoaderManager.doRetain();}}}}void performDestroyView() {......mCalled = false;onDestroyView();......//生命周期依附上LoaderManagerif (mLoaderManager != null) {mLoaderManager.doReportNextStart();}} }从上面可以看出,Fragment在其生命周期内会控制LoaderManager(LoaderManager其实控制了Loader)的doStart、doDestroy等方法,也就是说我们在Fragment中只管通过getLoaderManager方法来获取LoaderManager实例,然后使用就行,别的Fragment都会帮我们处理OK的。
接下来看看Activity中的LoaderManager,如下:
public class Activity extends ContextThemeWrapperimplements ... {//mAllLoaderManagers保存了Activity与Fragment的所有LoaderManagerArrayMap<String, LoaderManagerImpl> mAllLoaderManagers;LoaderManagerImpl mLoaderManager;......//Activity中获取LoaderManager实例的方法public LoaderManager getLoaderManager() {//可以看见,一个Activity只有一个LoaderManagerif (mLoaderManager != null) {return mLoaderManager;}mCheckedForLoaderManager = true;//咦?这不就是上面Fragment的getLoaderManager中调运的那个activity中的getLoaderManager吗,只是和这里的参数不一样而已mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true);return mLoaderManager;}//Activity与Fragment获取LoaderManager实例的真正方法!!LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {//可见一个Activity维护一个mAllLoaderManagers的MAPif (mAllLoaderManagers == null) {mAllLoaderManagers = new ArrayMap<String, LoaderManagerImpl>();}//尝试从缓存mAllLoaderManagers的MAP中获取已经实例化的LoaderManager实例LoaderManagerImpl lm = mAllLoaderManagers.get(who);if (lm == null) {if (create) {//如果没有找到并且需要实例化create(切记这个create参数是很重要的),就调运LoaderManagerImpl构造方法实例化一个LoaderManager对象,然后存入缓存mAllLoaderManagers的MAP中lm = new LoaderManagerImpl(who, this, started);mAllLoaderManagers.put(who, lm);}} else {lm.updateActivity(this);}return lm;}void invalidateFragment(String who) {if (mAllLoaderManagers != null) {LoaderManagerImpl lm = mAllLoaderManagers.get(who);if (lm != null && !lm.mRetaining) {//生命周期依附上LoaderManagerlm.doDestroy();mAllLoaderManagers.remove(who);}}}final void performStop() {if (mLoadersStarted) {mLoadersStarted = false;//生命周期依附上LoaderManagerif (mLoaderManager != null) {//mChangingConfigurations表示如果当前发生了配置变化则为true,否则为false!!!!!!!重点,Loader特性之一if (!mChangingConfigurations) {//当前Activity的stop不是由配置变化引起则直接调用LoaderManager的doStop()方法!!!!!!mLoaderManager.doStop();} else {//当前Activity配置变化,所以需要保存当前的loaderManager,在Activity恢复时恢复这个LoaderManager!!!!!!mLoaderManager.doRetain();}}}......}final void performDestroy() {......onDestroy();//生命周期依附上LoaderManagerif (mLoaderManager != null) {mLoaderManager.doDestroy();}......}protected void onCreate(@Nullable Bundle savedInstanceState) {if (mLastNonConfigurationInstances != null) {//从mLastNonConfigurationInstances中恢复mAllLoaderManagers(mLastNonConfigurationInstances是从onAttach中恢复的),Activity配置变化时会走这里!!!!mAllLoaderManagers = mLastNonConfigurationInstances.loaders;}......mCalled = true;}final void performStart() {......if (mAllLoaderManagers != null) {final int N = mAllLoaderManagers.size();LoaderManagerImpl loaders[] = new LoaderManagerImpl[N];for (int i=N-1; i>=0; i--) {loaders[i] = mAllLoaderManagers.valueAt(i);}//生命周期依附上LoaderManagerfor (int i=0; i<N; i++) {LoaderManagerImpl lm = loaders[i];//调用LoaderManager.finishRetain()以及doReportStart()方法来恢复LoaderManager的状态!!!!!lm.finishRetain();lm.doReportStart();}}mActivityTransitionState.enterReady(this);}//该方法会被ActivityThread类调用,且调运时机早于performDestroy()方法!!!!!!NonConfigurationInstances retainNonConfigurationInstances() {......NonConfigurationInstances nci = new NonConfigurationInstances();......//配置变化时保存mAllLoaderManagers!!!!!!nci.loaders = mAllLoaderManagers;return nci;} }通过上面的分析可以发现,Activity其实真正的管理了Activity及Fragment的LoaderManager(Fragment也会管理一部分自己LoaderManager的周期),而LoaderManager又管理了Loader,可以发现他们各自的管理范围都是十分的清晰明了的。
LoadManager及其实现类LoadManagerImpl的浅析
上面分析Activity及Fragment中获取LoaderManager实例时已经知道,我们获取的LoaderManager实例其实就是LoaderManagerImpl对象,而LoaderManagerImpl又是LoaderManager类的子类,所以接下来我们来分析这两个父子类。
先看下抽象父类LoaderManager,如下
public abstract class LoaderManager {//LoaderManager的回调接口定义public interface LoaderCallbacks<D> {public Loader<D> onCreateLoader(int id, Bundle args);public void onLoadFinished(Loader<D> loader, D data);public void onLoaderReset(Loader<D> loader);}//下面这些方法没必要再细说了,上面介绍过的public abstract <D> Loader<D> initLoader(int id, Bundle args,LoaderManager.LoaderCallbacks<D> callback);public abstract <D> Loader<D> restartLoader(int id, Bundle args,LoaderManager.LoaderCallbacks<D> callback);//会触发回调的onLoaderReset方法public abstract void destroyLoader(int id);public abstract <D> Loader<D> getLoader(int id);public abstract void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args);public static void enableDebugLogging(boolean enabled) {LoaderManagerImpl.DEBUG = enabled;} }可以看见LoaderManager抽象类只是定义了一些规范接口而已,那么接着我们看下抽象类LoaderManager的实现类LoaderManagerImpl,如下:
class LoaderManagerImpl extends LoaderManager {static final String TAG = "LoaderManager";static boolean DEBUG = false;//保存当前存活的Loaderfinal SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>(0);//保存已经运行完的Loaderfinal SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>(0);final String mWho;Activity mActivity;boolean mStarted;boolean mRetaining;boolean mRetainingStarted;//是否正在创建Loader,多线程中同时调运创建会导致异常boolean mCreatingLoader;//Loader的封装类final class LoaderInfo implements Loader.OnLoadCompleteListener<Object>,Loader.OnLoadCanceledListener<Object> {final int mId;final Bundle mArgs;LoaderManager.LoaderCallbacks<Object> mCallbacks;Loader<Object> mLoader;boolean mHaveData;boolean mDeliveredData;Object mData;boolean mStarted;//mRetaining标记Activity配置变化时保持当前Loader,不用销毁;和上面分析Activity的LoaderManager的retainNonConfigurationInstances方法关联!!!!!!boolean mRetaining;boolean mRetainingStarted;boolean mReportNextStart;boolean mDestroyed;boolean mListenerRegistered;LoaderInfo mPendingLoader;//LoaderInfo构造方法public LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callbacks) {mId = id;mArgs = args;mCallbacks = callbacks;}//启动一个Loadervoid start() {//配置改变恢复则不用启动,用原来的if (mRetaining && mRetainingStarted) {mStarted = true;return;}//如果已经启动,则不用再restart了if (mStarted) {return;}mStarted = true;//如果当前封装中mLoader为空并且通过构造方法的mCallbacks不为空则回调onCreateLoader方法创建Loaderif (mLoader == null && mCallbacks != null) {mLoader = mCallbacks.onCreateLoader(mId, mArgs);}if (mLoader != null) {if (mLoader.getClass().isMemberClass()&& !Modifier.isStatic(mLoader.getClass().getModifiers())) {//如果当前创建的Loader对象是一个非静态内部类则抛异常!!!!!!throw new IllegalArgumentException("Object returned from onCreateLoader must not be a non-static inner member class: "+ mLoader);}if (!mListenerRegistered) {//注册Loader的监听方法mLoader.registerListener(mId, this);mLoader.registerOnLoadCanceledListener(this);mListenerRegistered = true;}//调运Loader的startLoading方法mLoader.startLoading();}}//Activity的配置改变时进行标志位的设置,以便可以保存,配合上面Activity的分析!!!!!!void retain() {mRetaining = true;......}//Activity配置变化后重启后如果有数据则通知回调方法,配合上面Activity的分析!!!!!!void finishRetain() {......if (mStarted && mHaveData && !mReportNextStart) {callOnLoadFinished(mLoader, mData);}}//配合上面Activity的分析!!!!!!void reportStart() {......}//停止Loadervoid stop() {mStarted = false;if (!mRetaining) {//如果不是Activity配置变化,即不用保存则注销掉这些回调if (mLoader != null && mListenerRegistered) {......}}}//取消掉Loadervoid cancel() {......}//销毁掉Loadervoid destroy() {......if (mCallbacks != null && mLoader != null && mHaveData && needReset) {......try {//在destroy时如果有数据存在则调用callback的onLoaderReset方法!!!!!!mCallbacks.onLoaderReset(mLoader);} finally {......}}......if (mLoader != null) {//注销监听方法if (mListenerRegistered) {......}//close Cursor等重置操作mLoader.reset();}if (mPendingLoader != null) {mPendingLoader.destroy();}}//Loader被取消时回调该方法@Overridepublic void onLoadCanceled(Loader<Object> loader) {......LoaderInfo pending = mPendingLoader;//执行最新的Loaderif (pending != null) {mPendingLoader = null;mLoaders.put(mId, null);destroy();installLoader(pending);}}//加载完成时回调@Overridepublic void onLoadComplete(Loader<Object> loader, Object data) {......//执行最新的Loaderif (pending != null) {mPendingLoader = null;mLoaders.put(mId, null);destroy();installLoader(pending);return;}if (mData != data || !mHaveData) {mData = data;mHaveData = true;if (mStarted) {callOnLoadFinished(loader, data);}}......}//调用onLoadFinishedvoid callOnLoadFinished(Loader<Object> loader, Object data) {if (mCallbacks != null) {......try {//回调onLoadFinished方法mCallbacks.onLoadFinished(loader, data);} finally {......}mDeliveredData = true;}}}//!!!!!!真正LoaderManagerImpl的构造方法LoaderManagerImpl(String who, Activity activity, boolean started) {mWho = who;mActivity = activity;mStarted = started;}//更新当前Activity引用void updateActivity(Activity activity) {mActivity = activity;}//私有的创建Loader方法private LoaderInfo createLoader(int id, Bundle args,LoaderManager.LoaderCallbacks<Object> callback) {LoaderInfo info = new LoaderInfo(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);//回调callback的onCreateLoader方法得到Loader对象Loader<Object> loader = callback.onCreateLoader(id, args);//把得到的Loader对象包装成LoaderInfo对象info.mLoader = (Loader<Object>)loader;return info;}//包装了创建Loader与install方法,并将mCreatingLoader标记置位private LoaderInfo createAndInstallLoader(int id, Bundle args,LoaderManager.LoaderCallbacks<Object> callback) {try {mCreatingLoader = true;//调运上面的私有创建方法创建LoaderInfo对象LoaderInfo info = createLoader(id, args, callback);//把创建的LoaderInfo对象传入installLoader方法installLoader(info);return info;} finally {mCreatingLoader = false;}}void installLoader(LoaderInfo info) {//将创建的LoaderInfo对象存入mLoaders的Map中mLoaders.put(info.mId, info);if (mStarted) {//如果Activity已经started,则启动LoaderInfo的start方法info.start();}}//public的方法,创建一个Loader,前面介绍过的@SuppressWarnings("unchecked")public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {//如果多线程中正在有创建的则抛出异常(写代码要注意这种情况,尤其是跑Monkey容易抛出,解决办法就是保证都在统一线程中执行!!!!!!)if (mCreatingLoader) {throw new IllegalStateException("Called while creating a loader");}//从现有的Map中尝试获取指定ID的LoaderInfo对象LoaderInfo info = mLoaders.get(id);if (info == null) {//发现不存在就调运上面的createAndInstallLoader创建一个info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);} else {//否则还用当前的Loader,只是重新赋值了callBack而已info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;}if (info.mHaveData && mStarted) {//已经有数据,直接调运LoaderInfo的callOnLoadFinishedinfo.callOnLoadFinished(info.mLoader, info.mData);}//返回Loader对象return (Loader<D>)info.mLoader;}//重新创造Loader,前面介绍过的@SuppressWarnings("unchecked")public <D> Loader<D> restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {if (mCreatingLoader) {//如果多线程中正在有创建的则抛出异常(写代码要注意这种情况,尤其是跑Monkey容易抛出,解决办法就是保证都在统一线程中执行!!!!!!)throw new IllegalStateException("Called while creating a loader");}LoaderInfo info = mLoaders.get(id);if (info != null) {LoaderInfo inactive = mInactiveLoaders.get(id);if (inactive != null) {if (info.mHaveData) {//发现是已经运行完的Loader且已经存在的Loader有数据则destroy掉运行完的Loaderinactive.mDeliveredData = false;inactive.destroy();info.mLoader.abandon();mInactiveLoaders.put(id, info);} else {if (!info.mStarted) {//有相同id的Loader还没start则destory掉mLoaders.put(id, null);info.destroy();} else {//有一个相同id的Loader正在加载数据,但是还没加载完,调用它的cancel()方法通知取消加载info.cancel();if (info.mPendingLoader != null) {info.mPendingLoader.destroy();info.mPendingLoader = null;}//创建一个指定id的Loader同时赋给mPendingLoader,因为这个时候已经有一个Loader正在加载数据,而且我们已经调用了其cancel()方法来通知取消加载info.mPendingLoader = createLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);//返回创建的Loaderreturn (Loader<D>)info.mPendingLoader.mLoader;}}} else {//终止已存在的Loaderinfo.mLoader.abandon();mInactiveLoaders.put(id, info);}}//重新创建Loader返回info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);return (Loader<D>)info.mLoader;}//销毁指定id的Loaderpublic void destroyLoader(int id) {if (mCreatingLoader) {throw new IllegalStateException("Called while creating a loader");}//不解释,单纯的destoryint idx = mLoaders.indexOfKey(id);if (idx >= 0) {LoaderInfo info = mLoaders.valueAt(idx);mLoaders.removeAt(idx);info.destroy();}idx = mInactiveLoaders.indexOfKey(id);if (idx >= 0) {LoaderInfo info = mInactiveLoaders.valueAt(idx);mInactiveLoaders.removeAt(idx);info.destroy();}......}//获取指定id的Loader对象@SuppressWarnings("unchecked")public <D> Loader<D> getLoader(int id) {if (mCreatingLoader) {throw new IllegalStateException("Called while creating a loader");}//优先获取LoaderInfo中的mPendingLoaderLoaderInfo loaderInfo = mLoaders.get(id);if (loaderInfo != null) {if (loaderInfo.mPendingLoader != null) {return (Loader<D>)loaderInfo.mPendingLoader.mLoader;}return (Loader<D>)loaderInfo.mLoader;}return null;}...... }我勒个去!好长,好累!通过上面粗略的分析你会发现和我们上面基础实例介绍LoaderManager的方法时描述的一样,每个方法都有自己的特点,发挥着各自的作用,LoaderManager的实质是将Loader对象转换为LoaderInfo来进行管理,也就是管理了所有的Loader对象。
Loader及其实现类的浅析
上面分析了Activity及Fragment管理了LoaderManager的相关方法,LoaderManager管理了Loader的相关方法,那么接下来我们就来看看这个被管理的终极目标Loader是咋回事,还有他的子类咋回事。
先来看看我画的一张关系图,如下:
我去,这图现在看可能有些吓人,我们还是先来慢慢分析一下再说吧。
Loader基类源码浅析
我们先来看看这个Loader基类吧,该类核心方法及内部类结构图如下:
代码分析如下:
public class Loader<D> {int mId;OnLoadCompleteListener<D> mListener;OnLoadCanceledListener<D> mOnLoadCanceledListener;Context mContext;boolean mStarted = false;boolean mAbandoned = false;boolean mReset = true;boolean mContentChanged = false;boolean mProcessingChange = false;//数据源变化监听器(观察者模式),实现了ContentObserver类public final class ForceLoadContentObserver extends ContentObserver {public ForceLoadContentObserver() {super(new Handler());}@Overridepublic boolean deliverSelfNotifications() {return true;}@Overridepublic void onChange(boolean selfChange) {//实质是调运Loader的forceLoad方法onContentChanged();}}//Loader加载完成接口,当加载完成时Loader通知loaderManager,loaderManager再回调我们initLoader方法的callbackpublic interface OnLoadCompleteListener<D> {public void onLoadComplete(Loader<D> loader, D data);}//LoaderManager中监听cancel,同上类似public interface OnLoadCanceledListener<D> {public void onLoadCanceled(Loader<D> loader);}//构造方法public Loader(Context context) {//mContext持有Application的Context,防止泄露内存等mContext = context.getApplicationContext();}//加载完成时回调传递加载数据结果,实质是对OnLoadCompleteListener接口方法的封装public void deliverResult(D data) {if (mListener != null) {mListener.onLoadComplete(this, data);}}//类似同上,对OnLoadCanceledListener的方法的封装public void deliverCancellation() {if (mOnLoadCanceledListener != null) {mOnLoadCanceledListener.onLoadCanceled(this);}}public Context getContext() {return mContext;}public int getId() {return mId;}public void registerListener(int id, OnLoadCompleteListener<D> listener) {mListener = listener;mId = id;}public void unregisterListener(OnLoadCompleteListener<D> listener) {mListener = null;}public void registerOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {mOnLoadCanceledListener = listener;}public void unregisterOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {mOnLoadCanceledListener = null;}public boolean isStarted() {return mStarted;}public boolean isAbandoned() {return mAbandoned;}public boolean isReset() {return mReset;}//开始加载数据时LoaderManager会调用该方法public final void startLoading() {//设置标记mStarted = true;mReset = false;mAbandoned = false;onStartLoading();}//真正开始加载数据的地方******空方法,子类实现!!!!!!protected void onStartLoading() {}//取消Loader的方法public boolean cancelLoad() {return onCancelLoad();}//真正取消的地方******,子类实现!!!!!!return false表示取消失败(因为已完成或未开始)protected boolean onCancelLoad() {return false;}//强制重新Loader,放弃旧数据public void forceLoad() {onForceLoad();}//真正重新Loader的地方******空方法,子类实现!!!!!!protected void onForceLoad() {}//同上public void stopLoading() {mStarted = false;onStopLoading();}protected void onStopLoading() {}//同上public void abandon() {mAbandoned = true;onAbandon();}protected void onAbandon() {}//同上public void reset() {onReset();mReset = true;mStarted = false;mAbandoned = false;mContentChanged = false;mProcessingChange = false;}protected void onReset() {}//Loader数据变化的一些标记处理public boolean takeContentChanged() {boolean res = mContentChanged;mContentChanged = false;mProcessingChange |= res;return res;}public void commitContentChanged() {mProcessingChange = false;}public void rollbackContentChanged() {if (mProcessingChange) {mContentChanged = true;}}//上面ForceLoadContentObserver内部类的onChange方法调运public void onContentChanged() {if (mStarted) {forceLoad();} else {mContentChanged = true;}}//一些方便调试的方法public String dataToString(D data)public String toString()public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) }通过上面粗略的分析可以发现,Loader基类无非也就是一个方法接口的定义类,组织预留了一些方法供LoaderManager去调运处理,同时需要子类实现其提供的一些onXXX方法,以便LoaderManager调运Loader的方法时可以触发Loader子类的实现逻辑。
AsyncTaskLoader抽象子类源码浅析
上面既然说了Loader类的作用主要是规定接口,同时供LoaderManager管理,那LoaderManager管理的Loader自然需要做一些事情,也就是说我们需要继承Loader实现一些逻辑操作。然而好在系统API已经帮我们实现了一些简单的封装实现,我们这里就先来看下Loader的直接子类AsyncTaskLoader吧,先来看下该抽象子类的方法及内部类粗略图,如下:
代码分析如下:
public abstract class AsyncTaskLoader<D> extends Loader<D> {static final String TAG = "AsyncTaskLoader";static final boolean DEBUG = false;//LoadTask内部类是对AsyncTask的封装,实现了Runnable接口final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {......@Overrideprotected D doInBackground(Void... params) {try {//AsyncTask的子线程中执行AsyncTaskLoader的onLoadInBackground方法!!!!重点D data = AsyncTaskLoader.this.onLoadInBackground();//把执行结果数据D返回到UI线程return data;} catch (OperationCanceledException ex) {if (!isCancelled()) {throw ex;}return null;}}/* Runs on the UI thread */@Overrideprotected void onPostExecute(D data) {//AsyncTask子线程执行完毕后回调AsyncTaskLoader的dispatchOnLoadComplete方法AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);}/* Runs on the UI thread */@Overrideprotected void onCancelled(D data) {//取消AsyncTask时调运AsyncTaskLoader.this.dispatchOnCancelled(this, data);}//Runnable的实现方法@Overridepublic void run() {waiting = false;AsyncTaskLoader.this.executePendingTask();}......}private final Executor mExecutor;volatile LoadTask mTask;volatile LoadTask mCancellingTask;long mUpdateThrottle;long mLastLoadCompleteTime = -10000;Handler mHandler;//public构造方法public AsyncTaskLoader(Context context) {this(context, AsyncTask.THREAD_POOL_EXECUTOR);}/** {@hide} 无法被外部调运的构造方法 */public AsyncTaskLoader(Context context, Executor executor) {super(context);mExecutor = executor;}public void setUpdateThrottle(long delayMS) {mUpdateThrottle = delayMS;if (delayMS != 0) {mHandler = new Handler();}}@Overrideprotected void onForceLoad() {super.onForceLoad();//取消当前的LoadercancelLoad();//新建task并执行mTask = new LoadTask();executePendingTask();}@Overrideprotected boolean onCancelLoad() {......}public void onCanceled(D data) {}//LoadTask的Runnable方法run中执行void executePendingTask() {if (mCancellingTask == null && mTask != null) {if (mTask.waiting) {mTask.waiting = false;mHandler.removeCallbacks(mTask);}if (mUpdateThrottle > 0) {long now = SystemClock.uptimeMillis();if (now < (mLastLoadCompleteTime+mUpdateThrottle)) {// Not yet time to do another load.mTask.waiting = true;mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);return;}}//真正的触发执行AsyncTask方法mTask.executeOnExecutor(mExecutor, (Void[]) null);}}void dispatchOnCancelled(LoadTask task, D data) {onCanceled(data);if (mCancellingTask == task) {rollbackContentChanged();mLastLoadCompleteTime = SystemClock.uptimeMillis();mCancellingTask = null;//触发Loader的接口方法onLoadCanceled,在LoaderManager中实现deliverCancellation();executePendingTask();}}void dispatchOnLoadComplete(LoadTask task, D data) {if (mTask != task) {dispatchOnCancelled(task, data);} else {if (isAbandoned()) {// This cursor has been abandoned; just cancel the new data.onCanceled(data);} else {commitContentChanged();mLastLoadCompleteTime = SystemClock.uptimeMillis();mTask = null;//触发Loader的接口方法onLoadComplete,在LoaderManager中实现deliverResult(data);}}}//需要子类实现!!!!!在子线程中执行public abstract D loadInBackground();//LoadTask(AsyncTask的子线程中回调)中调运protected D onLoadInBackground() {return loadInBackground();}//LoadTask(AsyncTask的onCancelLoad中回调)调运public void cancelLoadInBackground() {}public boolean isLoadInBackgroundCanceled() {return mCancellingTask != null;}//锁标记处理public void waitForLoader() {LoadTask task = mTask;if (task != null) {task.waitForLoader();}} }可以看见上面继承Loader的AsyncTaskLoader其实质是提供了一个基于AsyncTask工作机制的Loader(子类LoadTask继承AsyncTask< Void, Void, D >,并且实现了Runable接口,功能十分强大。),但是不可直接用,因为其为abstract抽象类,所以我们需要继承实现它才可以使用,然而好在系统API已经帮我们提供了他现成的子类CursorLoader,但CursorLoader同时也限制了Loader的泛型数据为Cursor类型。当然了,我们如果想要Loader自己的类型数据那也很简单—继承实现AsyncTaskLoader即可,后面会给出例子的。
CursorLoader子类源码浅析
有了上面继承自Loader的抽象AsyncTaskLoader,接下来我们就来看看SDK为我们提供的抽象AsyncTaskLoader实现类CursorLoader,我们先来粗略看看该类的方法图,如下:
具体代码分析如下:
//继承自AsyncTaskLoader,数据类型为Cursor的Loader异步加载实现类 public class CursorLoader extends AsyncTaskLoader<Cursor> {//ContentObserver的子类ForceLoadContentObserverfinal ForceLoadContentObserver mObserver;Uri mUri;String[] mProjection;String mSelection;String[] mSelectionArgs;String mSortOrder;Cursor mCursor;CancellationSignal mCancellationSignal;/* Runs on a worker thread 最核心的实现方法,在这里查询获取数据 */@Overridepublic Cursor loadInBackground() {synchronized (this) {if (isLoadInBackgroundCanceled()) {throw new OperationCanceledException();}mCancellationSignal = new CancellationSignal();}try {//不过多解释,耗时的查询操作Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,mSelectionArgs, mSortOrder, mCancellationSignal);if (cursor != null) {try {// Ensure the cursor window is filled.cursor.getCount();//给Cursor设置观察者;ContentProvider通知Cursor的观察者数据发生了改变,Cursor通知CursorLoader的观察者数据发生了改变,CursorLoader通过ContentProvider重新加载新的数据cursor.registerContentObserver(mObserver);} catch (RuntimeException ex) {cursor.close();throw ex;}}return cursor;} finally {synchronized (this) {mCancellationSignal = null;}}}@Overridepublic void cancelLoadInBackground() {super.cancelLoadInBackground();synchronized (this) {if (mCancellationSignal != null) {mCancellationSignal.cancel();}}}/* Runs on the UI thread */@Overridepublic void deliverResult(Cursor cursor) {if (isReset()) {// An async query came in while the loader is stoppedif (cursor != null) {cursor.close();}return;}Cursor oldCursor = mCursor;mCursor = cursor;if (isStarted()) {super.deliverResult(cursor);}if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {oldCursor.close();}}public CursorLoader(Context context) {super(context);mObserver = new ForceLoadContentObserver();}public CursorLoader(Context context, Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {super(context);//新建一个当前类(Loader)的内部类对象,数据库变化时调运ForceLoadContentObserver的onChange方法,onChange调运Loader的onContentChanged方法,onContentChanged调运Loader的forceLoad方法mObserver = new ForceLoadContentObserver();mUri = uri;mProjection = projection;mSelection = selection;mSelectionArgs = selectionArgs;mSortOrder = sortOrder;}@Overrideprotected void onStartLoading() {if (mCursor != null) {deliverResult(mCursor);}if (takeContentChanged() || mCursor == null) {forceLoad();}}@Overrideprotected void onStopLoading() {// Attempt to cancel the current load task if possible.cancelLoad();}@Overridepublic void onCanceled(Cursor cursor) {if (cursor != null && !cursor.isClosed()) {cursor.close();}}@Overrideprotected void onReset() {super.onReset();// Ensure the loader is stoppedonStopLoading();if (mCursor != null && !mCursor.isClosed()) {mCursor.close();}mCursor = null;}public Uri getUri() {return mUri;}public void setUri(Uri uri) {mUri = uri;}public String[] getProjection() {return mProjection;}public void setProjection(String[] projection) {mProjection = projection;}public String getSelection() {return mSelection;}public void setSelection(String selection) {mSelection = selection;}public String[] getSelectionArgs() {return mSelectionArgs;}public void setSelectionArgs(String[] selectionArgs) {mSelectionArgs = selectionArgs;}public String getSortOrder() {return mSortOrder;}public void setSortOrder(String sortOrder) {mSortOrder = sortOrder;} }可以发现,CursorLoader的封装大大简化了应用开发者代码的复杂度;它完全就是一个异步的数据库查询瑞士军刀,没有啥特别需要分析的地方,所以不再过多说明。
Loaders相关源码浅析总结
通过上面我们的源码分析和分析前那副图可以总结如下结论:
一次完整的数据加载流程为Activity调用LoaderManager的doStart()方法,然后LoaderManager调用Loader的startLoading()方法,然后Loader调运AsyncTaskLoader的doingBackground()方法进行耗时数据加载,然后AsyncTaskLoader回调LoaderManager的complete数据加载完成方法,接着LoaderManager回调我们在Activity中实现的callback中的onLoadFinish()方法。
Acivity和Fragment的生命周期主动管理了LoaderManager,每个Activity用一个ArrayMap的mAllLoaderManager来保存当前Activity及其附属Frament的唯一LoaderManager;在Activity配置发生变化时,Activity在destory前会保存mAllLoaderManager,当Activity再重新创建时,会在Activity的onAttcach()、onCreate()、performStart()方法中恢复mAllLoaderManager。
LoaderManager给Activity提供了管理自己的一些方法;同时主动管理了对应的Loader,它把每一个Loader封装为LoadInfo对象,同时它负责主动调运管理Loader的startLoading()、stopLoading()、,forceLoad()等方法。
由于整个Activity和Fragment主动管理了Loader,所以关于Loader的释放(譬如CursorLoader的Cursor关闭等)不需要我们人为处理,Loader框架会帮我们很好的处理的;同时特别注意,对于CursorLoader,当我们数据源发生变化时Loader框架会通过ContentObserver调用onContentChanged的forceLoad方法重新请求数据进行回调刷新。
好了,至此你会发现Loader真的很牛叉,No!应该是Google的工程师真的很牛叉,架构真的很赞,值得推荐。
四、应用层开发之Loader进阶实战
上面对于Loader的基础使用及源码框架都进行了简单分析,有了上面的铺垫我们再回过头来看看我们开发中的一些高级技巧,通过这些高级技巧不仅是对前面源码分析的实例验证,也是对自己知识的积累。
ContentPorvider情况下的CurSorLoader自动刷新
在我们使用CurSorLoader时大家都会考虑一种情况的处理—–当数据库发生变化时如何自动刷新当前UI。呵呵,我们先来说说这个原理,数据库在数据改变时通过ContentPorvider和ContentResolver发出通知,接着ContentProvider通知Cursor的观察者数据发生了变化,然后Cursor通知CursorLoader的观察者数据发生了变化,接着CursorLoader通过ContentProvider加载新数据,完事调用CursorAdapter的changeCursor()用新数据替换旧数据显示。
这个过程具体的实现步骤如下:
对获取的Cursor数据设置需要监听的URI(即,在ContentProvider的query()方法或者Loader的loadingBackground()方法中调用Cursor的setNotificationUri()方法);
在ContentProvider的insert()、update()、delete()等方法中调用ContentResolver的notifyChange()方法;
通过上面两步我们就能享受CurSorLoader的自动数据刷新功能了;可以发现,所谓的CurSorLoader自动刷新无非就是观察者模式的框架而已,所以不再过多说明。
特别注意:
有些人觉得为了方便可能会将上面第一步对于Cursor设置监听直接写在了ContentProvider的query()方法中,如下:
@Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder) {SQLiteDatabase database = sqLiteOpenHelper.getReadableDatabase();Cursor cursor = database.query(EmailContent.CONTACT_TABLE, projection,selection,selectionArgs, null, null, sortOrder);//设置NotificationUri监听cursor.setNotificationUri(contentResolver, EmailContent.MESSAGE);return cursor; }这里要提醒的是,这种写法在某些场合下是不值得推荐的(譬如大规模上千次并发平凡的调运query操作场合),因为效率极低,他会频繁的通过Binder进行通信,导致system_server不停的调运GC操作,以至于会使系统卡顿。
PS:因为我以前跳过一次这个坑,平时使用应用没啥问题,但是当进行压力测试时却发现LogCat一直在不停的打印GC,同时导致当前系统卡顿,杀掉应用后系统就不卡了,所以基本怀疑问题就出在了应用中,于是通过很多办法去查找(譬如dempsys content去查看个数),最终发现罪魁祸首是这个监听频繁调运导致的,随将其挪到loadingBackground中不再卡顿。
不使用ContentPorvider且自定义Loader的情况下自动刷新
我们目前的项目其实都使用了ContentPorvider实现,所以就是上面讲的那些情况。但是你一定会问,如果我们应用的数据不用于应用间共享,使用ContentProvider那得多麻烦啊?我先告诉你,是很麻烦,但是Android提供的CursorLoader的API必须使用ContentProvider才能实现数据加载和自动刷新。
这时候你指定会说,那还说个屁!哎,别急,你看看下面这段代码是否会有所感触呢,如下:
public NoProviderLoader extends AsyncTaskLoader {......ForceLoadContentObserver mObserver = new ForceLoadContentObserver();......@Overridepublic Cursor loadInBackground() {SQLiteDatabase database = sqLiteOpenHelper.getReadableDatabase();Cursor cursor = database.query(table, columns, selection, selectionArgs, groupBy, having, orderBy);if (cursor != null) {//最重要的两行代码!!!!!!cursor.registerContentObserver(mObserver);//给Cursor设置观察者cursor.setNotificationUri(getContext().getContentResolver(), otificationUri);//给Cursor设置要观察的URI}return cursor;}...... }咦?是不是上面代码很奇怪,异步操作的方法中没有使用ContentProvider,而是直接读取了数据库。握草!这不就是我们刚刚想要的需求么,它没有使用ContentProvider提供Cursor数据,同时实现了数据变化自动更新功能。
简单解释下上面代码的原理吧,我们自定义的NoProviderLoader中定义的ForceLoadContentObserver是Loader的一个内部类,上面源码分析已经解释过了,当数据变化时会调运该类的onChange()方法,实质是调运了Loader的forceLoad()方法,所以能够自动刷新,不多解释了。
Loader自定义之AsyncTaskLoader衍生
可能看到这里你更加会举一反三的反驳一句了,上面搞了半天都是和数据库Cursor相关的东东,难道Loader就不能异步处理别的数据结构么?答案是能,因为你可能已经注意到了Loader和AsyncTaskLoader都是泛型类;既然这样,那我们找猫画虎一把呗,仿照CursorLoader自定义一个自己的异步加载试试,具体实现如下(哈哈,想了又想,这里还是直接给出官方的自定义AsyncTaskLoader好点,毕竟权威些,详细点我查看官方自定义实现Demo):
官方对于查询已安装App列表的Loader实现,支持新App安装后自动刷新的功能,实现如下:
/*** This class holds the per-item data in our Loader.*/ public static class AppEntry {public AppEntry(AppListLoader loader, ApplicationInfo info) {mLoader = loader;mInfo = info;mApkFile = new File(info.sourceDir);}public ApplicationInfo getApplicationInfo() {return mInfo;}public String getLabel() {return mLabel;}public Drawable getIcon() {if (mIcon == null) {if (mApkFile.exists()) {mIcon = mInfo.loadIcon(mLoader.mPm);return mIcon;} else {mMounted = false;}} else if (!mMounted) {// If the app wasn't mounted but is now mounted, reload// its icon.if (mApkFile.exists()) {mMounted = true;mIcon = mInfo.loadIcon(mLoader.mPm);return mIcon;}} else {return mIcon;}return mLoader.getContext().getResources().getDrawable(android.R.drawable.sym_def_app_icon);}@Override public String toString() {return mLabel;}void loadLabel(Context context) {if (mLabel == null || !mMounted) {if (!mApkFile.exists()) {mMounted = false;mLabel = mInfo.packageName;} else {mMounted = true;CharSequence label = mInfo.loadLabel(context.getPackageManager());mLabel = label != null ? label.toString() : mInfo.packageName;}}}private final AppListLoader mLoader;private final ApplicationInfo mInfo;private final File mApkFile;private String mLabel;private Drawable mIcon;private boolean mMounted; }/*** Perform alphabetical comparison of application entry objects.*/ public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() {private final Collator sCollator = Collator.getInstance();@Overridepublic int compare(AppEntry object1, AppEntry object2) {return sCollator.compare(object1.getLabel(), object2.getLabel());} };/*** Helper for determining if the configuration has changed in an interesting* way so we need to rebuild the app list.*/ public static class InterestingConfigChanges {final Configuration mLastConfiguration = new Configuration();int mLastDensity;boolean applyNewConfig(Resources res) {int configChanges = mLastConfiguration.updateFrom(res.getConfiguration());boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;if (densityChanged || (configChanges&(ActivityInfo.CONFIG_LOCALE|ActivityInfo.CONFIG_UI_MODE|ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) {mLastDensity = res.getDisplayMetrics().densityDpi;return true;}return false;} }/*** Helper class to look for interesting changes to the installed apps* so that the loader can be updated.*/ public static class PackageIntentReceiver extends BroadcastReceiver {final AppListLoader mLoader;public PackageIntentReceiver(AppListLoader loader) {mLoader = loader;IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);filter.addAction(Intent.ACTION_PACKAGE_REMOVED);filter.addAction(Intent.ACTION_PACKAGE_CHANGED);filter.addDataScheme("package");mLoader.getContext().registerReceiver(this, filter);// Register for events related to sdcard installation.IntentFilter sdFilter = new IntentFilter();sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);mLoader.getContext().registerReceiver(this, sdFilter);}@Override public void onReceive(Context context, Intent intent) {// Tell the loader about the change.mLoader.onContentChanged();} }/*** A custom Loader that loads all of the installed applications.*/ public static class AppListLoader extends AsyncTaskLoader<List<AppEntry>> {final InterestingConfigChanges mLastConfig = new InterestingConfigChanges();final PackageManager mPm;List<AppEntry> mApps;PackageIntentReceiver mPackageObserver;public AppListLoader(Context context) {super(context);// Retrieve the package manager for later use; note we don't// use 'context' directly but instead the save global application// context returned by getContext().mPm = getContext().getPackageManager();}/*** This is where the bulk of our work is done. This function is* called in a background thread and should generate a new set of* data to be published by the loader.*/@Override public List<AppEntry> loadInBackground() {// Retrieve all known applications.List<ApplicationInfo> apps = mPm.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES |PackageManager.GET_DISABLED_COMPONENTS);if (apps == null) {apps = new ArrayList<ApplicationInfo>();}final Context context = getContext();// Create corresponding array of entries and load their labels.List<AppEntry> entries = new ArrayList<AppEntry>(apps.size());for (int i=0; i<apps.size(); i++) {AppEntry entry = new AppEntry(this, apps.get(i));entry.loadLabel(context);entries.add(entry);}// Sort the list.Collections.sort(entries, ALPHA_COMPARATOR);// Done!return entries;}/*** Called when there is new data to deliver to the client. The* super class will take care of delivering it; the implementation* here just adds a little more logic.*/@Override public void deliverResult(List<AppEntry> apps) {if (isReset()) {// An async query came in while the loader is stopped. We// don't need the result.if (apps != null) {onReleaseResources(apps);}}List<AppEntry> oldApps = mApps;mApps = apps;if (isStarted()) {// If the Loader is currently started, we can immediately// deliver its results.super.deliverResult(apps);}// At this point we can release the resources associated with// 'oldApps' if needed; now that the new result is delivered we// know that it is no longer in use.if (oldApps != null) {onReleaseResources(oldApps);}}/*** Handles a request to start the Loader.*/@Override protected void onStartLoading() {if (mApps != null) {// If we currently have a result available, deliver it// immediately.deliverResult(mApps);}// Start watching for changes in the app data.if (mPackageObserver == null) {mPackageObserver = new PackageIntentReceiver(this);}// Has something interesting in the configuration changed since we// last built the app list?boolean configChange = mLastConfig.applyNewConfig(getContext().getResources());if (takeContentChanged() || mApps == null || configChange) {// If the data has changed since the last time it was loaded// or is not currently available, start a load.forceLoad();}}/*** Handles a request to stop the Loader.*/@Override protected void onStopLoading() {// Attempt to cancel the current load task if possible.cancelLoad();}/*** Handles a request to cancel a load.*/@Override public void onCanceled(List<AppEntry> apps) {super.onCanceled(apps);// At this point we can release the resources associated with 'apps'// if needed.onReleaseResources(apps);}/*** Handles a request to completely reset the Loader.*/@Override protected void onReset() {super.onReset();// Ensure the loader is stoppedonStopLoading();// At this point we can release the resources associated with 'apps'// if needed.if (mApps != null) {onReleaseResources(mApps);mApps = null;}// Stop monitoring for changes.if (mPackageObserver != null) {getContext().unregisterReceiver(mPackageObserver);mPackageObserver = null;}}/*** Helper function to take care of releasing resources associated* with an actively loaded data set.*/protected void onReleaseResources(List<AppEntry> apps) {// For a simple List<> there is nothing to do. For something// like a Cursor, we would close it here.} }不用多说,上面Loader为Google出品,强大的不得了,我们完全可以仿写这个例子实现自己的请求。
如下为官方对该自定义Loader调运的Demo代码:
public static class AppListAdapter extends ArrayAdapter<AppEntry> {private final LayoutInflater mInflater;public AppListAdapter(Context context) {super(context, android.R.layout.simple_list_item_2);mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);}public void setData(List<AppEntry> data) {clear();if (data != null) {addAll(data);}}/*** Populate new items in the list.*/@Override public View getView(int position, View convertView, ViewGroup parent) {View view;if (convertView == null) {view = mInflater.inflate(R.layout.list_item_icon_text, parent, false);} else {view = convertView;}AppEntry item = getItem(position);((ImageView)view.findViewById(R.id.icon)).setImageDrawable(item.getIcon());((TextView)view.findViewById(R.id.text)).setText(item.getLabel());return view;} }public static class AppListFragment extends ListFragmentimplements OnQueryTextListener, OnCloseListener,LoaderManager.LoaderCallbacks<List<AppEntry>> {// This is the Adapter being used to display the list's data.AppListAdapter mAdapter;// The SearchView for doing filtering.SearchView mSearchView;// If non-null, this is the current filter the user has provided.String mCurFilter;@Override public void onActivityCreated(Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);// Give some text to display if there is no data. In a real// application this would come from a resource.setEmptyText("No applications");// We have a menu item to show in action bar.setHasOptionsMenu(true);// Create an empty adapter we will use to display the loaded data.mAdapter = new AppListAdapter(getActivity());setListAdapter(mAdapter);// Start out with a progress indicator.setListShown(false);// Prepare the loader. Either re-connect with an existing one,// or start a new one.getLoaderManager().initLoader(0, null, this);}public static class MySearchView extends SearchView {public MySearchView(Context context) {super(context);}// The normal SearchView doesn't clear its search text when// collapsed, so we will do this for it.@Overridepublic void onActionViewCollapsed() {setQuery("", false);super.onActionViewCollapsed();}}@Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {// Place an action bar item for searching.MenuItem item = menu.add("Search");item.setIcon(android.R.drawable.ic_menu_search);item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM| MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);mSearchView = new MySearchView(getActivity());mSearchView.setOnQueryTextListener(this);mSearchView.setOnCloseListener(this);mSearchView.setIconifiedByDefault(true);item.setActionView(mSearchView);}@Override public boolean onQueryTextChange(String newText) {// Called when the action bar search text has changed. Since this// is a simple array adapter, we can just have it do the filtering.mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;mAdapter.getFilter().filter(mCurFilter);return true;}@Override public boolean onQueryTextSubmit(String query) {// Don't care about this.return true;}@Overridepublic boolean onClose() {if (!TextUtils.isEmpty(mSearchView.getQuery())) {mSearchView.setQuery(null, true);}return true;}@Override public void onListItemClick(ListView l, View v, int position, long id) {// Insert desired behavior here.Log.i("LoaderCustom", "Item clicked: " + id);}@Override public Loader<List<AppEntry>> onCreateLoader(int id, Bundle args) {// This is called when a new Loader needs to be created. This// sample only has one Loader with no arguments, so it is simple.return new AppListLoader(getActivity());}@Override public void onLoadFinished(Loader<List<AppEntry>> loader, List<AppEntry> data) {// Set the new data in the adapter.mAdapter.setData(data);// The list should now be shown.if (isResumed()) {setListShown(true);} else {setListShownNoAnimation(true);}}@Override public void onLoaderReset(Loader<List<AppEntry>> loader) {// Clear the data in the adapter.mAdapter.setData(null);} }强大的一逼!这下满技能,不解释,自己看。
进阶总结
通过前面基础实例、源码分析、进阶演示你会发现Loader的真的非常好用,非常牛逼,牛逼的我不想再解释啥了,自己体会吧。
PS:之前看见微博上有人讨论AsyncTaskLoader与AsyncTask的区别,这下彻底明朗了,看完源码我们再回过头来总结性的说说他们二者区别,如下:
| AsyncTaskLoader | 会自动刷新数据变化;会自动处理Activiy配置变化造成的影响;适合处理纯数据加载 | 不能实时通知UI刷新;不能在onLoadFinished时主动切换生命周期(譬如replace Fragment) |
| AsyncTask | 可以与UI实时交互及replace操作 | 不会自动处理Activiy配置变化造成的影响 |
好了,该撕逼的也撕了,该装逼的也装了,该分析的也分析了,该学习的也学到了,接下来就是看自己如何带着Loader去叱诧风云了。
总结
以上是生活随笔为你收集整理的Android Loader机制全面详解及源码浅析的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: 安卓自定义View进阶-Matrix C
- 下一篇: Android性能优化典范 - 第6季