一、简介
很多时候,我们可能想在Activity或Fragment中加载数据,例如使用ContentProvider获取数据库数据。显然,类似ContenProvider从数据库中读取数据多数是耗时行为,不能在主线程中完成加载因为这会阻塞主线程。Android 3.0后,提供了Loader加载器,可以轻松的在Activity或Fragment中异步加载数据。Loader具有以下特性:
可用于每个 Activity 和 Fragment。
支持异步加载数据。
监控其数据源并在内容变化时传递新结果。
在某一配置更改后重建加载器时,会自动重新连接上一个加载器的游标。 因此,它们无需重新查询其数据。
此外,Loader和Activity或Fragment的生命周期绑定,Activity或Fragment管理LoaderManager,而LoaderManager又管理着Loader。在它们生命周期结束时(onDestroy),它们会把Loader也Destroy。并且Loader不能作为内部类的对象初始化,因为内部类会持有外部Activity/Fragment的引用,造成内存泄漏。
二、使用
常用的Loader API有:
Loader API 摘要
在应用中使用加载器时,可能会涉及到多个类和接口。 下表汇总了这些类和接口:
LoaderManager
|
一种与 Activity 或 Fragment 相关联的的抽象类,用于管理一个或多个 Loader 实例。 这有助于应用管理与Activity 或 Fragment 生命周期相关联的、运行时间较长的操作。它最常见的用法是与 CursorLoader 一起使用,但应用可自由写入其自己的加载器,用于加载其他类型的数据。
每个 Activity 或片段中只有一个 LoaderManager。但一个 LoaderManager 可以有多个加载器。
|
|
LoaderManager.LoaderCallbacks
|
一种回调接口,用于客户端与 LoaderManager 进行交互。例如,您可使用 onCreateLoader() 回调方法创建新的加载器。
|
|
Loader
|
一种执行异步数据加载的抽象类。这是加载器的基类。 您通常会使用 CursorLoader,但您也可以实现自己的子类。加载器处于活动状态时,应监控其数据源并在内容变化时传递新结果。
|
|
AsyncTaskLoader
|
提供 AsyncTask 来执行工作的抽象加载器。
|
|
CursorLoader
|
AsyncTaskLoader 的子类,它将查询 ContentResolver 并返回一个 Cursor。此类采用标准方式为查询游标实现 Loader 协议。它是以 AsyncTaskLoader 为基础而构建,在后台线程中执行游标查询,以免阻塞应用的 UI。使用此加载器是从 ContentProvider 异步加载数据的最佳方式,而不用通过片段或 Activity 的 API 来执行托管查询。
|
LoaderManager.LoaderCallbacks中通常由用户实现,onCreateLoader方法返回想要使用的Loader实例,onLoadFinished为Loader加载数据完成后的回调函数,onLoaderReset为Loader重置时的初始化函数。
以下是一个使用Loader异步读取手机中截图图片路径的例子:
回调类PictureLoaderCallback.java:
package com.meituan.huangdanyang.practise;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
public class PictureLoaderCallback implements LoaderManager.LoaderCallbacks {
private Context context;
private OnLoadFinishListener onLoadFinishListener;
private List res;
private Long last;
public PictureLoaderCallback(Context context) {
this.context = context;
}
public PictureLoaderCallback(Context context, OnLoadFinishListener onLoadFinishListener) {
this.context = context;
this.onLoadFinishListener = onLoadFinishListener;
}
@Override
public Loader onCreateLoader(int id, Bundle args) {
Log.i("开始时间",System.currentTimeMillis() + "");
last = System.currentTimeMillis();
return new CursorLoader(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI,null,
MediaStore.Images.Media.MIME_TYPE + "=? or "
+ MediaStore.Images.Media.MIME_TYPE + "=?",
new String[] {"image/jpeg", "image/png"},
MediaStore.Images.Media.DATE_MODIFIED
);
}
@Override
public void onLoadFinished(@NonNull Loader loader, Cursor data) {
if (data == null) {
return;
}
Log.i("返回时间",(System.currentTimeMillis() - last) + "");
last = System.currentTimeMillis();
res = new ArrayList<>();
data.moveToPrevious();
while (data.moveToNext()) {
/* for (int i = 0;i < data.getColumnCount();i++) {
System.out.print(data.getString(i) + " ");
}*/
if (data.getString(1).matches(".*Screenshots.*")) {
res.add(data.getString(1));
}
/* System.out.println();*/
}
Log.i("结束时间",System.currentTimeMillis() - last + "");
if (onLoadFinishListener != null) {
onLoadFinishListener.onComplete(res);
}
}
@Override
public void onLoaderReset(@NonNull Loader loader) {
}
}
在Fragment中初始化Loader及使用Loader加载完成后的数据:
public class HomeFragment extends BaseFragment implements OnLoadFinishListener{
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(0, null, new PictureLoaderCallback(getContext(),
this));
}
@Override
public void onComplete(List data) {
for (String item:data) {
Log.i("ITEM",item);
}
}
}
这样在Loader加载完成时,调用onLoadFinished方法再调用Fragment传入的OnLoadFinishListener的onComplete方法,Fragment中就拿到了加载到的数据。
三、异步机制
Loader中是怎么支持异步加载的呢?
AsyncTaskLoader是支持异步加载的Loader的抽象类。上面代码中的CursorLoader就是AsyncTaskLoader的子类。
AsyncTaskLoader是怎么支持异步的?如它的名字一般,AsyncTaskLoader异步实现是依靠Android的AsyncTask。
AsyncTaskLoader有一个继承自AsyncTask的内部类:
final class LoadTask extends ModernAsyncTask implements Runnable {
private final CountDownLatch mDone = new CountDownLatch(1);
// Set to true to indicate that the task has been posted to a handler for
// execution at a later time. Used to throttle updates.
boolean waiting;
/* Runs on a worker thread */
@Override
protected D doInBackground(Void... params) {
if (DEBUG) Log.v(TAG, this + " >>> doInBackground");
try {
D data = AsyncTaskLoader.this.onLoadInBackground();
if (DEBUG) Log.v(TAG, this + " <<< doInBackground");
return data;
} catch (OperationCanceledException ex) {
if (!isCancelled()) {
// onLoadInBackground threw a canceled exception spuriously.
// This is problematic because it means that the LoaderManager did not
// cancel the Loader itself and still expects to receive a result.
// Additionally, the Loader's own state will not have been updated to
// reflect the fact that the task was being canceled.
// So we treat this case as an unhandled exception.
throw ex;
}
if (DEBUG) Log.v(TAG, this + " <<< doInBackground (was canceled)", ex);
return null;
}
}
/* Runs on the UI thread */
@Override
protected void onPostExecute(D data) {
if (DEBUG) Log.v(TAG, this + " onPostExecute");
try {
AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
} finally {
mDone.countDown();
}
}
/* Runs on the UI thread */
@Override
protected void onCancelled(D data) {
if (DEBUG) Log.v(TAG, this + " onCancelled");
try {
AsyncTaskLoader.this.dispatchOnCancelled(this, data);
} finally {
mDone.countDown();
}
}
/* Runs on the UI thread, when the waiting task is posted to a handler.
* This method is only executed when task execution was deferred (waiting was true). */
@Override
public void run() {
waiting = false;
AsyncTaskLoader.this.executePendingTask();
}
/* Used for testing purposes to wait for the task to complete. */
public void waitForLoader() {
try {
mDone.await();
} catch (InterruptedException e) {
// Ignore
}
}
}
doInBackgound中调用了AsyncTaskLoader.this.onLoadInBackground(),这里就是真正执行耗时操作的地方,由子类实现抽象类AsyncTaskLoader的接口。联系CursorLoader的源码:
@Override
public Cursor loadInBackground() {
synchronized (this) {
if (isLoadInBackgroundCanceled()) {
throw new OperationCanceledException();
}
mCancellationSignal = new CancellationSignal();
}
try {
Cursor cursor = ContentResolverCompat.query(getContext().getContentResolver(),
mUri, mProjection, mSelection, mSelectionArgs, mSortOrder,
mCancellationSignal);
if (cursor != null) {
try {
// Ensure the cursor window is filled.
cursor.getCount();
cursor.registerContentObserver(mObserver);
} catch (RuntimeException ex) {
cursor.close();
throw ex;
}
}
return cursor;
} finally {
synchronized (this) {
mCancellationSignal = null;
}
}
}
CursorLoader实现了AsynTaskLoader的loadInBackground方法,很清楚,在这个方法里调用ContentResolverCompat的query方法进行查询操作。所以,这个耗时的查询操作将在子线程中执行。
执行完成后,根据AsyncTask的机制,调用onPostExecute方法:从而 AsyncTaskLoader.this.dispatchOnLoadComplete(this, data):
void dispatchOnLoadComplete(LoadTask task, D data) {
if (mTask != task) {
if (DEBUG) Log.v(TAG, "Load complete of old task, trying to cancel");
dispatchOnCancelled(task, data);
} else {
if (isAbandoned()) {
// This cursor has been abandoned; just cancel the new data.
onCanceled(data);
} else {
commitContentChanged();
mLastLoadCompleteTime = SystemClock.uptimeMillis();
mTask = null;
if (DEBUG) Log.v(TAG, "Delivering result");
deliverResult(data);
}
}
}
没有异常的情况下,调用deliverResult(data);把结果分发下去。这是个Loader中就有的方法,看看CursorLoader重写的deliverResult(data)方法:
@Override
public void deliverResult(Cursor cursor) {
if (isReset()) {
// An async query came in while the loader is stopped
if (cursor != null) {
cursor.close();
}
return;
}
Cursor oldCursor = mCursor;
mCursor = cursor;
if (isStarted()) {
super.deliverResult(cursor);
}
if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
oldCursor.close();
}
}
其实还是调用了祖先Loader的方法。
public void deliverResult(D data) {
if (mListener != null) {
mListener.onLoadComplete(this, data);
}
}
这是个观察者模式,也就是说,观察者观察被观察者数据的改变作出反应。
至于mListener从哪来的,这就由LoadManagerImpl管理了。
LoadManagerImpl的静态内部类给Loader的mListener赋值:
public static class LoaderInfo extends MutableLiveData
implements Loader.OnLoadCompleteListener {
private final int mId;
private final @Nullable Bundle mArgs;
private final @NonNull Loader mLoader;
private LifecycleOwner mLifecycleOwner;
private LoaderObserver mObserver;
private Loader mPriorLoader;
LoaderInfo(int id, @Nullable Bundle args, @NonNull Loader loader,
@Nullable Loader priorLoader) {
mId = id;
mArgs = args;
mLoader = loader;
mPriorLoader = priorLoader;
mLoader.registerListener(id, this);
}
...
@Override
public void onLoadComplete(@NonNull Loader loader, @Nullable D data) {
if (DEBUG) Log.v(TAG, "onLoadComplete: " + this);
if (Looper.myLooper() == Looper.getMainLooper()) {
setValue(data);
} else {
// The Loader#deliverResult method that calls this should
// only be called on the main thread, so this should never
// happen, but we don't want to lose the data
if (DEBUG) {
Log.w(TAG, "onLoadComplete was incorrectly called on a "
+ "background thread");
}
postValue(data);
}
}
@Override
public void setValue(D value) {
super.setValue(value);
// Now that the new data has arrived, we can reset any prior Loader
if (mPriorLoader != null) {
mPriorLoader.reset();
mPriorLoader = null;
}
}
}
这个LoadInfo,继承自LiveData,而LiveData又是Android提供的一种观察者模式的数据存储,它也能与UI控件的生命周期绑定,从而不会产生内存泄漏。在数据改变时通知观察者。这里就不再说LiveData了
LiveData的官方文档:https://developer.android.com/topic/libraries/architecture/livedata
既然是观察者模式,那么观察者在哪呢,其实就在这:
Loader setCallback(@NonNull LifecycleOwner owner,
@NonNull LoaderCallbacks callback) {
LoaderObserver observer = new LoaderObserver<>(mLoader, callback);
// Add the new observer
observe(owner, observer);
// Loaders only support one observer at a time, so remove the current observer, if any
if (mObserver != null) {
removeObserver(mObserver);
}
mLifecycleOwner = owner;
mObserver = observer;
return mLoader;
}
也就是说,观察者对于数据改变产生的“反应”,其实主要就是LoaderCallbacks中的回调函数。