Loader 知識梳理(2) - initLoader和restartLoader的區(qū)別

一、概述

在前面的一篇文章中,我們分析了LoaderManager的實現(xiàn),里面涉及到了很多的細(xì)節(jié)問題,我們并不需要刻意地記住每個流程,之所以需要分析,以后在使用的過程中,如果遇到問題了,再去查看相關(guān)的源代碼就好了。
對于Loader框架的理解,我認(rèn)為掌握以下四個方面的東西就可以了:

  • LoaderManager的實現(xiàn)原理有一個大概的印象。
  • LoaderManager的三個關(guān)鍵方法initLoader/restartLoader/destroyLoader的使用場景。
  • 學(xué)會自定義Loader來實現(xiàn)數(shù)據(jù)的異步加載。
  • 總結(jié)一些App開發(fā)中常用的場景。

第一點可以參考前面的這篇文章:

Loader框架 - LoaderManager初探

今天這篇,我們將專注于分析第二點:initLoader/restartLoader的區(qū)別。

二、基本概念的區(qū)別

首先,我們回顧一下,對于init/restart的定義:

  • initLoader

  • 通過調(diào)用這個方法來初始化一個特定IDLoader,如果當(dāng)前已經(jīng)有一個和這個ID關(guān)聯(lián)的Loader,那么并不會去回調(diào)onCreateLoader來通知使用者傳入一個新的 Loader實例替代那個舊的實例,僅僅是替代Callback,也就是說,上面的Bundle參數(shù)被丟棄了;而假如不存在一個關(guān)聯(lián)的實例,那么會進行初始化,并啟動它。

  • 這個方法通常是在Activity/Fragment的初始化階段調(diào)用,因為它會判斷之前是否存在相同的Loader,這樣我們就可以復(fù)用之前已經(jīng)加載過的數(shù)據(jù),當(dāng)屏幕裝轉(zhuǎn)導(dǎo)致Activity重建的時候,我們就不需要再去重新創(chuàng)建一個新的Loader,也免去了重新讀取數(shù)據(jù)的過程。

  • restartLoader

  • 調(diào)用這個方法,將會重新創(chuàng)建一個指定IDLoader,如果當(dāng)前已經(jīng)有一個和這個ID關(guān)聯(lián)的Loader,那么會對它進行canceled/stopped/destroyed等操作,之后,使用新傳入的Bundle參數(shù)來創(chuàng)建一個新的Loader,并在數(shù)據(jù)加載完畢后遞交給調(diào)用者。

  • 并且,在調(diào)用完這個函數(shù)之后,所有之前和這個ID關(guān)聯(lián)的Loader都會失效,我們將不會收到它們傳遞過來的任何數(shù)據(jù)。

總結(jié)下來就是:

  • 當(dāng)調(diào)用上面這兩個方法時,如果不存在一個和ID關(guān)聯(lián)的Loader,那么這兩個方法是完全相同的。
  • 如果已經(jīng)存在相關(guān)聯(lián)的Loader,那么init方法除了替代Callback,不會做任何其它的事情,包括取消/停止等。而restart方法將會創(chuàng)建一個新的Loader,并且重新開始查詢。

三、代碼的區(qū)別

為了方便大家更直觀地理解,我們截取一部分的源碼來看一下:

  • initLoader
LoaderInfo info = mLoaders.get(id);
if (info == null) {
    //如果不存在關(guān)聯(lián)的Loader,那么創(chuàng)建一個新的Loader
    info = createAndInstallLoader(id, args, LoaderManager.LoaderCallbacks<Object>)callback);
} else {
   //如果已經(jīng)存在,那么僅僅替代Callback,傳入的Bundle參數(shù)會被丟棄。
   info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}
  • restartLoader
LoaderInfo info = mLoaders.get(id);
//如果已經(jīng)存在一個相關(guān)聯(lián)的Loader,那么執(zhí)行操作。
if (info != null) {
    //mInactiveLoaders列表就是用來跟蹤那些已經(jīng)被拋棄的Loader
    LoaderInfo inactive = mInactiveLoaders.get(id);
    if (inactive != null) {
        //對跟蹤列表進行一系列的操作。
    } else {
        //取消被拋棄的Loader,并加入到跟蹤列表當(dāng)中,以便在新的Loader完成任務(wù)之后銷毀它。
        info.mLoader.abandon();
        mInactiveLoaders.put(id, info);
    }
}
//通知調(diào)用者,創(chuàng)建一個新的Loader,這個Loader將會和新的Bundle和Callback相關(guān)聯(lián)。
info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);

通過上面的代碼,就印證了前面第二節(jié)我們的說話,我們根據(jù)這些區(qū)別可以知道它們各自的職責(zé):

  • initLoader是用來確保Loader能夠被初始化,如果已經(jīng)存在相同IDLoader,那么它會復(fù)用之前的。
  • restartLoader的應(yīng)用場景則是我們的查詢條件發(fā)生了改變。因為LoaderManager是用ID關(guān)聯(lián)的,當(dāng)這個Loader已經(jīng)獲取到了數(shù)據(jù),那么就不需要再啟動它了。因此當(dāng)我們的需求發(fā)生了改變,就需要重新創(chuàng)建一個Loader。

也就是說:

  • 查詢條件一直不變時,使用initLoader
  • 查詢條件有可能發(fā)生改變時,采用restartLoader。

五、對于屏幕旋轉(zhuǎn)的情況

5.1 重建

當(dāng)我們在Manifest.xml沒有給Activity配置configChanged的時候,旋轉(zhuǎn)屏幕會導(dǎo)致的Activity/Fragment重建,這時候有兩點需要注意的:

  • 由于此時我們的查詢條件并不會發(fā)生改變,并且LoaderManager會幫我們恢復(fù)Loader的狀態(tài)。因此,我們沒有必要再去調(diào)用restartLoader來重新創(chuàng)建Loader來執(zhí)行一次耗時的查詢操作。

  • LoaderManager雖然會恢復(fù)Loader,但是它不會保存Callback實例,因此,如果我們希望在旋轉(zhuǎn)完之后獲得數(shù)據(jù),那么至少要調(diào)用一次initLoader來傳入一個新的Callback進行監(jiān)聽。

在這種情況下,假如我們在旋轉(zhuǎn)之前Loader已經(jīng)加載數(shù)據(jù)完畢了,那么onLoadFinished會立即被會調(diào)。

5.2 沒有重建

當(dāng)沒有重建時,不會走onCreate方法,因此需要在別的地方來初始化Loader。

5.3 LoaderId

針對上面的這兩種情況,我們都需要自己去保存LoaderId,在組件恢復(fù)之后,通過這個保存的id去調(diào)用init/restart方法,一般情況下,是通過savedInstanceState來保存的。

六、示例

現(xiàn)在,我們通過一個很簡單的例子,來看一下,initLoader/restartLoader的區(qū)別,我們的Demo中有一個EditText和一個TextView,當(dāng)EditText發(fā)生改變時,我們將當(dāng)前EditText的內(nèi)容作為查詢的Key,查詢?nèi)蝿?wù)就是調(diào)用Loader,延時2s,并將這個key作為查詢的結(jié)果展示在TextView上。

6.1 采用initLoader來查詢:

public class MainActivity extends Activity {

    private static final String LOADER_TAG = "loader_tag";
    private static final String QUERY = "query";

    private MyLoaderCallback mMyLoaderCallback;
    private TextView mResultView;
    private EditText mEditText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    private void init() {
        mEditText = (EditText) findViewById(R.id.loader_input);
        mResultView = (TextView) findViewById(R.id.loader_result);
        mEditText.addTextChangedListener(new MyEditTextWatcher());
        mMyLoaderCallback = new MyLoaderCallback();
    }

    private void startQuery(String query) {
        if (query != null) {
            Bundle bundle = new Bundle();
            bundle.putString(QUERY, query);
            getLoaderManager().initLoader(0, bundle, mMyLoaderCallback);
        }
    }

    private void showResult(String result) {
        if (mResultView != null) {
            mResultView.setText(result);
        }
    }

    private static class MyLoader extends BaseDataLoader<String> {

        public MyLoader(Context context, Bundle bundle) {
            super(context, bundle);
        }

        @Override
        protected String loadData(Bundle bundle) {
            Log.d(LOADER_TAG, "loadData");
            try {
                Thread.sleep(2000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return bundle != null ? bundle.getString(QUERY) : "empty";
        }
    }

    private class MyLoaderCallback implements LoaderManager.LoaderCallbacks {

        @Override
        public Loader onCreateLoader(int id, Bundle args) {
            Log.d(LOADER_TAG, "onCreateLoader");
            return new MyLoader(getApplicationContext(), args);
        }

        @Override
        public void onLoadFinished(Loader loader, Object data) {
            Log.d(LOADER_TAG, "onLoadFinished");
            showResult((String) data);
        }

        @Override
        public void onLoaderReset(Loader loader) {
            Log.d(LOADER_TAG, "onLoaderReset");
            showResult("");
        }
    }

    private class MyEditTextWatcher implements TextWatcher {

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            Log.d(LOADER_TAG, "onTextChanged=" + s);
            startQuery(s != null ? s.toString() : "");
        }

        @Override
        public void afterTextChanged(Editable s) {}
    }

}

當(dāng)我們輸入a時,成功地獲取到了數(shù)據(jù):


之后,我們繼續(xù)輸入b,發(fā)現(xiàn)onLoadFinished立即被回調(diào)了,但是結(jié)果還是之前地a

我們通過AS的斷電發(fā)現(xiàn),整個調(diào)用過程如下:

也就是在initLoader之后,立即執(zhí)行了:

    public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
        //按照前面的分析,此時的info不為null。
        if (info.mHaveData && mStarted) {
            info.callOnLoadFinished(info.mLoader, info.mData);
        }
        return (Loader<D>)info.mLoader;
    }

而之后,callOnLoadFinished就會把之前的數(shù)據(jù)傳回給調(diào)用者,因此沒有執(zhí)行onCreateLoader,也沒有進行查詢操作:

        void callOnLoadFinished(Loader<Object> loader, Object data) {
            //傳遞數(shù)據(jù)給調(diào)用者.
            if (mCallbacks != null) {
                mCallbacks.onLoadFinished(loader, data);
            }
        }

假如,我們在a觸發(fā)的任務(wù)還沒有執(zhí)行時就輸入b,我們看看會發(fā)生什么,可以看到,它并不會考慮后來的任務(wù):

6.2 采用restartLoader查詢

還是按照前面的方式,我們先輸入a


這時候,和采用initLoader的結(jié)果完全相同,接下來再輸入b

可以清楚地看到,在執(zhí)行完restartLoader之后,LoaderManager回調(diào)了onCreateLoader方法讓我們傳入新的Loader,并且之后重新進行了查詢,并成功地返回了結(jié)果。
接下來,我們看一下連續(xù)輸入的情況:

可以看到,雖然a觸發(fā)的任務(wù)已經(jīng)開始了,但是當(dāng)我們輸入b的時候,最終得到的時ab這個結(jié)果,并且a所觸發(fā)的任務(wù)的結(jié)果并沒有返回給調(diào)用者,這也是我們所希望的,因為我們的結(jié)果一定是要以用戶最后輸入的為準(zhǔn)。

6.3 加上保證正確初始化的代碼

最后,我們再加上保證Loader能夠正確初始化的代碼,一個簡單的聯(lián)想輸入 - 加載框架就搭建好了。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        restore(savedInstanceState);
    }
    
    private void save(Bundle outState) {
        if (mEditText != null) {
            outState.putString(QUERY, mEditText.getText().toString());
        }
    }
    
    private void restore(Bundle savedInstanceState) {
        if (savedInstanceState != null) {
            Bundle bundle = new Bundle();
            String query = bundle.getString(QUERY);
            bundle.putString(QUERY, query);
            getLoaderManager().initLoader(0, bundle, mMyLoaderCallback);
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        save(outState);
        super.onSaveInstanceState(outState);
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評論 19 139
  • 1 背景## 在Android中任何耗時的操作都不能放在UI主線程中,所以耗時的操作都需要使用異步實現(xiàn)。同樣的,在...
    我是昵稱閱讀 1,343評論 0 3
  • 一、概述 剛開始學(xué)習(xí)Loader的時候,只是使用CursorLoader把它當(dāng)作加載封裝在ContentProvi...
    澤毛閱讀 10,278評論 4 8
  • Android開發(fā)者都經(jīng)歷過APP UI開發(fā)不當(dāng) 會造成overDraw,導(dǎo)致APP UI渲染過慢,但是很多人卻沒...
    Tamic閱讀 16,199評論 30 104
  • 我不會老去 在未見到你之前 云淡風(fēng)輕 天色也不晚 我可以先泡上一杯青茶 在茶色氤氳中 翻開一座城 城中人相見,歡笑...
    仲童閱讀 281評論 0 5

友情鏈接更多精彩內(nèi)容