Loader使用及異步支持

一航唆、簡(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具有以下特性:

  • 可用于每個(gè) ActivityFragment

  • 支持異步加載數(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)和接口:

LoaderManager

|

一種與 ActivityFragment 相關(guān)聯(lián)的的抽象類(lèi)薄坏,用于管理一個(gè)或多個(gè) Loader 實(shí)例趋厉。 這有助于應(yīng)用管理與ActivityFragment 生命周期相關(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è)加載器矮瘟。

|
|

LoaderManager.LoaderCallbacks

|

一種回調(diào)接口瞳脓,用于客戶端與 LoaderManager 進(jìn)行交互。例如澈侠,您可使用 onCreateLoader() 回調(diào)方法創(chuàng)建新的加載器劫侧。

|
|

Loader

|

一種執(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é)果审姓。

|
|

AsyncTaskLoader

|

提供 AsyncTask 來(lái)執(zhí)行工作的抽象加載器。

|
|

CursorLoader

|

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ù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末愉适,一起剝皮案震驚了整個(gè)濱河市犯助,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌维咸,老刑警劉巖剂买,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異癌蓖,居然都是意外死亡瞬哼,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)租副,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)坐慰,“玉大人,你說(shuō)我怎么就攤上這事用僧〗嵴停” “怎么了赞咙?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)糟港。 經(jīng)常有香客問(wèn)我攀操,道長(zhǎng),這世上最難降的妖魔是什么着逐? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任崔赌,我火速辦了婚禮意蛀,結(jié)果婚禮上耸别,老公的妹妹穿的比我還像新娘。我一直安慰自己县钥,他們只是感情好秀姐,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著若贮,像睡著了一般省有。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谴麦,一...
    開(kāi)封第一講書(shū)人閱讀 49,792評(píng)論 1 290
  • 那天蠢沿,我揣著相機(jī)與錄音,去河邊找鬼匾效。 笑死舷蟀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的面哼。 我是一名探鬼主播野宜,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼魔策!你這毒婦竟也來(lái)了匈子?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤闯袒,失蹤者是張志新(化名)和其女友劉穎虎敦,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體政敢,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡其徙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了堕仔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片擂橘。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖摩骨,靈堂內(nèi)的尸體忽然破棺而出通贞,到底是詐尸還是另有隱情朗若,我是刑警寧澤,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布昌罩,位于F島的核電站哭懈,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏茎用。R本人自食惡果不足惜遣总,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望轨功。 院中可真熱鬧旭斥,春花似錦、人聲如沸古涧。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)羡滑。三九已至菇爪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柒昏,已是汗流浹背凳宙。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留职祷,地道東北人氏涩。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像堪旧,于是被迫代替她去往敵國(guó)和親削葱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348