原文: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时自动重连接

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

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


001 /**
002 * Demonstration of the use of a CursorLoader to load and display contacts
003 * data in a fragment.
004 */
005 public classLoaderCursorextends Activity {
006
007 @Override
008 protectedvoidonCreate(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 =newCursorLoaderListFragment();
016 fm.beginTransaction().add(android.R.id.content, list).commit();
017 }
018 }
019
020
021 publicstaticclass CursorLoaderListFragment extends ListFragment
022 implementsLoaderManager.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 @Overridepublicvoid onActivityCreated(Bundle savedInstanceState) {
031
032 mAdapter =newSimpleCursorAdapter(getActivity(),
033 android.R.layout.simple_list_item_2,null,
034 newString[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
035 newint[] { android.R.id.text1, android.R.id.text2 },0);
036 setListAdapter(mAdapter);
037
038 getLoaderManager().initLoader(0,null,this);
039 }
040
041
042 @Overridepublicvoid onListItemClick(ListView l, View v,int position, longid) {
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 staticfinalString[] CONTACTS_SUMMARY_PROJECTION =newString[] {
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 publicLoader<Cursor> onCreateLoader(intid, 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 returnnewCursorLoader(getActivity(), baseUri,
076 CONTACTS_SUMMARY_PROJECTION, select,null,
077 Contacts.DISPLAY_NAME +" COLLATE LOCALIZED ASC");
078 }
079
080 publicvoidonLoadFinished(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 publicvoidonLoaderReset(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(intid, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
02 if(mCreatingLoader) {
03 thrownewIllegalStateException("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(intid, Bundle args,
02 LoaderManager.LoaderCallbacks<Object> callback) {
03 try{
04 mCreatingLoader =true;
05 LoaderInfo info = createLoader(id, args, callback);
06 installLoader(info);
07 returninfo;
08 } finally {
09 mCreatingLoader =false;
10 }
11 }
12
13 privateLoaderInfo createLoader(intid, Bundle args,
14 LoaderManager.LoaderCallbacks<Object> callback) {
15 LoaderInfo info =newLoaderInfo(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
16 Loader<Object> loader = callback.onCreateLoader(id, args);
17 info.mLoader = (Loader<Object>)loader;
18 returninfo;
19 }
20
21 voidinstallLoader(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这个SparseArray<LoaderInfo>中,这个对象可以理解为一个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 publicfinalvoid startLoading() {
03 mStarted =true;
04 mReset =false;
05 mAbandoned =false;
06 onStartLoading();
07 }
08
09 CursorLoader:
10 protectedvoidonStartLoading() {
11 if(mCursor !=null) {
12 deliverResult(mCursor);
13 }
14 if(takeContentChanged() || mCursor ==null) {
15 forceLoad();
16 }
17 }
18
19 AsynTaskLoader:
20 protectedvoidonForceLoad() {
21 super.onForceLoad();
22 cancelLoad();
23 mTask =newLoadTask();
24 if(DEBUG) Slog.v(TAG,"Preparing load: mTask="+ mTask);
25 executePendingTask();
26 }

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


01 AsyncTaskLoader:
02 final classLoadTaskextends AsyncTask<Void, Void, D>implements Runnable {
03 privatefinalCountDownLatch mDone = newCountDownLatch(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 booleanwaiting;
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 protectedvoidonPostExecute(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 returnloadInBackground();
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 returncursor;
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 voidonChange(booleanselfChange) {
02 onContentChanged();
03 }
04
05 public voidonContentChanged() {
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的过程.

(会有其他情况,参照 Activity的ConfigChanges属性 )

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


01 Activity:
02 protectedvoidonStart() {
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 }elseif (!mCheckedForLoaderManager) {
11 mLoaderManager = getLoaderManager(null, mLoadersStarted,false);
12 }
13 mCheckedForLoaderManager =true;
14 }
15
16 getApplication().dispatchActivityStarted(this);
17 }
18
19 LoaderManagerImp:
20 voiddoStart() {
21 if(DEBUG) Log.v(TAG,"Starting in "+ this);
22 if(mStarted) {
23 RuntimeException e =newRuntimeException("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(inti = mLoaders.size()-1; i >=0; i--) {
34 mLoaders.valueAt(i).start();
35 }
36 }


留意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时,请注意相应的条件判断


更多相关文章

  1. Android(安卓)动态布局
  2. AIDL
  3. Activity的启动流程
  4. Android面试-基础知识
  5. Android(安卓)数据绑定(Data Binding)详解
  6. android 读写文件数据
  7. Android存储使用参考
  8. Android与设计模式(2)观察者/备忘录/模板/中介者
  9. Viewpager Indicator中PagerSlidngTab用法(与ViewPager一起使用)

随机推荐

  1. 复选框
  2. Android设置竖屏
  3. RelativeLayout用到的一些重要的属性
  4. 使用Android(安卓)studio分析内存泄露
  5. [Android]Common Sreen Size of Android
  6. Android(安卓)VideoView播放视频
  7. java.net.SocketTimeoutException: conne
  8. android 设置边框圆角
  9. Android(安卓)相关
  10. Android(安卓)自定义进度条