Android之Loader理解
2016-02-18 15:44
435 查看
在看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时自动重连接
看来这东西蛮强大的,开始我的探索之路吧.
先简单看一下它的用法先:
这里是Android提供的实例代码,有删减。
从代码上看来,通过实现LoaderManager.LoaderCallbacks就行了.
在onCreateLoader里面实现你要请求的耗时操作,当异步线程操作完成之后就会从onLoadFinished返回数据.
用起来是不是很简单呢?下面具体来看一下它是怎么做到的吧.
getLoaderManager()是定义在Activity类的一个方法,返回类型LoaderManager,但这只是个接口,它真正的实现类是谁呢?
继续往下走,看到这个LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create),方法时,答案便揭晓了.
下面我们来看看LoaderManager相关的类结构,省略了很多东西,但不影响我们的分析.
现在我们来到了LoaderManagerImp的initLoader方法了.
这是一个新的Loader,那么info应该是null,转入执行createAndInstallLoader.
createLoader把必要的信息都封装在LoaderInfo类里面,留意以下这一行:
callback.onCreateLoader(id,arg),这里正是我们上面在客户端实现接口LoaderCallback的那个方法.
接着调用installLoader,这个方法把这次Loader的信息put进mLoader这个SparseArrayCompat中,这个对象可以理解为一个Map,它的性能比Map要好.
mStarted的值是true,它是在getLoaderManager的时候在Activity中传进来的true值.
好了,下面进入LoaderInfo的start方法了.
mLoader就是在客户端实现的那个Loader,回到我们刚开始时的例子,它就是一个CursorLoader.
在分析CursorLoader的startLoading之前,我们先看一下这些Loader的类结构先:
从这些类的名称看来,真正实现了异步传输功能的类应该就是AsyncTaskLoader了,事实是不是这样呢?
继续深入下去:
这里的startLoading是调用了Loader类的方法,下文中我会用这样的方法来标识方法是属于哪个类的: 如Loader –> startLoading
终于看到了LoadTask关键字啦,答案就要揭晓啦.
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方法
对于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方法里面找到了线索.
留意doStart的For循环,真相大白了..
最后总结一下:
1、异步是通过AsynTaskLoader来实现的。
2、通过观察者模式来实现监控数据的变化.
3、通过Activity生命周期中的onStart来实现自动重连接.
究竟是什么东西呢?
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 onActivity 4000 Created(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 1ba3b |
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 | } |
从代码上看来,通过实现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 | } |
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 | } |
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 | } |
在分析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 | } |
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 | } |
在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 | } |
最后一个问题,也就是第四点:如何做到在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 | } |
最后总结一下:
1、异步是通过AsynTaskLoader来实现的。
2、通过观察者模式来实现监控数据的变化.
3、通过Activity生命周期中的onStart来实现自动重连接.
相关文章推荐
- 第四章 提高工作效率的16条Android开发小经验
- android四大组件(详细总结)
- ANDROID开发之SQLite详解
- Android 操作SQLite基本用法
- Android SwipeRefreshLayout 包含ListView 上拉刷新 下拉加载
- Android 横竖屏切换activity生命周期
- android 入门 007(界面跳转)
- Android底部菜单和侧滑菜单的综合应用
- android hasExtra的用法
- Android 单例(Singleton)模式 日志类
- android内存泄露介绍
- android listview显示数据库内容
- android 基于签名的加密基础知识
- 获取android版本号
- ANDROID_MARS学习笔记_S01原始版_023_MP3PLAYER004_同步显示歌词
- Android中处理崩溃异常
- Android自定义属性,format详解
- Android常用布局及属性--LinearLayout
- Android 手机蓝牙开发01
- 日常问题汇总