一. 写在前面的话
在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. 传递新数据给LoaderManagerLoader的3大特性:
1. 封装数据加载:Activity/Fragment他们不需要知道怎么去加载数据,而是把这个任务委托给了Loader,Loader在幕后完成数据的加载,然后传递给UI,最重要就是我们不需要担心后台关于线程的问题,Loader的封装会自动管理线程问题, Android API 提供了 AsyncTaskLoader这个类,看到这个名字我们大概就明白了,基本的数据加载都在loadInBackground()里面。
2. 监听数据完成:对于每一个Loader,LoaderManager都会注册一个OnLoadCompleteListener去指引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;
// 存放加载的数据reference
private List<AppEntry> mApps;
public AppListLoader(Context ctx)
{
// 注意:不要保存ctx对象的索引(一般我们这么使用:this.context =
// ctx),这种操作会倒是泄漏的。如果需要上下文索引可以调用getContext()
// 主要原因就是这个loader可能被不同的Activity使用
super(ctx);
mPm = getContext().getPackageManager();
}
/****************************************************/
/************** (1) 异步加载数据 *************************/
/****************************************************/
// 后台加载安装的application数据
@Override
public List<AppEntry> loadInBackground()
{
LogUtil.i(TAG, "Loader inBackground");
// 方法主要本体:加载数据
List<ApplicationInfo> apps = mPm.getInstalledApplications(0);
if (apps == null)
{
apps = new ArrayList<ApplicationInfo>();
}
// 加载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(getContext());
entries.add(entry);
}
// 工具:排序
Collections.sort(entries, ALPHA_COMPARATOR);
return entries;
}
/*******************************************/
/*********** (2) 传递数据 **********************/
/*******************************************/
// 传递数据(superclass会完成数据的传送)给onLoadFinished
@Override
public 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的三种状态 ***************************/
/*********************************************************/
@Override
protected 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();
}
}
@Override
protected void onStopLoading()
{
LogUtil.i(TAG, "Loader onStopLoading");
// Loader处于stop状态的时候,需要关闭Loader的加载任务
cancelLoad();
// 注意:stop状态下检测器还是会去检测数据源的变化的,这样如果数据源发生变化的时候系统还是强制加载数据的
}
@Override
protected 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;
}
}
@Override
public void onCanceled(List<AppEntry> apps)
{
LogUtil.i(TAG, "Loader onCanceled");
// 关闭当前的asynchronous加载
super.onCanceled(apps);
// Load被关闭了,需要释放所有关联的资源
releaseResources(apps);
}
@Override
public 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();
@Override
public 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软件的名称从中文改成英文)