在看Android的文档时,看到了这么一个东西: Loader

究竟是什么东西呢?

Introduced in Android 3.0, loaders make it easy to asynchronously load data in an activity or fragment. Loaders have these characteristics:

1、They are available to every Activity and Fragment. //支持Activity和Fragment

2、They provide asynchronous loading of data. //异步下载

3、They monitor the source of their data and deliver new results when the content changes. //当数据源改变时能及时通知客户端

4、They automatically reconnect to the last loader's cursor when being recreated after a configuration change. Thus, they don't need to re-query their data. //发生configuration change时自动重连接

看来这东西蛮强大的,开始我的探索之路吧.

先简单看一下它的用法先:


001 /**
002 * Demonstration of the use of a CursorLoader to load and display contacts
003 * data in a fragment.
004 */
005 public class LoaderCursor extends Activity {
006
007 @Override
008 protected void onCreate(Bundle savedInstanceState) {
009 super.onCreate(savedInstanceState);
010
011 FragmentManager fm = getFragmentManager();
012
013 // Create the list fragment and add it as our sole content.
014 if (fm.findFragmentById(android.R.id.content) == null) {
015 CursorLoaderListFragment list = new CursorLoaderListFragment();
016 fm.beginTransaction().add(android.R.id.content, list).commit();
017 }
018 }
019
020
021 public static class CursorLoaderListFragment extends ListFragment
022 implements LoaderManager.LoaderCallbacks<Cursor> {
023
024 // This is the Adapter being used to display the list's data.
025 SimpleCursorAdapter mAdapter;
026
027 // If non-null, this is the current filter the user has provided.
028 String mCurFilter;
029
030 @Override public void onActivityCreated(Bundle savedInstanceState) {
031
032 mAdapter = new SimpleCursorAdapter(getActivity(),
033 android.R.layout.simple_list_item_2, null,
034 new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
035 new int[] { android.R.id.text1, android.R.id.text2 }, 0);
036 setListAdapter(mAdapter);
037
038 getLoaderManager().initLoader(0, null, this);
039 }
040
041
042 @Override public void onListItemClick(ListView l, View v, int position, long id) {
043 // Insert desired behavior here.
044 Log.i("FragmentComplexList", "Item clicked: " + id);
045 }
046
047 // These are the Contacts rows that we will retrieve.
048 static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
049 Contacts._ID,
050 Contacts.DISPLAY_NAME,
051 Contacts.CONTACT_STATUS,
052 Contacts.CONTACT_PRESENCE,
053 Contacts.PHOTO_ID,
054 Contacts.LOOKUP_KEY,
055 };
056
057 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
058 // This is called when a new Loader needs to be created. This
059 // sample only has one Loader, so we don't care about the ID.
060 // First, pick the base URI to use depending on whether we are
061 // currently filtering.
062 Uri baseUri;
063 if (mCurFilter != null) {
064 baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
065 Uri.encode(mCurFilter));
066 } else {
067 baseUri = Contacts.CONTENT_URI;
068 }
069
070 // Now create and return a CursorLoader that will take care of
071 // creating a Cursor for the data being displayed.
072 String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
073 + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
074 + Contacts.DISPLAY_NAME + " != '' ))";
075 return new CursorLoader(getActivity(), baseUri,
076 CONTACTS_SUMMARY_PROJECTION, select, null,
077 Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
078 }
079
080 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
081 // Swap the new cursor in. (The framework will take care of closing the
082 // old cursor once we return.)
083 mAdapter.swapCursor(data);
084
085 // The list should now be shown.
086 if (isResumed()) {
087 setListShown(true);
088 } else {
089 setListShownNoAnimation(true);
090 }
091 }
092
093 public void onLoaderReset(Loader<Cursor> loader) {
094 // This is called when the last Cursor provided to onLoadFinished()
095 // above is about to be closed. We need to make sure we are no
096 // longer using it.
097 mAdapter.swapCursor(null);
098 }
099 }
100
101 }

这里是Android提供的实例代码,有删减。

从代码上看来,通过实现LoaderManager.LoaderCallbacks就行了.

在onCreateLoader里面实现你要请求的耗时操作,当异步线程操作完成之后就会从onLoadFinished返回数据.

用起来是不是很简单呢?下面具体来看一下它是怎么做到的吧.

getLoaderManager()是定义在Activity类的一个方法,返回类型LoaderManager,但这只是个接口,它真正的实现类是谁呢?

继续往下走,看到这个LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create),方法时,答案便揭晓了.

下面我们来看看LoaderManager相关的类结构,省略了很多东西,但不影响我们的分析.

现在我们来到了LoaderManagerImp的initLoader方法了.


01 public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
02 if (mCreatingLoader) {
03 throw new IllegalStateException("Called while creating a loader");
04 }
05
06 LoaderInfo info = mLoaders.get(id);
07
08 if (info == null) {
09 // Loader doesn't already exist; create.
10 info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
11 if (DEBUG) Log.v(TAG, " Created new loader " + info);
12 } else {
13 if (DEBUG) Log.v(TAG, " Re-using existing loader " + info);
14 info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
15 }
16
17 if (info.mHaveData && mStarted) {
18 // If the loader has already generated its data, report it now.
19 info.callOnLoadFinished(info.mLoader, info.mData);
20 }
21
22 return (Loader<D>)info.mLoader;
23 }

这是一个新的Loader,那么info应该是null,转入执行createAndInstallLoader.
01 private LoaderInfo createAndInstallLoader(int id, Bundle args,
02 LoaderManager.LoaderCallbacks<Object> callback) {
03 try {
04 mCreatingLoader = true;
05 LoaderInfo info = createLoader(id, args, callback);
06 installLoader(info);
07 return info;
08 } finally {
09 mCreatingLoader = false;
10 }
11 }
12
13 private LoaderInfo createLoader(int id, Bundle args,
14 LoaderManager.LoaderCallbacks<Object> callback) {
15 LoaderInfo info = new LoaderInfo(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
16 Loader<Object> loader = callback.onCreateLoader(id, args);
17 info.mLoader = (Loader<Object>)loader;
18 return info;
19 }
20
21 void installLoader(LoaderInfo info) {
22 mLoaders.put(info.mId, info);
23 if (mStarted) {
24 // The activity will start all existing loaders in it's onStart(),
25 // so only start them here if we're past that point of the activitiy's
26 // life cycle
27 info.start();
28 }
29 }

createLoader把必要的信息都封装在LoaderInfo类里面,留意以下这一行:

callback.onCreateLoader(id,arg),这里正是我们上面在客户端实现接口LoaderCallback的那个方法.

接着调用installLoader,这个方法把这次Loader的信息put进mLoader这个SparseArrayCompat中,这个对象可以理解为一个Map,它的性能比Map要好.

mStarted的值是true,它是在getLoaderManager的时候在Activity中传进来的true值.

好了,下面进入LoaderInfo的start方法了.


01 void start() {
02 if (mLoader != null) {
03
04 if (!mListenerRegistered) {
05 mLoader.registerListener(mId, this);
06 mListenerRegistered = true;
07 }
08 mLoader.startLoading();
09 }
10 }


mLoader就是在客户端实现的那个Loader,回到我们刚开始时的例子,它就是一个CursorLoader.

在分析CursorLoader的startLoading之前,我们先看一下这些Loader的类结构先:

从这些类的名称看来,真正实现了异步传输功能的类应该就是AsyncTaskLoader了,事实是不是这样呢?

继续深入下去:

这里的startLoading是调用了Loader类的方法,下文中我会用这样的方法来标识方法是属于哪个类的: 如Loader –> startLoading


01 Loader:
02 public final void startLoading() {
03 mStarted = true;
04 mReset = false;
05 mAbandoned = false;
06 onStartLoading();
07 }
08
09 CursorLoader:
10 protected void onStartLoading() {
11 if (mCursor != null) {
12 deliverResult(mCursor);
13 }
14 if (takeContentChanged() || mCursor == null) {
15 forceLoad();
16 }
17 }
18
19 AsynTaskLoader:
20 protected void onForceLoad() {
21 super.onForceLoad();
22 cancelLoad();
23 mTask = new LoadTask();
24 if (DEBUG) Slog.v(TAG, "Preparing load: mTask=" + mTask);
25 executePendingTask();
26 }

终于看到了LoadTask关键字啦,答案就要揭晓啦.


01 AsyncTaskLoader:
02 final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {
03 private final CountDownLatch mDone = new CountDownLatch(1);
04
05 // Set to true to indicate that the task has been posted to a handler for
06 // execution at a later time. Used to throttle updates.
07 boolean waiting;
08
09 /* Runs on a worker thread */
10 @Override
11 protected D doInBackground(Void... params) {
12 if (DEBUG) Slog.v(TAG, this + " >>> doInBackground");
13 try {
14 D data = AsyncTaskLoader.this.onLoadInBackground();
15 return data;
16 } catch (OperationCanceledException ex) {
17 }
18 }
19
20 /* Runs on the UI thread */
21 @Override
22 protected void onPostExecute(D data) {
23 if (DEBUG) Slog.v(TAG, this + " onPostExecute");
24 try {
25 AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
26 } finally {
27 mDone.countDown();
28 }
29 }
30 }
31
32 AsyncTaskLoader:
33 protected D onLoadInBackground() {
34 return loadInBackground();
35 }
36
37 CursorLoader:
38 public Cursor loadInBackground() {
39 try {
40 Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
41 mSelectionArgs, mSortOrder, mCancellationSignal);
42 if (cursor != null) {
43 // Ensure the cursor window is filled
44 cursor.getCount();
45 registerContentObserver(cursor, mObserver);
46 }
47 return cursor;
48 } finally {
49 synchronized (this) {
50 mCancellationSignal = null;
51 }
52
53 }

LoadTask原来是个AsyncTask类型,看到这里大家大家应该觉得有种豁然的感觉了吧.

在ForceLoad里面启动该线程,开始执行doInBackground,回调CursorLoader里面的loadInBackgroud,这个方法里面执行真正的耗时操作,

执行完之后一层一层返回,接着调用onPostExecute方法.

好了,现在数据总算是拿到了.

接着执行,把获取的数据往回调.

LoadTask -> onPostExecute

----->

AsynTaskLoader-> dispatchOnLoadComplete

----->

Loader->deliverResult

回调前面注册的loadComplete:

LoaderInfo -> onLoadComplete

---->

LoaderInfo ->callOnLoadFinished

把数据回调给客户端

mCallbacks.onLoadFinished(loader, data);

到这里就完美解释了Loader的特点2,异步

第三点当数据源改变时能及时通知客户端又是如何体现的呢?

这里用了观察者模式来实现.我们先看一下CursorLoader的构造函数:

mObserver = new ForceLoadContentObserver();

这个ForceLoadContentObserver是什么东西呢?

ForceLoadContentObserver继承了ContentObserver,这是Android内部的一个对象,继承了它,就能享受到数据变化时可以接收到通知(也就是观察者中的Subject),这里类似于数据库中的触发器.

先往下看:

在CursorLoader->loadInBackground方法中有这么一句:

registerContentObserver(cursor, mObserver);//注册观察者

答案揭晓了.

注册观察者后,当对应的URI发生变化是,会触发onChange方法


01 public void onChange(boolean selfChange) {
02 onContentChanged();
03 }
04
05 public void onContentChanged() {
06 if (mStarted) {
07 forceLoad(); //这里重新发送请求.
08 } else {
09 // This loader has been stopped, so we don't want to load
10 // new data right now... but keep track of it changing to
11 // refresh later if we start again.
12 mContentChanged = true;
13 }
14 }


对于forceLoad方法前面已经提高过了,大家应该还有印象吧.

最后一个问题,也就是第四点:如何做到在configuration change自动重链接的呢?

只要能回答这两个问题,这个问题就解决了.

<1>loader如何在configuration change之前保存数据?

<2>loader如何在configuration chage之后恢复数据并继续load?

LoaderManager:

还记得吗?Loader创建之初,在LoaderManagerImp->installLoader方法里面,

mLoaders.put(info.mId, info);

Info 是LoaderInfo对象,里面封装了Loader的相关信息,表示这个LoaderInfo的Key是mId.

就是在这里保存了loader.这样就回答了问题<1>

对于问题二,首先我们来了解一下configuration change发生之后会发生什么事情呢?

还记得这个生命周期图吗,Fragment的也是差不多的.

当configuration change发生之后,会先把原来的Activity销毁掉,然后再重新构建一个,

也就是会重走一遍onCreate->onStart->onResume的过程.

好了,明白这个之后,我在onStart方法里面找到了线索.


01 Activity:
02 protected void onStart() {
03 if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this);
04 mCalled = true;
05
06 if (!mLoadersStarted) {
07 mLoadersStarted = true;
08 if (mLoaderManager != null) {
09 mLoaderManager.doStart();
10 } else if (!mCheckedForLoaderManager) {
11 mLoaderManager = getLoaderManager(null, mLoadersStarted, false);
12 }
13 mCheckedForLoaderManager = true;
14 }
15
16 getApplication().dispatchActivityStarted(this);
17 }
18
19 LoaderManagerImp:
20 void doStart() {
21 if (DEBUG) Log.v(TAG, "Starting in " + this);
22 if (mStarted) {
23 RuntimeException e = new RuntimeException("here");
24 e.fillInStackTrace();
25 Log.w(TAG, "Called doStart when already started: " + this, e);
26 return;
27 }
28
29 mStarted = true;
30
31 // Call out to sub classes so they can start their loaders
32 // Let the existing loaders know that we want to be notified when a load is complete
33 for (int i = mLoaders.size()-1; i >= 0; i--) {
34 mLoaders.valueAt(i).start();
35 }
36 }


留意doStart的For循环,真相大白了..

最后总结一下:

1、异步是通过AsynTaskLoader来实现的。

2、通过观察者模式来实现监控数据的变化.

3、通过Activity生命周期中的onStart来实现自动重连接.

更多相关文章

  1. Delphi XE5 android 获取网络状态
  2. Android运行虚拟机报错Execution failed for task ':app:transfo
  3. Android(安卓)Post方法请求URL 迭代器
  4. Android(安卓)四大组件之 Service
  5. Android(安卓)view手势缩放与移动
  6. Android(安卓)xposed Hook 初探01
  7. android截屏简单引用
  8. 浅谈Java中Collections.sort对List排序的两种方法
  9. Python list sort方法的具体使用

随机推荐

  1. Android学习以及如何用android赚钱
  2. Android大赛首轮获奖作品解析。。。
  3. 跟核心虚拟机Dalvik说再见Android(安卓)R
  4. Android源代码是这样搞到的(图解)
  5. Java基础查漏补缺:(开篇)为什么要在即将找工
  6. Android推送、智能心跳解决方案、手机休
  7. Android内存管理的原理--进程管理
  8. 学习Android自己的第一个应用
  9. Android、iPhone和Java三个平台一致的加
  10. android 笔记 --- Android自动判断输入是