一.    写在前面的话

在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软件的名称从中文改成英文)