Android异步加载神器Loader全解析
作者:网络转载 发布时间:[ 2015/10/9 13:13:33 ] 推荐标签:移动平台测试 移动测试
首先说一下 这个例子是干嘛的,他主要是监听手机里app list的变化,比如你删除了一个应用安装了一个应用,马上能捕捉到你的手机里app list的变化 并显示在界面,大家都知道 监听app list是通过监听系统广播来完成的。 我主要讲一下 这个官方demo里 是如何在监听到系统广播以后和loader结合起来然后自动回调方法的。
/**
* Helper class to look for interesting changes to the installed apps
* so that the loader can be updated.
*/
public static class PackageIntentReceiver extends BroadcastReceiver {
final AppListLoader mLoader;
//这个构造函数是很重要的 他接收的 是自定义的loader
public PackageIntentReceiver(AppListLoader loader) {
mLoader = loader;
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
mLoader.getContext().registerReceiver(this, filter);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
//在这个地方 直接用loader来注册这个广播接收器
mLoader.getContext().registerReceiver(this, sdFilter);
}
//在收到广播以后 什么事情都没有做,而是调用了loader的onContentChanged方法
@Override public void onReceive(Context context, Intent intent) {
// Tell the loader about the change.
mLoader.onContentChanged();
}
}
你看这里的25-26行 调用了 loader的onContentChanged方法。继续看下面的loader
/**
* A custom Loader that loads all of the installed applications.
*/
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();
}
//实际上重要的是这个方法了,每当这个回调方法被调用的时候 去取applist 然后将结果返回到
//onLoadFinished 这个回调方法里面!
@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;
}
/**
* Called when there is new data to deliver to the client. The
* super class will take care of delivering it; the implementation
* here just adds a little more logic.
*/
@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 = mApps;
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);
}
}
/**
* Handles a request to start the Loader.
*/
@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();
}
}
/**
* Handles a request to stop the Loader.
*/
@Override protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
/**
* Handles a request to cancel a load.
*/
@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);
}
/**
* Handles a request to completely reset the Loader.
*/
@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;
}
}
/**
* Helper function to take care of releasing resources associated
* with an actively loaded data set.
*/
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.
}
}
好,到这里流程很明显了,在loader里 注册广播接收器,当广播接收器 收到广播以后 调用loader的onContentChanged方法,这个方法一调用 AppListLoader里的loadInBackGround会被调用,然后当loadInBackGround执行完毕以后 会把结果传递给onLoadFinished方法了。 搞清楚这个流程 你真正学会了使用loader这个大杀器了。当然了,我们并不满足于此,loader还有一个特性是可以自动管理他自己的生命周期 等等。我们现在去看看他的源码,是如何完成这一点的。 并且上述几个方法之间是如何相互调用的,顺序如何。
首先 我们要搞清楚几个类之间的关系:
public class CursorLoader extends AsyncTaskLoader<Cursor> {
public abstract class AsyncTaskLoader<D> extends Loader<D> {
public class Loader<D> {
这样很清晰。首先由一个实体类作为基础的基类,Loader 注意他可以接受一个泛型为参数,然后有一个抽象类:AsyncTaskLoader 也是泛型作为参数。
后实际调用运作的类是CursorLoader类了,这里可以看出来 传进去的泛型是一个Cursor。你在自定义Loader的时候,这个泛型参数 当然是可以自己决定的,
比如官方demo里 传的是一个List。
搞清楚 他们三者之间的关系,剩下的简单多了。可以逐步分析了。
在前面的3个demo里,我们分别演示了在fragment和activity里 调用loader的方法。 那我们看看 这两者之间有什么异同点。先来看fragment。
fragment里 我们是这样调用的:
//这个地方初始化了我们的loader
getLoaderManager().initLoader(0, null, this);
直接get了一个manager 然后init他。我们进去看fragment的源码:
//这边能看出来一个fragment只能有一个loadermanager了。
public LoaderManager getLoaderManager() {
if (mLoaderManager != null) {
return mLoaderManager;
}
//mHost很好理解 是fragment的宿主,也是跟fragment 相关联的activity。
if (mHost == null) {
throw new IllegalStateException("Fragment " + this + " not attached to Activity");
}
mCheckedForLoaderManager = true;
mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, true);
return mLoaderManager;
}
既然 我们知道 fragment的getLoaderManager也是通过activity的getLoader去调用的,那我们去activity里的源码看看 :
//在activty中终实际上调用的是他了 是这个方法
LoaderManagerImpl getLoaderManagerImpl() {
if (mLoaderManager != null) {
return mLoaderManager;
}
mCheckedForLoaderManager = true;
mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true /*create*/);
return mLoaderManager;
}
//这个地方能看到 主要的第一个参数 who,你到这能发现 如果是activity自己调用的话,传进去的who的值是root
//也是说一个actvity 只能有一个loadermanger 但是我们可以发现在fragment里 传进去的值是下面这个:
// Internal unique name for this fragment;
//String mWho;
//也是说每一个fragment的mWho的值都是的,而在activty中,是维护了一个map,一个key 对应一个loadermanager
//key是fragment的那个的标示,或者是activity自己,activity自己的标示是(root)了
LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {
if (mAllLoaderManagers == null) {
mAllLoaderManagers = new ArrayMap<String, LoaderManager>();
}
LoaderManagerImpl lm = (LoaderManagerImpl) mAllLoaderManagers.get(who);
if (lm == null) {
if (create) {
lm = new LoaderManagerImpl(who, this, started);
mAllLoaderManagers.put(who, lm);
}
} else {
lm.updateHostController(this);
}
return lm;
}
好 一直到这里 ,我们可以下一个结论了,真正的loadermanager都是存储在activity中的,包括fragment的loadermanager也是,通过一个map来保证 get的时候取的manager是自己对应的,并且全局。继续往下看:
public abstract class LoaderManager {
/**
* Callback interface for a client to interact with the manager.
*/
public interface LoaderCallbacks<D> {
/**
* Instantiate and return a new Loader for the given ID.
*
* @param id The ID whose loader is to be created.
* @param args Any arguments supplied by the caller.
* @return Return a new Loader instance that is ready to start loading.
*/
public Loader<D> onCreateLoader(int id, Bundle args);
/**
* Called when a previously created loader has finished its load. Note
* that normally an application is <em>not</em> allowed to commit fragment
* transactions while in this call, since it can happen after an
* activity's state is saved. See {@link FragmentManager#beginTransaction()
* FragmentManager.openTransaction()} for further discussion on this.
*
* <p>This function is guaranteed to be called prior to the release of
* the last data that was supplied for this Loader. At this point
* you should remove all use of the old data (since it will be released
* soon), but should not do your own release of the data since its Loader
* owns it and will take care of that. The Loader will take care of
* management of its data so you don't have to. In particular:
*
* <ul>
* <li> <p>The Loader will monitor for changes to the data, and report
* them to you through new calls here. You should not monitor the
* data yourself. For example, if the data is a {@link android.database.Cursor}
* and you place it in a {@link android.widget.CursorAdapter}, use
* the {@link android.widget.CursorAdapter#CursorAdapter(android.content.Context,
* android.database.Cursor, int)} constructor <em>without</em> passing
* in either {@link android.widget.CursorAdapter#FLAG_AUTO_REQUERY}
* or {@link android.widget.CursorAdapter#FLAG_REGISTER_CONTENT_OBSERVER}
* (that is, use 0 for the flags argument). This prevents the CursorAdapter
* from doing its own observing of the Cursor, which is not needed since
* when a change happens you will get a new Cursor throw another call
* here.
* <li> The Loader will release the data once it knows the application
* is no longer using it. For example, if the data is
* a {@link android.database.Cursor} from a {@link android.content.CursorLoader},
* you should not call close() on it yourself. If the Cursor is being placed in a
* {@link android.widget.CursorAdapter}, you should use the
* {@link android.widget.CursorAdapter#swapCursor(android.database.Cursor)}
* method so that the old Cursor is not closed.
* </ul>
*
* @param loader The Loader that has finished.
* @param data The data generated by the Loader.
*/
public void onLoadFinished(Loader<D> loader, D data);
/**
* Called when a previously created loader is being reset, and thus
* making its data unavailable. The application should at this point
* remove any references it has to the Loader's data.
*
* @param loader The Loader that is being reset.
*/
public void onLoaderReset(Loader<D> loader);
}
一看知道 loadermanger 其实是一个抽象类。是定义了一些 我们需要的接口而已,这些接口方法的含义和用法 在那3个demo里 相信大家都有了解,不多说。
我们去看看这个抽象类的实现类,为什么要看他,因为你在get到这个maganger以后 马上去调用了他的init方法 我们看看这部分的逻辑是怎么样的:
public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
if (mCreatingLoader) {
throw new IllegalStateException("Called while creating a loader");
}
//这个是先看看是否有活动的loader 有的话取出来 没有的话 创建一个
LoaderInfo info = mLoaders.get(id);
if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);
if (info == null) {
// Loader doesn't already exist; create.
info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
if (DEBUG) Log.v(TAG, " Created new loader " + info);
} else {
if (DEBUG) Log.v(TAG, " Re-using existing loader " + info);
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}
if (info.mHaveData && mStarted) {
// If the loader has already generated its data, report it now.
info.callOnLoadFinished(info.mLoader, info.mData);
}
return (Loader<D>)info.mLoader;
}
//这个是现在存活的loader
final SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>(0);
//这个是已经运行结束的loader
final SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>(0);
//其实这个创建loader的过程特别简单,我们主要看第三个参数,callback 这个参数
//一想明白,在前面3个demo里我们是直接在fragemet和activity里实现的callback
//所以传进去的是this,也是说 回调是在这个函数里 真正的和loader 发生了关联了
private LoaderInfo createAndInstallLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks<Object> callback) {
try {
mCreatingLoader = true;
LoaderInfo info = createLoader(id, args, callback);
installLoader(info);
return info;
} finally {
mCreatingLoader = false;
}
}
你看 一直到这里,我们明白了 callback是怎么和loadermageer本身发生关联的。 我们继续往下看。这次我们要搞明白当数据源发生变化的时候 是怎么一步步回调我们子类loader的方法的。
我们先看Loader这个基类的主要方法:
//这个是一个观察者 当发生变化的时候 他调用了onContentChanged方法
public final class ForceLoadContentObserver extends ContentObserver {
public ForceLoadContentObserver() {
super(new Handler());
}
@Override
public boolean deliverSelfNotifications() {
return true;
}
@Override
public void onChange(boolean selfChange) {
onContentChanged();
}
}
//下面这2个方法一看明白 终当数据源发生变化的时候 会通知这个观察者,然后这个观察者会终调用
//onForceLoad这个方法 而onForceLoad是交给子类去实现的 也是AsyncTaskLoader的onForceLoad方法了
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系SPASVO小编(021-61079698-8054),我们将立即处理,马上删除。
相关推荐
Android自动化测试框架有哪些?有什么用途?Android测试中最容易忽略的测试点有哪些?Android 手机自动化测试工具有哪几种?移动APP测试之android性能测试快速提升Android App 的代码覆盖率Android Unit Test 框架比较Android单元测试框架Robolectric3.0介绍(一)Android单元测试的整理在Android Studio中实现单元测试Android连接MySQL方法,测试成功关于Android MVP模式的思考Android 数据库管理?ActiveAndroid编写Android测试单元该做的和不该做的事Android阿里面试Java基础锦集在Android项目中使用Java8Java / Android 面试中所遇到的那些坑
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11热门文章
常见的移动App Bug??崩溃的测试用例设计如何用Jmeter做压力测试QC使用说明APP压力测试入门教程移动app测试中的主要问题jenkins+testng+ant+webdriver持续集成测试使用JMeter进行HTTP负载测试Selenium 2.0 WebDriver 使用指南