原文:http://blog.csdn.net/luohai859/article/details/23938291
在看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时自动重连接
看来这东西蛮强大的,开始我的探索之路吧.
先简单看一下它的用法先:
002 |
* Demonstration of the use of a CursorLoader to load and display contacts |
003 |
* data in a fragment. |
005 |
public class LoaderCursor extends Activity { |
008 |
protected void onCreate(Bundle savedInstanceState) { |
009 |
super .onCreate(savedInstanceState); |
011 |
FragmentManager fm = getFragmentManager(); |
014 |
if (fm.findFragmentById(android.R.id.content) == null ) { |
015 |
CursorLoaderListFragment list = new CursorLoaderListFragment(); |
016 |
fm.beginTransaction().add(android.R.id.content, list).commit(); |
021 |
public static class CursorLoaderListFragment extends ListFragment |
022 |
implements LoaderManager.LoaderCallbacks<Cursor> { |
025 |
SimpleCursorAdapter mAdapter; |
030 |
@Override public void onActivityCreated(Bundle savedInstanceState) { |
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); |
038 |
getLoaderManager().initLoader( 0 , null , this ); |
042 |
@Override public void onListItemClick(ListView l, View v, int position, long id) { |
044 |
Log.i( "FragmentComplexList" , "Item clicked: " + id); |
048 |
static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] { |
050 |
Contacts.DISPLAY_NAME, |
051 |
Contacts.CONTACT_STATUS, |
052 |
Contacts.CONTACT_PRESENCE, |
057 |
public Loader<Cursor> onCreateLoader( int id, Bundle args) { |
063 |
if (mCurFilter != null ) { |
064 |
baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, |
065 |
Uri.encode(mCurFilter)); |
067 |
baseUri = Contacts.CONTENT_URI; |
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" ); |
080 |
public void onLoadFinished(Loader<Cursor> loader, Cursor data) { |
083 |
mAdapter.swapCursor(data); |
089 |
setListShownNoAnimation( true ); |
093 |
public void onLoaderReset(Loader<Cursor> loader) { |
097 |
mAdapter.swapCursor( null ); |
这里是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) { |
03 |
throw new IllegalStateException( "Called while creating a loader" ); |
06 |
LoaderInfo info = mLoaders.get(id); |
10 |
info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); |
11 |
if (DEBUG) Log.v(TAG, " Created new loader " + info); |
13 |
if (DEBUG) Log.v(TAG, " Re-using existing loader " + info); |
14 |
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback; |
17 |
if (info.mHaveData && mStarted) { |
19 |
info.callOnLoadFinished(info.mLoader, info.mData); |
22 |
return (Loader<D>)info.mLoader; |
这是一个新的Loader,那么info应该是null,转入执行createAndInstallLoader.
01 |
private LoaderInfo createAndInstallLoader( int id, Bundle args, |
02 |
LoaderManager.LoaderCallbacks<Object> callback) { |
04 |
mCreatingLoader = true ; |
05 |
LoaderInfo info = createLoader(id, args, callback); |
09 |
mCreatingLoader = false ; |
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; |
21 |
void installLoader(LoaderInfo info) { |
22 |
mLoaders.put(info.mId, info); |
createLoader把必要的信息都封装在LoaderInfo类里面,留意以下这一行:
callback.onCreateLoader(id,arg),这里正是我们上面在客户端实现接口LoaderCallback的那个方法.
接着调用installLoader,这个方法把这次Loader的信息put进mLoader这个SparseArray<LoaderInfo>中,这个对象可以理解为一个Map,它的性能比Map要好.
mStarted的值是true,它是在getLoaderManager的时候在Activity中传进来的true值.
好了,下面进入LoaderInfo的start方法了.
04 |
if (!mListenerRegistered) { |
05 |
mLoader.registerListener(mId, this ); |
06 |
mListenerRegistered = true ; |
08 |
mLoader.startLoading(); |
mLoader就是在客户端实现的那个Loader,回到我们刚开始时的例子,它就是一个CursorLoader.
在分析CursorLoader的startLoading之前,我们先看一下这些Loader的类结构先:
从这些类的名称看来,真正实现了异步传输功能的类应该就是AsyncTaskLoader了,事实是不是这样呢?
继续深入下去:
这里的startLoading是调用了Loader类的方法,下文中我会用这样的方法来标识方法是属于哪个类的: 如Loader –> startLoading
02 |
public final void startLoading() { |
10 |
protected void onStartLoading() { |
12 |
deliverResult(mCursor); |
14 |
if (takeContentChanged() || mCursor == null ) { |
20 |
protected void onForceLoad() { |
24 |
if (DEBUG) Slog.v(TAG, "Preparing load: mTask=" + mTask); |
终于看到了LoadTask关键字啦,答案就要揭晓啦.
02 |
final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable { |
03 |
private final CountDownLatch mDone = new CountDownLatch( 1 ); |
22 |
protected void onPostExecute(D data) { |
23 |
if (DEBUG) Slog.v(TAG, this + " onPostExecute" ); |
25 |
AsyncTaskLoader. this .dispatchOnLoadComplete( this , data); |
33 |
protected D onLoadInBackground() { |
34 |
return loadInBackground(); |
38 |
public Cursor loadInBackground() { |
40 |
Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection, |
41 |
mSelectionArgs, mSortOrder, mCancellationSignal); |
45 |
registerContentObserver(cursor, mObserver); |
50 |
mCancellationSignal = null ; |
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) { |
05 |
public void onContentChanged() { |
12 |
mContentChanged = true ; |
对于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的过程.
(会有其他情况,参照 Activity的ConfigChanges属性 )
好了,明白这个之后,我在onStart方法里面找到了线索.
02 |
protected void onStart() { |
03 |
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this ); |
06 |
if (!mLoadersStarted) { |
07 |
mLoadersStarted = true ; |
08 |
if (mLoaderManager != null ) { |
09 |
mLoaderManager.doStart(); |
10 |
} else if (!mCheckedForLoaderManager) { |
11 |
mLoaderManager = getLoaderManager( null , mLoadersStarted, false ); |
13 |
mCheckedForLoaderManager = true ; |
16 |
getApplication().dispatchActivityStarted( this ); |
21 |
if (DEBUG) Log.v(TAG, "Starting in " + this ); |
23 |
RuntimeException e = new RuntimeException( "here" ); |
25 |
Log.w(TAG, "Called doStart when already started: " + this , e); |
33 |
for ( int i = mLoaders.size()- 1 ; i >= 0 ; i--) { |
34 |
mLoaders.valueAt(i).start(); |
留意doStart的For循环,真相大白了..
最后总结一下:
1、异步是通过AsynTaskLoader来实现的。
2、通过观察者模式来实现监控数据的变化.
3、通过Activity生命周期中的onStart来实现自动重连接.
第二个文章
原文: http://blog.sina.com.cn/s/blog_62c5894901014g5x.html
Google倒是提供了一个标准的Loader,即CursorLoader,它是Loader的标准实现,如果你的数据能够用Cursor表示,比如来自SQLiteDatabase的数据就是标准的Cursor,那么这个类对你而言就够用了,具体如何使用CursorLoader,请参看如下两个例子:
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.html
这个例子中牵涉的东西较多,除了我们关注的CursorLoader外,还包括ContentProvider、SQLiteOperHelper、SQLiteDatabase、Fragment、Uri等等常用概念,因此,在仔细阅读了本例后,你将学会如何让这这些类在你的应用中分工协作。
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.html
这个例子要简单些,它教你如何获取联系人,虽然简单,却切中要害。
本文若是只关注这些,就没有意思了,毕竟在很多情况下,我们会感觉CursorLoader不顺眼,于是想写一个属于自己的、更帅的Loader,此时抽象类AsyncTaskLoader就会叫嚣着粉墨登场了,该抽象类定义了你的加载器异步加载数据需要实现的接口,那么如何实现呢?你可以去看下面的例子:
http://developer.android.com/reference/android/content/AsyncTaskLoader.html#loadInBackground()
为了让你以及我自己更好的学习自定义Loader这一伎俩,我把里头的关键代码摘了出来,咱们共同分析一番:
public static class AppListLoaderextends AsyncTaskLoader<List<AppEntry>>{ final InterestingConfigChanges mLastConfig =new InterestingConfigChanges(); final PackageManagermPm; 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 saveglobal application // context returned by getContext(). mPm =getContext().getPackageManager(); } @Override publicList<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 loadtheir 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; } @Override publicvoid deliverResult(List<AppEntry>apps) { if (isReset()) { // Anasync query came in while the loaderis stopped. We // don't need the result. if (apps !=null) { onReleaseResources(apps); } } List<AppEntry> oldApps= apps; mApps = apps; if (isStarted()) { // If the Loader is currently started, we canimmediately // deliver its results. super.deliverResult(apps); } // At this point we can release the resourcesassociated with // 'oldApps' if needed; now that the new result isdelivered we // know that it is no longer in use. if (oldApps !=null) { onReleaseResources(oldApps); } } @Override protectedvoid onStartLoading() { if (mApps !=null) { // If we currently have a result available,deliver it // immediately. deliverResult(mApps); } // Start watching for changes in theappdata. if (mPackageObserver ==null) { mPackageObserver =new PackageIntentReceiver(this); } // Has something interesting in the configurationchanged since we // last built theapp list? boolean configChange =mLastConfig.applyNewConfig(getContext().getResources()); if (takeContentChanged() || mApps==null || configChange) { // If the data has changed since the last time itwas loaded // or is not currently available, start aload. forceLoad(); } } @Override protectedvoid onStopLoading() { // Attempt to cancel the current load task ifpossible. cancelLoad(); } @Override publicvoid onCanceled(List<AppEntry>apps) { super.onCanceled(apps); // At this point we can release the resourcesassociated with 'apps' // if needed. onReleaseResources(apps); } @Override protectedvoid onReset() { super.onReset(); // Ensure the loader is stopped onStopLoading(); // At this point we can release the resourcesassociated with 'apps' // if needed. if (mApps !=null) { onReleaseResources(mApps); mApps = null; } // Stop monitoring for changes. if (mPackageObserver !=null) { getContext().unregisterReceiver(mPackageObserver); mPackageObserver =null; } } protected void onReleaseResources(List<AppEntry>apps) { // For a simpleList<> there is nothing todo. For something // like a Cursor, we would close ithere. } } |
诸位请看loadInBackground方法,这是Loader的核心方法,必须得重载,你要在这里头实现加载数据的功能,看看名字就知道,该方法将在后台运行,这方法没有什么特别说的,自己想加什么样的数据,自己清楚。
再看deliverResult方法,当数据到达客户端后,这个方法将被调用,该方法可以不重载,你可以在其中根据需要实现传递数据的逻辑,请注意例子中对两个状态的判断(注:编程,就得养成if的好习惯),一个是isReset()这个方法用来判断Loader是否已经被重置,如果重置了,那么留着资源也没有啥用了,得把它释放掉;一个是isStarted(),如果Loader被启动那么就把数据传递出去:直接调super的传递方法就OK。
onStartLoading方法,
必须得重载,且别忘记在里头调用forceLoad方法,你也看到,在例子中人家调用了deliverResult方法,并且创建了一个观察者来接收数据,在调用forceLoad时,请注意相应的条件判断