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

1、概述

在前面的一篇文章中,咱們分析了LoaderManager的實現,裏面涉及到了不少的細節問題,咱們並不須要刻意地記住每一個流程,之因此須要分析,之後在使用的過程當中,若是遇到問題了,再去查看相關的源代碼就行了。 對於Loader框架的理解,我認爲掌握如下四個方面的東西就能夠了:bash

  • LoaderManager的實現原理有一個大概的印象。
  • LoaderManager的三個關鍵方法initLoader/restartLoader/destroyLoader的使用場景。
  • 學會自定義Loader來實現數據的異步加載。
  • 總結一些App開發中經常使用的場景。

第一點能夠參考前面的這篇文章:框架

Loader框架 - LoaderManager初探異步

今天這篇,咱們將專一於分析第二點:initLoader/restartLoader的區別ide

2、基本概念的區別

首先,咱們回顧一下,對於init/restart的定義:函數

  • initLoaderui

  • 經過調用這個方法來初始化一個特定IDLoader,若是當前已經有一個和這個ID關聯的Loader,那麼並不會去回調onCreateLoader來通知使用者傳入一個新的 Loader實例替代那個舊的實例,僅僅是替代Callback,也就是說,上面的Bundle參數被丟棄了;而假如不存在一個關聯的實例,那麼會進行初始化,並啓動它。spa

  • 這個方法一般是在Activity/Fragment的初始化階段調用,由於它會判斷以前是否存在相同的Loader,這樣咱們就能夠複用以前已經加載過的數據,當屏幕裝轉致使Activity重建的時候,咱們就不須要再去從新建立一個新的Loader,也免去了從新讀取數據的過程。rest

  • restartLoadercode

  • 調用這個方法,將會從新建立一個指定IDLoader,若是當前已經有一個和這個ID關聯的Loader,那麼會對它進行canceled/stopped/destroyed等操做,以後,使用新傳入的Bundle參數來建立一個新的Loader,並在數據加載完畢後遞交給調用者。xml

  • 而且,在調用完這個函數以後,全部以前和這個ID關聯的Loader都會失效,咱們將不會收到它們傳遞過來的任何數據。

總結下來就是:

  • 當調用上面這兩個方法時,若是不存在一個和ID關聯的Loader,那麼這兩個方法是徹底相同的。
  • 若是已經存在相關聯的Loader,那麼init方法除了替代Callback,不會作任何其它的事情,包括取消/中止等。而restart方法將會建立一個新的Loader,而且從新開始查詢。

3、代碼的區別

爲了方便你們更直觀地理解,咱們截取一部分的源碼來看一下:

  • initLoader
LoaderInfo info = mLoaders.get(id);
if (info == null) {
    //若是不存在關聯的Loader,那麼建立一個新的Loader
    info = createAndInstallLoader(id, args, LoaderManager.LoaderCallbacks<Object>)callback);
} else {
   //若是已經存在,那麼僅僅替代Callback,傳入的Bundle參數會被丟棄。
   info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}
複製代碼
  • restartLoader
LoaderInfo info = mLoaders.get(id);
//若是已經存在一個相關聯的Loader,那麼執行操做。
if (info != null) {
    //mInactiveLoaders列表就是用來跟蹤那些已經被拋棄的Loader
    LoaderInfo inactive = mInactiveLoaders.get(id);
    if (inactive != null) {
        //對跟蹤列表進行一系列的操做。
    } else {
        //取消被拋棄的Loader,並加入到跟蹤列表當中,以便在新的Loader完成任務以後銷燬它。
        info.mLoader.abandon();
        mInactiveLoaders.put(id, info);
    }
}
//通知調用者,建立一個新的Loader,這個Loader將會和新的Bundle和Callback相關聯。
info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
複製代碼

經過上面的代碼,就印證了前面第二節咱們的說話,咱們根據這些區別能夠知道它們各自的職責:

  • initLoader是用來確保Loader可以被初始化,若是已經存在相同IDLoader,那麼它會複用以前的。
  • restartLoader的應用場景則是咱們的查詢條件發生了改變。由於LoaderManager是用ID關聯的,當這個Loader已經獲取到了數據,那麼就不須要再啓動它了。所以當咱們的需求發生了改變,就須要從新建立一個Loader

也就是說:

  • 查詢條件一直不變時,使用initLoader
  • 查詢條件有可能發生改變時,採用restartLoader

5、對於屏幕旋轉的狀況

5.1 重建

當咱們在Manifest.xml沒有給Activity配置configChanged的時候,旋轉屏幕會致使的Activity/Fragment重建,這時候有兩點須要注意的:

  • 因爲此時咱們的查詢條件並不會發生改變,而且LoaderManager會幫咱們恢復Loader的狀態。所以,咱們沒有必要再去調用restartLoader來從新建立Loader來執行一次耗時的查詢操做。

  • LoaderManager雖然會恢復Loader,可是它不會保存Callback實例,所以,若是咱們但願在旋轉完以後得到數據,那麼至少要調用一次initLoader來傳入一個新的Callback進行監聽。

在這種狀況下,假如咱們在旋轉以前Loader已經加載數據完畢了,那麼onLoadFinished會當即被會調

5.2 沒有重建

當沒有重建時,不會走onCreate方法,所以須要在別的地方來初始化Loader

5.3 LoaderId

針對上面的這兩種狀況,咱們都須要本身去保存LoaderId,在組件恢復以後,經過這個保存的id去調用init/restart方法,通常狀況下,是經過savedInstanceState來保存的。

6、示例

如今,咱們經過一個很簡單的例子,來看一下,initLoader/restartLoader的區別,咱們的Demo中有一個EditText和一個TextView,當EditText發生改變時,咱們將當前EditText的內容做爲查詢的Key,查詢任務就是調用Loader,延時2s,並將這個key做爲查詢的結果展現在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) {}
    }

}
複製代碼

當咱們輸入a時,成功地獲取到了數據:

以後,咱們繼續輸入 b,發現 onLoadFinished當即被回調了,可是結果仍是以前地 a
咱們經過 AS的斷電發現,整個調用過程以下:
也就是在 initLoader以後,當即執行了:

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就會把以前的數據傳回給調用者,所以沒有執行onCreateLoader,也沒有進行查詢操做:

void callOnLoadFinished(Loader<Object> loader, Object data) {
            //傳遞數據給調用者.
            if (mCallbacks != null) {
                mCallbacks.onLoadFinished(loader, data);
            }
        }
複製代碼

假如,咱們在a觸發的任務尚未執行時就輸入b,咱們看看會發生什麼,能夠看到,它並不會考慮後來的任務:

6.2 採用restartLoader查詢

仍是按照前面的方式,咱們先輸入a

這時候,和採用 initLoader的結果徹底相同,接下來再輸入 b
能夠清楚地看到,在執行完 restartLoader以後, LoaderManager回調了 onCreateLoader方法讓咱們傳入新的 Loader,而且以後從新進行了查詢,併成功地返回告終果。 接下來,咱們看一下連續輸入的狀況:
能夠看到,雖然 a觸發的任務已經開始了,可是當咱們輸入 b的時候,最終獲得的時 ab這個結果,而且 a所觸發的任務的結果並無返回給調用者,這也是咱們所但願的,由於咱們的結果必定是要以用戶最後輸入的爲準。

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

最後,咱們再加上保證Loader可以正確初始化的代碼,一個簡單的聯想輸入 - 加載框架就搭建好了。

@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);
    }
複製代碼
相關文章
相關標籤/搜索