一航唆、簡(jiǎn)介
很多時(shí)候,我們可能想在Activity或Fragment中加載數(shù)據(jù)院刁,例如使用ContentProvider獲取數(shù)據(jù)庫(kù)數(shù)據(jù)糯钙。顯然,類(lèi)似ContenProvider從數(shù)據(jù)庫(kù)中讀取數(shù)據(jù)多數(shù)是耗時(shí)行為退腥,不能在主線程中完成加載因?yàn)檫@會(huì)阻塞主線程超营。Android 3.0后,提供了Loader加載器阅虫,可以輕松的在Activity或Fragment中異步加載數(shù)據(jù)演闭。Loader具有以下特性:
支持異步加載數(shù)據(jù)颓帝。
監(jiān)控其數(shù)據(jù)源并在內(nèi)容變化時(shí)傳遞新結(jié)果米碰。
在某一配置更改后重建加載器時(shí),會(huì)自動(dòng)重新連接上一個(gè)加載器的游標(biāo)购城。 因此吕座,它們無(wú)需重新查詢其數(shù)據(jù)。
此外瘪板,Loader和Activity或Fragment的生命周期綁定吴趴,Activity或Fragment管理LoaderManager,而LoaderManager又管理著Loader侮攀。在它們生命周期結(jié)束時(shí)(onDestroy)锣枝,它們會(huì)把Loader也Destroy。并且Loader不能作為內(nèi)部類(lèi)的對(duì)象初始化兰英,因?yàn)閮?nèi)部類(lèi)會(huì)持有外部Activity/Fragment的引用撇叁,造成內(nèi)存泄漏。
二畦贸、使用
常用的Loader API有:
Loader API 摘要
在應(yīng)用中使用加載器時(shí)陨闹,可能會(huì)涉及到多個(gè)類(lèi)和接口。 下表匯總了這些類(lèi)和接口:
|
一種與 Activity 或 Fragment 相關(guān)聯(lián)的的抽象類(lèi)薄坏,用于管理一個(gè)或多個(gè) Loader 實(shí)例趋厉。 這有助于應(yīng)用管理與Activity 或 Fragment 生命周期相關(guān)聯(lián)的、運(yùn)行時(shí)間較長(zhǎng)的操作胶坠。它最常見(jiàn)的用法是與 CursorLoader 一起使用君账,但應(yīng)用可自由寫(xiě)入其自己的加載器,用于加載其他類(lèi)型的數(shù)據(jù)涵但。
每個(gè) Activity 或片段中只有一個(gè) LoaderManager杈绸。但一個(gè) LoaderManager 可以有多個(gè)加載器矮瘟。
|
|
|
一種回調(diào)接口瞳脓,用于客戶端與 LoaderManager 進(jìn)行交互。例如澈侠,您可使用 onCreateLoader() 回調(diào)方法創(chuàng)建新的加載器劫侧。
|
|
|
一種執(zhí)行異步數(shù)據(jù)加載的抽象類(lèi)。這是加載器的基類(lèi)哨啃。 您通常會(huì)使用 CursorLoader烧栋,但您也可以實(shí)現(xiàn)自己的子類(lèi)。加載器處于活動(dòng)狀態(tài)時(shí)拳球,應(yīng)監(jiān)控其數(shù)據(jù)源并在內(nèi)容變化時(shí)傳遞新結(jié)果审姓。
|
|
|
提供 AsyncTask 來(lái)執(zhí)行工作的抽象加載器。
|
|
|
AsyncTaskLoader 的子類(lèi)祝峻,它將查詢 ContentResolver 并返回一個(gè) Cursor魔吐。此類(lèi)采用標(biāo)準(zhǔn)方式為查詢游標(biāo)實(shí)現(xiàn) Loader 協(xié)議。它是以 AsyncTaskLoader 為基礎(chǔ)而構(gòu)建莱找,在后臺(tái)線程中執(zhí)行游標(biāo)查詢酬姆,以免阻塞應(yīng)用的 UI。使用此加載器是從 ContentProvider 異步加載數(shù)據(jù)的最佳方式奥溺,而不用通過(guò)片段或 Activity 的 API 來(lái)執(zhí)行托管查詢辞色。
|
LoaderManager.LoaderCallbacks中通常由用戶實(shí)現(xiàn),onCreateLoader方法返回想要使用的Loader實(shí)例浮定,onLoadFinished為L(zhǎng)oader加載數(shù)據(jù)完成后的回調(diào)函數(shù)相满,onLoaderReset為L(zhǎng)oader重置時(shí)的初始化函數(shù)。
以下是一個(gè)使用Loader異步讀取手機(jī)中截圖圖片路徑的例子:
回調(diào)類(lèi)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<Cursor> {
private Context context;
private OnLoadFinishListener onLoadFinishListener;
private List<String> 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<Cursor> onCreateLoader(int id, Bundle args) {
Log.i("開(kāi)始時(shí)間",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<Cursor> loader, Cursor data) {
if (data == null) {
return;
}
Log.i("返回時(shí)間",(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("結(jié)束時(shí)間",System.currentTimeMillis() - last + "");
if (onLoadFinishListener != null) {
onLoadFinishListener.onComplete(res);
}
}
@Override
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
}
}
在Fragment中初始化Loader及使用Loader加載完成后的數(shù)據(jù):
public class HomeFragment extends BaseFragment implements OnLoadFinishListener<String>{
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(0, null, new PictureLoaderCallback(getContext(),
this));
}
@Override
public void onComplete(List<String> data) {
for (String item:data) {
Log.i("ITEM",item);
}
}
}
這樣在Loader加載完成時(shí)桦卒,調(diào)用onLoadFinished方法再調(diào)用Fragment傳入的OnLoadFinishListener的onComplete方法雳灵,F(xiàn)ragment中就拿到了加載到的數(shù)據(jù)。
三闸盔、異步機(jī)制
Loader中是怎么支持異步加載的呢悯辙?
AsyncTaskLoader是支持異步加載的Loader的抽象類(lèi)。上面代碼中的CursorLoader就是AsyncTaskLoader的子類(lèi)迎吵。
AsyncTaskLoader是怎么支持異步的躲撰?如它的名字一般,AsyncTaskLoader異步實(shí)現(xiàn)是依靠Android的AsyncTask击费。
AsyncTaskLoader有一個(gè)繼承自AsyncTask的內(nèi)部類(lèi):
final class LoadTask extends ModernAsyncTask<Void, Void, D> 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中調(diào)用了AsyncTaskLoader.this.onLoadInBackground()拢蛋,這里就是真正執(zhí)行耗時(shí)操作的地方,由子類(lèi)實(shí)現(xiàn)抽象類(lèi)AsyncTaskLoader的接口蔫巩。聯(lián)系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實(shí)現(xiàn)了AsynTaskLoader的loadInBackground方法谆棱,很清楚快压,在這個(gè)方法里調(diào)用ContentResolverCompat的query方法進(jìn)行查詢操作。所以垃瞧,這個(gè)耗時(shí)的查詢操作將在子線程中執(zhí)行瞄崇。
執(zhí)行完成后懈糯,根據(jù)AsyncTask的機(jī)制圾结,調(diào)用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);
}
}
}
沒(méi)有異常的情況下鸭轮,調(diào)用deliverResult(data);把結(jié)果分發(fā)下去。這是個(gè)Loader中就有的方法嗦锐,看看CursorLoader重寫(xiě)的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();
}
}
其實(shí)還是調(diào)用了祖先Loader的方法嫌松。
public void deliverResult(D data) {
if (mListener != null) {
mListener.onLoadComplete(this, data);
}
}
這是個(gè)觀察者模式,也就是說(shuō)奕污,觀察者觀察被觀察者數(shù)據(jù)的改變作出反應(yīng)萎羔。
至于mListener從哪來(lái)的,這就由LoadManagerImpl管理了碳默。
LoadManagerImpl的靜態(tài)內(nèi)部類(lèi)給Loader的mListener賦值:
public static class LoaderInfo<D> extends MutableLiveData<D>
implements Loader.OnLoadCompleteListener<D> {
private final int mId;
private final @Nullable Bundle mArgs;
private final @NonNull Loader<D> mLoader;
private LifecycleOwner mLifecycleOwner;
private LoaderObserver<D> mObserver;
private Loader<D> mPriorLoader;
LoaderInfo(int id, @Nullable Bundle args, @NonNull Loader<D> loader,
@Nullable Loader<D> priorLoader) {
mId = id;
mArgs = args;
mLoader = loader;
mPriorLoader = priorLoader;
mLoader.registerListener(id, this);
}
...
@Override
public void onLoadComplete(@NonNull Loader<D> 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;
}
}
}
這個(gè)LoadInfo贾陷,繼承自LiveData,而LiveData又是Android提供的一種觀察者模式的數(shù)據(jù)存儲(chǔ)腻窒,它也能與UI控件的生命周期綁定昵宇,從而不會(huì)產(chǎn)生內(nèi)存泄漏。在數(shù)據(jù)改變時(shí)通知觀察者儿子。這里就不再說(shuō)LiveData了
LiveData的官方文檔:https://developer.android.com/topic/libraries/architecture/livedata
既然是觀察者模式瓦哎,那么觀察者在哪呢,其實(shí)就在這:
Loader<D> setCallback(@NonNull LifecycleOwner owner,
@NonNull LoaderCallbacks<D> callback) {
LoaderObserver<D> 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;
}
也就是說(shuō)柔逼,觀察者對(duì)于數(shù)據(jù)改變產(chǎn)生的“反應(yīng)”蒋譬,其實(shí)主要就是LoaderCallbacks中的回調(diào)函數(shù)。