转载 http://blog.sina.com.cn/s/blog_62c5894901014g5x.html
在Android3.0中,Google引入了一种数据异步加载机制,该机制的核心,便是LoaderManager、Loader,顾名思义,LoaderManager是Loader的管理者,而Loader便是数据加载器,你可以根据自己的需要实现形形色色的数据加载器。
Google强烈建议在加载数据时,使用LoaderManager及其相关的机制。
每个Activity和Fragment中,都会有且只有一个LoaderManager,而LoaderManager中可以有多个Loader,也就是说,在一个Activity或者Fragment中,你可以同时异步加载N则不同的数据,具体加多少则,要看你那一亩三分地(Activity和Fragment就是你的地)有多大产。
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 AppListLoader extends AsyncTaskLoader<List<AppEntry>> { final InterestingConfigChanges mLastConfig = new InterestingConfigChanges(); final PackageManager mPm;
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 save global application // context returned by getContext(). mPm = getContext().getPackageManager(); }
@Override public List<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 load their 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 public void deliverResult(List<AppEntry> apps) { if (isReset()) { // An async query came in while the loader is 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 can immediately // deliver its results. super.deliverResult(apps); }
// At this point we can release the resources associated with // 'oldApps' if needed; now that the new result is delivered we // know that it is no longer in use. if (oldApps != null) { onReleaseResources(oldApps); } }
@Override protected void onStartLoading() { if (mApps != null) { // If we currently have a result available, deliver it // immediately. deliverResult(mApps); }
// Start watching for changes in the app data. if (mPackageObserver == null) { mPackageObserver = new PackageIntentReceiver(this); }
// Has something interesting in the configuration changed since we // last built the app list? boolean configChange = mLastConfig.applyNewConfig(getContext().getResources());
if (takeContentChanged() || mApps == null || configChange) { // If the data has changed since the last time it was loaded // or is not currently available, start a load. forceLoad(); } }
@Override protected void onStopLoading() { // Attempt to cancel the current load task if possible. cancelLoad(); }
@Override public void onCanceled(List<AppEntry> apps) { super.onCanceled(apps);
// At this point we can release the resources associated with 'apps' // if needed. onReleaseResources(apps); }
@Override protected void onReset() { super.onReset();
// Ensure the loader is stopped onStopLoading();
// At this point we can release the resources associated 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 simple List<> there is nothing to do. For something // like a Cursor, we would close it here. } } |
诸位请看loadInBackground方法,这是Loader的核心方法,必须得重载,你要在这里头实现加载数据的功能,看看名字就知道,该方法将在后台运行,这方法没有什么特别说的,自己想加什么样的数据,自己清楚。
再看deliverResult方法,当数据到达客户端后,这个方法将被调用,该方法可以不重载,你可以在其中根据需要实现传递数据的逻辑,请注意例子中对两个状态的判断(注:编程,就得养成if的好习惯),一个是isReset()这个方法用来判断Loader是否已经被重置,如果重置了,那么留着资源也没有啥用了,得把它释放掉;一个是isStarted(),如果Loader被启动那么就把数据传递出去:直接调super的传递方法就OK。
onStartLoading方法,必须得重载,且别忘记在里头调用forceLoad方法,你也看到,在例子中人家调用了deliverResult方法,并且创建了一个观察者来接收数据,在调用forceLoad时,请注意相应的条件判断,关于为什么要调用forceLoad方法,网上有下面一段话:
onStartLoading() is called in two places: 1. LoaderManagerImpl.doStart()->LoaderInfo.start(), which will happen when the hosting fragment isstarted. 2. LoaderManagerImpl.installLoader(LoaderInfo)->if the hosting fragment is started, callLoaderInfo.start().
So basically, your custom class extending AsyncTaskLoader will have to override onStartLoading(),and call forceLoad() within when necessary.
Loader::startLoading() will be called when the hosting fragment is started, and youronStartLoading() will be called as consequence. |
以我目前的水平,并不能确切明白这段话的含义是什么,但是,我翻了一下Android SDK的源代码,发现Loader及AsyncTaskLoader中,startLoading方法其实屁都没做,所以,万事求己不如求人,还是老老实实去实现onStartLoading方法吧,不过Google这么做有些坑爹的嫌疑,所以很多人在那里问,哎呀,我的loadInBackground怎么老不执行啊?
onStopLoading、onCancel方法都没有什么好说的,倒是onReset方法的实现值得关注,诸位看看便知,至于onReleaseResouces方法,就是给你一次释放资源的机会,如果你用的是Cursor之类的东西,请在这里头close吧。
我刚刚开始研究Android SDK,也许是从.NET猛地转到JAVA的缘故,很多架构 方面的东西都不是很习惯。Google Android SDK的架构简单、灵活而又强大,这是值得称道的,但是也有其不甚完善的地方。就拿LoaderManager来说,知道Android 3.0才引入,Google开发文档说,使用这东西,能够避免在加载数据时界面死在那里。于是,我就相当然地认为,Google不是神,它的产品设计中,也会存在哪些低级的毗漏与缺陷。
但是,无论如何Google Android SDK以及iOS SDK,都非常值得我们去研究一番,即便你不编程,不写代码,但是里头的思想,确是一笔财富。