浅谈Android数据加载 —— Loader

来源:互联网 发布:spring cloud 打印sql 编辑:程序博客网 时间:2024/05/29 03:12

一.    写在前面的话

        在Android3.0之前很多的应用都会遇到UI主线程阻塞的问题,也就是常说的ANR(Application Not Responding),其中占据最大就是数据加载这块。其实至今每个android程序员都意识到这个问题的存在,都会想各种方法去避开数据加载带来的体验问题(最典型的就是:manage cursors。但是都会存在很多的缺陷,比 1.Activity参数发生变化就需要重新加载数据,2.数据同步问题等。当然也有很多的开源框架中也可以处理这些问题,但是我个人认为Android提供的Loader机制绝对是首选。 Loader的出现以一种更友好的方式改善了用户体验。主要优点概括:

1.    与Activity和Fragment完美融合;

2.    异步加载数据;

3.    监听数据变化(同步数据);

4.    自动重新连接,兼容配置改变;

参考文献:Android API 


二.     LoaderManager


        LoaderManager主要功能:1.管理一个或者多个Loader,2.关联Activity和Fragment。       

        简单的说:LoaderManger就是管理Loaders的,同时LoaderManger受到Activity或者Fragment的维护。每一个Activity或者Fragment拥有一个唯一的LoaderManager,负责starting, stopping, retaining, restarting, and destroying Loaders, 通过这个三个方法 initLoader(),restartLoader(), or destroyLoader()。然而,Activity或者Fragment的生命周期方法会管理LoaderManger,例如:Activity destoryed.此时需要LoaderManger 关闭或者destory所有关联的资源(释放资源),eg: Cursor。

 public class SampleActivity extends Activity implements LoaderManager.LoaderCallbacks<D>{//返回 a new Loader的工厂方法,当第一次创建Loader的时候,LoaderManager自动调用此方法  public Loader<D> onCreateLoader(int id, Bundle args) { ... }//当数据加载完成的时候,此方法会自动调用,该方法的主要作用就是更新UI public void onLoadFinished(Loader<D> loader, D data) { ... }//当Loader的数据被重置的时候调用,主要作用就是移除旧数据的引用。 public void onLoaderReset(Loader<D> loader) { ... }}

三.    Loader


Loader的理解(Loader机制最关键的地方):

Loader的主要功能 :1. 异步加载数据,2. 监听数据源的变化,3. 传递新数据给LoaderManager
Loader的3大特性:

1.     封装数据加载:Activity/Fragment他们不需要知道怎么去加载数据,而是把这个任务委托给了Loader,Loader在幕后完成数据的加载,然后传递给UI,最重要就是我们不需要担心后台关于线程的问题,Loader的封装会自动管理线程问题, Android API 提供了 AsyncTaskLoader<D>这个类,看到这个名字我们大概就明白了,基本的数据加载都在loadInBackground()里面。

2.     监听数据完成:对于每一个Loader,LoaderManager都会注册一个OnLoadCompleteListener<D>去指引Loader传递数据,其实这些都是封装好了的,我们看到的结果直接在LoaderManager中的onLoadFinished(Loader loader, D result)提现。

3.    监听数据源变化:Loader一般实现监听eg:ContentObserver,BroadcaseReceiver,去监听数据变化,单数据发生变化时,就会执行 Loader中的onContentChanged().此时Loader的状态就会改变重新去加载数据。

4.    Loader的状态:

1.started    工作状态Loader在任何时候都会传递数据同时也会监听数据源的变化,直到Loader变成stopped或者reset状态,也就说Loader处于工作状态的时候onLoadFinished(在Activity/Fragment中的方法)会被执行

2.stopped    停止状态Loader还会处于监听数据源的变化,但是不会传递数据,也就是onLoadFinished是没有数据输出的。当Loader处于stopped状态的时候既可以转换成started或者reset状态。

3.reset重置状态Loader既不监听数据也不传递数据。当我们将loader处于reset状态的时候,需要将释放资源(一般在onLoaderReset里面将references释放掉),一般情况下reset状态是不可逆的。


Loader工作过程及使用:    

使用Loader还是需要记住一些流程的:
1.    继承AsyncTaskLoader, 重写 onStartLoading(), onStopLoading(), onReset(), onCanceled(), and deliverResult(D results)。
                这些方法的都是受到LoaderManager根据Activity/Fragment的状态按照规律调用的。这也是之前说的为什么LoaderManager管理Loader的原因。 例如:当Activity第一次启动,Activity就会驱动LoaderManager去启动Loaders(一般在Activity的onStart()方法中),LoaderManager会去call startLoading() -- 也就是Loader的onStartLoading方法。

public class AppListLoader extends AsyncTaskLoader<List<AppEntry>>{private static final String TAG = "AppListLoader";public final PackageManager mPm;// 存放加载的数据referenceprivate List<AppEntry> mApps;public AppListLoader(Context ctx){// 注意:不要保存ctx对象的索引(一般我们这么使用:this.context =// ctx),这种操作会倒是泄漏的。如果需要上下文索引可以调用getContext()// 主要原因就是这个loader可能被不同的Activity使用super(ctx);mPm = getContext().getPackageManager();}/****************************************************//************** (1) 异步加载数据 *************************//****************************************************/// 后台加载安装的application数据@Overridepublic List<AppEntry> loadInBackground(){LogUtil.i(TAG, "Loader inBackground");// 方法主要本体:加载数据List<ApplicationInfo> apps = mPm.getInstalledApplications(0);if (apps == null){apps = new ArrayList<ApplicationInfo>();}// 加载LabelsList<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(getContext());entries.add(entry);}// 工具:排序Collections.sort(entries, ALPHA_COMPARATOR);return entries;}/*******************************************//*********** (2) 传递数据 **********************//*******************************************/// 传递数据(superclass会完成数据的传送)给onLoadFinished@Overridepublic void deliverResult(List<AppEntry> apps){if (isReset()){LogUtil.i(TAG, "Loader Reset ");// Loader被reset之后,需要释放资源// 这种情况一般出现在正在异步加载数据的时候,Loader被reset了。这样加载过来的数据本来是需要传递出去的,但是Loader就在此时被reset状态了// Loader就不需要传递数据了if (apps != null){// 释放资源releaseResources(apps);return;}}List<AppEntry> oldApps = mApps;mApps = apps;if (isStarted()){LogUtil.i(TAG, "Loader 在started状态传递数据");// Loader处于started状态,数据正常传递(superclass可以完成这个任务)super.deliverResult(apps);}// 释放不需要的旧数据if (oldApps != null && oldApps != apps){LogUtil.i(TAG, "Loader 释放就的数据");releaseResources(oldApps);}}/*********************************************************//************* (3) Loader的三种状态 ***************************//*********************************************************/@Overrideprotected void onStartLoading(){LogUtil.i(TAG, "Loader onStartLoading");if (mApps != null){LogUtil.i(TAG, "Loader 在onStartLoading状态即使传递数据");deliverResult(mApps);}// 注册监听器监测数据源的变化if (mAppsObserver == null){mAppsObserver = new InstalledAppsObserver(this);}if (mLocaleObserver == null){mLocaleObserver = new SystemLocaleObserver(this);}if (takeContentChanged()){LogUtil.i(TAG, "数据源的数据发生了变化需要强制加载新的数据");// 当检测器发现数据源的数据发生了变化,促发Loader重新获取新的数据forceLoad();} else if (mApps == null){// 如果数据是空的时候,强制Loader重新加载数据LogUtil.i(TAG, "Loader当前的数据为空null,强制重新加载数据");forceLoad();}}@Overrideprotected void onStopLoading(){LogUtil.i(TAG, "Loader onStopLoading");// Loader处于stop状态的时候,需要关闭Loader的加载任务cancelLoad();// 注意:stop状态下检测器还是会去检测数据源的变化的,这样如果数据源发生变化的时候系统还是强制加载数据的}@Overrideprotected void onReset(){LogUtil.i(TAG, "Loader onReset");// 确保Loader处于stopped状态onStopLoading();// Loader处于reset状态的时候既不需要加载数据也不需要监听数据// 释放资源if (mApps != null){releaseResources(mApps);mApps = null;}// 关闭监听if (mAppsObserver != null){getContext().unregisterReceiver(mAppsObserver);mAppsObserver = null;}if (mLocaleObserver != null){getContext().unregisterReceiver(mLocaleObserver);mLocaleObserver = null;}}@Overridepublic void onCanceled(List<AppEntry> apps){LogUtil.i(TAG, "Loader onCanceled");// 关闭当前的asynchronous加载super.onCanceled(apps);// Load被关闭了,需要释放所有关联的资源releaseResources(apps);}@Overridepublic void forceLoad(){LogUtil.i(TAG, "Loader forceLoad");super.forceLoad();}/** * @Title: releaseResources * @Description: TODO(释放资源) * @param @param apps 设定文件 * @return void 返回类型 * @throws */private void releaseResources(List<AppEntry> apps){// 在这个demo里面,释放资源没有什么内容,其实主要针对的释放资源就是数据库之类的cursor。// 在这个方法里面做的事情就是释放所有关联Loader的referenc}/*********************************************************************//************************ (4) 监听数据源 **********************************//*********************************************************************/// 监听apk的安装,卸载,更新private InstalledAppsObserver mAppsObserver;// 监听系统Locale的变化(这里主要是系统语言发生变化)private SystemLocaleObserver mLocaleObserver;/** * @Fields ALPHA_COMPARATOR : TODO(主要是对application的排序) */private static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>(){Collator sCollator = Collator.getInstance();@Overridepublic int compare(AppEntry object1, AppEntry object2){return sCollator.compare(object1.getLabel(), object2.getLabel());}};}

五.    Demo


        这个例子主要功能是加载手机里面安装的程序。通过Loader在ListView中显示出来。完全基于上面所讲的内容写的demo,可能大家对于上面所讲的内容过于晦涩难懂(有时候说是说不清的,一看代码就明白了,呵呵),我会在代码里面添加注释同时添加log日志在里面,方便大家理解和分析,希望此Demo对大家以后使用Loader会有一些帮助。

1.     AppListLoader     (AsyncTaskLoader的子类)后台获取数据

2.    注册了2个系统范围内的BroadcastReceiver 用于监听数据源分别是

1). InstalledAppsObserver ——安装,卸载,更新apk的监听,

2). SystemLocaleObserver——用于对系统本地的监听(可能大家不是很理解,举个例子:如果用户改变了操作系统的语言:把中文改成了英文,这样数据源的数据也是发生了变化,同样也需要Loader加载的apk软件的名称从中文改成英文)


github源码


0 0
原创粉丝点击