Android Loader详解

原文: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相关的类结构,省略了很多东西,但不影响我们的分析.

Android Loader详解_第1张图片

现在我们来到了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的类结构先:

Android Loader详解_第2张图片

从这些类的名称看来,真正实现了异步传输功能的类应该就是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时,请注意相应的条件判断


你可能感兴趣的:(android,loader)