Android Loader详解

来源:互联网 发布:怎么自学java 编辑:程序博客网 时间:2024/04/30 06:53

原文: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 */
005public classLoaderCursorextendsActivity {
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    publicstaticclassCursorLoaderListFragment extendsListFragment
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        @OverridepublicvoidonActivityCreated(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        @OverridepublicvoidonListItemClick(ListView l, View v,intposition, 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方法了.


01public <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.
01private 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方法了.


01void 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


01Loader:
02    publicfinalvoidstartLoading() {
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关键字啦,答案就要揭晓啦.


01AsyncTaskLoader:
02final classLoadTaskextendsAsyncTask<Void, Void, D>implementsRunnable {
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     
32AsyncTaskLoader:   
33protected D onLoadInBackground() {
34        returnloadInBackground();
35}  
36 
37CursorLoader:
38public 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方法


01public voidonChange(booleanselfChange) {
02            onContentChanged();
03}
04 
05public 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方法里面找到了线索.


01Activity:
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 =newInterestingConfigChanges();

   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=newArrayList<AppEntry>(apps.size());

       for (int i=0;i<apps.size(); i++) {

           AppEntry entry =newAppEntry(this, apps.get(i));

           entry.loadLabel(context);

           entries.add(entry);

       }

 

       // Sort the list.

       Collections.sort(entries,ALPHA_COMPARATOR);

 

       // Done!

       return entries;

   }

 

   

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


0 0
原创粉丝点击