Loader源碼分析

https://developer.android.com/reference/android/content/AsyncTaskLoader.html

什么是loader loader是干什么的

Loader一個執行異步數據加載的類。在Loader處于活動狀態時,應該監視其數據源并在內容發生更改時提供新的結果。
Loader有兩個子類,AsyncTaskLoader和CursorLoader
在本次簡單實例中我們用的就是CursorLoader進行加載數據。對Cursor進行操作,建議使用CursorLoader。
有時CursorLoader不能滿足我們的需求,需要我們自定義Loader,一般情況下,我們不直接集成Loader,而是繼承AsyncTaskLoader。
子類通常必須至少實現onStartLoading(),onStopLoading(),onForceLoad()和onReset()。

  1. 在單獨的線程中加載數據,并不會阻塞UI線程
  2. 監聽數據的變化
  3. Activity配置發生變化如屏幕橫豎屏切換,不會在重新加載數據。

什么是LoaderManager

LoaderManager是專門管理Loader的管理器,一個Activity或者Fragment只能有一個LoaderManager,一個LoaderManager可以管理多個Loader,LoaderManager管理Loader的各種事件。

Loader的簡單使用

public class LoaderDemoActivity extends AppCompatActivity {

    private ListView listView;
    private SimpleCursorAdapter adapter;
    private CursorLoader loader;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_loader_demo);
        initView();
    }


    private void initView() {
        listView = (ListView) findViewById(R.id.lv_loader_demo);
        adapter = new SimpleCursorAdapter(this, R.layout.item_loader_demo, null, new String[]{MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.DATA}, new int[]{R.id.tv_loader_music, R.id.tv_loader_who_music, R.id.tv_loader_path}, 0);
        listView.setAdapter(adapter);
        getLoaderManager().initLoader(0, null, new LoaderManager.LoaderCallbacks<Cursor>() {
            @Override
            public Loader<Cursor> onCreateLoader(int id, Bundle args) {


                Toast.makeText(LoaderDemoActivity.this, "創建Loader", Toast.LENGTH_SHORT).show();
                loader = new CursorLoader(LoaderDemoActivity.this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
                return loader;
            }

            @Override
            public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
                Toast.makeText(LoaderDemoActivity.this, "Loader加載完成", Toast.LENGTH_SHORT).show();
                adapter.swapCursor(data);
            }

            @Override
            public void onLoaderReset(Loader<Cursor> loader) {
                Toast.makeText(LoaderDemoActivity.this, "Loader重置", Toast.LENGTH_SHORT).show();
                adapter.swapCursor(null);
            }

        });
    }
}

在Activity中創建一個Loader,首先調用getLoaderManager方法,獲取LoaderManager,在調用initLoader方法會返回一個Loader。

  • initLoader
    確保Loader已經初始化,并且處于活動狀態,如果Loader不存在,會創建一個加載器,如果存在會復用之前的加載器。initLoader有三個參數:
  1. id 區分不同的Loader
  2. Bundle 一個可選的參數
  3. LoaderManager.LoaderCallbacks<D> callback 回調接口

LoaderManager.LoaderCallbacks<D> callback回調存在三個方法:

  1. onCreateLoader 實例化并根據給定的id返回一個新的Loader
  2. onLoadFinished 在加載完成時調用 data是由Loader生成的數據
  3. onLoaderReset 表示正在重置,在這個方法中應該刪除對數據的引用。

Loader類源碼分析

接口

  • public final class ForceLoadContentObserver extends ContentObserver
 public final class ForceLoadContentObserver extends ContentObserver {
        public ForceLoadContentObserver() {
            super(new Handler());
        }

        @Override
        public boolean deliverSelfNotifications() {
            return true;
        }

        @Override
        public void onChange(boolean selfChange) {
            onContentChanged();
        }
    }
  • deliverSelfNotifications 如果返回true,觀察者將自我接受更改通知
    ContentObserver的一個實現類,負責將他鏈接到Loader,當觀察者告訴它數據已經改變時,Loader會重新加載數據,通常不需要自己使用,當cursor的數據發生改變時,CursorLoader會使用它來執行更新。

  • onChange 當觀察的內容發生改變時才被調用。子類應該重寫來處理內容更改的邏輯。

  • public interface OnLoadCompleteListener<D>

 public interface OnLoadCompleteListener<D> {
        /**
         * Called on the thread that created the Loader when the load is complete.
         *
         * @param loader the loader that completed the load
         * @param data the result of the load
         */
        public void onLoadComplete(Loader<D> loader, D data);
    }

OnLoadCompleteListener是一個接口,用于發現loader何時加載完數據。不需要我們來實現,是由LoaderManager實現。
唯一方法 onLoadComplete 在加載完成時,調用在創建Loader的線程,data就是加載的結果。

  • OnLoadCanceledListener
public interface OnLoadCanceledListener<D> {
        /**
         * Called on the thread that created the Loader when the load is canceled.
         *
         * @param loader the loader that canceled the load
         */
        public void onLoadCanceled(Loader<D> loader);
    }

用于在加載數據完之前檢查加載器何時取消。也是,不需要我們來實現,LoaderManager會處理。onLoadCanceled唯一實現方法,在此方法調用時,應該釋放數據。

方法

  • deliverResult
public void deliverResult(D data) {
        if (mListener != null) {
            mListener.onLoadComplete(this, data);
        }
    }

將加載的結果返回給注冊的監聽器。只能由子類調用。必須從進程的主線程中調用。

  • deliverCancellation
public void deliverCancellation() {
        if (mOnLoadCanceledListener != null) {
            mOnLoadCanceledListener.onLoadCanceled(this);
        }
    }

通知已經注冊的Loader.OnLoadCanceledListener接口Loader已經取消。只能由子類調用。必須從進程的主線程中調用。

  • getContext getId
/**
     * @return an application context retrieved from the Context passed to the constructor.
     */
    public Context getContext() {
        return mContext;
    }

    /**
     * @return the ID of this loader
     */
    public int getId() {
        return mId;
    }

返回上下文
返回Loader的ID

  • registerListener 和 unregisterListener
/**
     * Registers a class that will receive callbacks when a load is complete.
     * The callback will be called on the process's main thread so it's safe to
     * pass the results to widgets.
     *
     * <p>Must be called from the process's main thread.
     */
    public void registerListener(int id, OnLoadCompleteListener<D> listener) {
        if (mListener != null) {
            throw new IllegalStateException("There is already a listener registered");
        }
        mListener = listener;
        mId = id;
    }

    /**
     * Remove a listener that was previously added with {@link #registerListener}.
     *
     * Must be called from the process's main thread.
     */
    public void unregisterListener(OnLoadCompleteListener<D> listener) {
        if (mListener == null) {
            throw new IllegalStateException("No listener register");
        }
        if (mListener != listener) {
            throw new IllegalArgumentException("Attempting to unregister the wrong listener");
        }
        mListener = null;
    }
  1. registerListener:注冊一個將在加載完成時接受回調的類。必須主線程中調用
  2. unregisterListener:移除監聽器
  • registerOnLoadCanceledListener 和 unregisterOnLoadCanceledListener
/**
     * Registers a listener that will receive callbacks when a load is canceled.
     * The callback will be called on the process's main thread so it's safe to
     * pass the results to widgets.
     *
     * Must be called from the process's main thread.
     *
     * @param listener The listener to register.
     */
    public void registerOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
        if (mOnLoadCanceledListener != null) {
            throw new IllegalStateException("There is already a listener registered");
        }
        mOnLoadCanceledListener = listener;
    }

    /**
     * Unregisters a listener that was previously added with
     * {@link #registerOnLoadCanceledListener}.
     *
     * Must be called from the process's main thread.
     *
     * @param listener The listener to unregister.
     */
    public void unregisterOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
        if (mOnLoadCanceledListener == null) {
            throw new IllegalStateException("No listener register");
        }
        if (mOnLoadCanceledListener != listener) {
            throw new IllegalArgumentException("Attempting to unregister the wrong listener");
        }
        mOnLoadCanceledListener = null;
    }
  1. registerOnLoadCanceledListener:注冊一個將在加載被取消時接收回調的偵聽器。
  2. unregisterOnLoadCanceledListener:取消注冊的registerOnLoadCanceledListener(Loader.OnLoadCanceledListener)的偵聽器
  • isStarted 和 isAbandoned和isReset
/**
     * Return whether this load has been started.  That is, its {@link #startLoading()}
     * has been called and no calls to {@link #stopLoading()} or
     * {@link #reset()} have yet been made.
     */
    public boolean isStarted() {
        return mStarted;
    }

    /**
     * Return whether this loader has been abandoned.  In this state, the
     * loader <em>must not</em> report any new data, and <em>must</em> keep
     * its last reported data valid until it is finally reset.
     */
    public boolean isAbandoned() {
        return mAbandoned;
    }

    /**
     * Return whether this load has been reset.  That is, either the loader
     * has not yet been started for the first time, or its {@link #reset()}
     * has been called.
     */
    public boolean isReset() {
        return mReset;
    }
  1. isStarted:返回這個Loader是否已經被加載。如果返回true表示已經調用了startLoading方法,并且沒有調用stopLoading方法或者reset方法。
  2. isAbandoned:返回這個Loader是否被放棄。如果返回true,Loader不能發送任何新的數據,并且必須保持上次發送的數據有效值。知道調用reset。
  3. isReset:返回這個Loader是否被重置。也就是說,加載程序尚未第一次啟動,或者調用了reset()。

startLoading

public final void startLoading() {
        mStarted = true;
        mReset = false;
        mAbandoned = false;
        onStartLoading();
    }

當相關的Activity或者Fragment正在啟動時,LoaderManager通常會自動調用這個方法,與LoaderManager一起使用時,不要自己調用這個方法,否則會和LoaderManager發生沖突。loader會異步加載數據,當結果準備就緒時,回調會發生在主線程。如果之前的數據加載完成,并且有效,則結果立即傳遞給回調,Loader將監聽數據源,如果有更改,則提供新的數據。調用stopLoading()將停止傳遞回調。
這會更新Loader的內部狀態,以便isStarted()和isReset()將返回正確的值,然后調用實現的onStartLoading()。
必須從進程的主線程中調用。

cancelLoad

public boolean cancelLoad() {
        return onCancelLoad();
    }

嘗試取消當前加載任務。

取消的操作不是立即執行,因為數據加載在后臺進程。如果loader當前正在進行加載,此時調用這個方法要求取消加載,在這種情況,一旦后臺進程完成加載工作,其余的狀態都會被清除。如果在此期間另一個Loader請求加載,它會一直保持取消加載完成。

此方法有個boolean返回值,如果任務無法取消,返回false,通常是因為loader在后臺已經正常完成了,也有可能沒有調用startLoading。如果返回true,任務仍在繼續運行,當任務完成時將調用Loader.OnLoadCanceledListener。

  • onCancelLoad
protected boolean onCancelLoad() {
        return false;
    }

子類必須實現這個方法,處理取消的邏輯。

  • forceLoad 和 onForceLoad
public void forceLoad() {
        onForceLoad();
    }

    /**
     * Subclasses must implement this to take care of requests to {@link #forceLoad()}.
     * This will always be called from the process's main thread.
     */
    protected void onForceLoad() {
    }
  1. forceLoad 強制進行異步加載。和startLoading不同,忽略以前的數據,加載一個新的數據。通常只有加載程序啟動時才應該調用這個方法,也就是說isStart返回true。
  2. 子類必須實現這個方法來處理forceLoad的請求。
  • stopLoading 和 onStopLoading
public void stopLoading() {
        mStarted = false;
        onStopLoading();
    }

    /**
     * Subclasses must implement this to take care of stopping their loader,
     * as per {@link #stopLoading()}.  This is not called by clients directly,
     * but as a result of a call to {@link #stopLoading()}.
     * This will always be called from the process's main thread.
     */
    protected void onStopLoading() {
    }
  1. stopLoading

通常來說會被Loader Manager自動調用,當相關的Activity或者Fragment停止時,不需要自己調用,否則會和LoaderManager發生沖突。

停止傳遞更新,知道下一此調用startLoading。在此時實現不應該數據無效,客戶端仍然可以使用上一次發送的數據。但是如果數據發生改變,就不會發送新的數據。仍然會監聽數據源更改,但是不發送新數據給客戶端,直到startLoading被調用。

這將更新Loader的內部狀態,以便isStarted()將返回正確的值,然后調用實現的onStopLoading()。

  1. onStopLoading:子類必須實現這個來停止它們的加載器。這不是由客戶端直接調用,而是由stopLoading()調用結果。這將始終從進程的主線程中調用。
  • abandon 和 onAbandon
public void abandon() {
        mAbandoned = true;
        onAbandon();
    }

protected void onAbandon() {
    }
  1. abandon: 在LoaderManager重新啟動Loader時,通常會自動調用這個方法,和Loader Manager一起使用時,不能自己調用方法,否則會和LoaderManager發生沖突,告訴Loader,它正在被放棄,這是在reset之前,讓他保留當前的數據,但不發送任何新數據。

  2. onAbandon:子類必須重新實現被放棄方法,這個是在onRest方法之前可選的中間狀態,這意味著客戶端對來自加載器的數據不在關注,此時Loader不需要在發送新的數據。但是Loader必須保證上次數據是有效的,直到onReset被調用,可以使用isAbandoned方法檢索當前的廢棄狀態。

  • reset
public void reset() {
        onReset();
        mReset = true;
        mStarted = false;
        mAbandoned = false;
        mContentChanged = false;
        mProcessingChange = false;
    }

protected void onReset() {
    }
  1. reset
    這個方法通常是在Loader正在銷毀時被LoaderManager自動調用的,和LoaderManager一起使用時,自己不要去調用這個方法,否則會和LoaderManager發生沖突。
    這個方法會重置Loader的狀態,在這一點上,應該釋放所有的資源,因為可用不會再次調用這個方法。
    也有可能會調用startLoading方法,此時loader必須可以再次運行。
    此方法會更新內部的狀態,以便isStarted()和isReset()將返回正確的值,然后調用實現的onReset()。

  2. onReset
    子類要想重置Loader必須實現此方法,

  • takeContentChanged
/**
     * Take the current flag indicating whether the loader's content had
     * changed while it was stopped.  If it had, true is returned and the
     * flag is cleared.
     */
    public boolean takeContentChanged() {
        boolean res = mContentChanged;
        mContentChanged = false;
        mProcessingChange |= res;
        return res;
    }

獲取當前的標志,查看Loader加載的內容是否在停止前發生了改變,如果有,返回true,,并清除標志位。

  • commitContentChanged
public void commitContentChanged() 
        mProcessingChange = false;
    }

根據takeContentChanged的返回值,提交已經處理完的更改的內容。與rollbackContentChanged()一起使用來處理加載被取消的情況。 當你處理一個Loader而不被取消的時候調用它。

  • rollbackContentChanged
public void rollbackContentChanged() {
        if (mProcessingChange) {
            onContentChanged();
        }
    }

通知已放棄由takeContentChanged()返回的內容更改的處理,并且想要回滾到還有待處理內容更改的狀態。 這是為了處理在數據被傳送回加載程序之前由于內容改變而導致的數據加載已被取消的情況。

  • onContentChanged
public void onContentChanged() {
        if (mStarted) {
            forceLoad();
        } else {
            // This loader has been stopped, so we don't want to load
            //Loader已經停止了,所以我們不加載
            // new data right now...  but keep track of it changing to
            //新的數據現在...但跟蹤它改變
            // refresh later if we start again.
            //如果再一次啟動,等待數據刷新
            mContentChanged = true;
        }
    }

Loader.ForceLoadContentObserver檢測到有更改時調用, 默認實現檢查Loader是否當前啟動; 如果是,它只是簡單地調用forceLoad(); 否則,它設置一個標志,以便takeContentChanged()返回true。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容