在前面的一篇文章中,咱們分析了LoaderManager
的實現,裏面涉及到了不少的細節問題,咱們並不須要刻意地記住每一個流程,之因此須要分析,之後在使用的過程當中,若是遇到問題了,再去查看相關的源代碼就行了。 對於Loader
框架的理解,我認爲掌握如下四個方面的東西就能夠了:bash
LoaderManager
的實現原理有一個大概的印象。LoaderManager
的三個關鍵方法initLoader/restartLoader/destroyLoader
的使用場景。Loader
來實現數據的異步加載。App
開發中經常使用的場景。第一點能夠參考前面的這篇文章:框架
今天這篇,咱們將專一於分析第二點:initLoader/restartLoader
的區別。ide
首先,咱們回顧一下,對於init/restart
的定義:函數
initLoader
ui
經過調用這個方法來初始化一個特定ID
的Loader
,若是當前已經有一個和這個ID
關聯的Loader
,那麼並不會去回調onCreateLoader
來通知使用者傳入一個新的 Loader
實例替代那個舊的實例,僅僅是替代Callback
,也就是說,上面的Bundle
參數被丟棄了;而假如不存在一個關聯的實例,那麼會進行初始化,並啓動它。spa
這個方法一般是在Activity/Fragment
的初始化階段調用,由於它會判斷以前是否存在相同的Loader
,這樣咱們就能夠複用以前已經加載過的數據,當屏幕裝轉致使Activity
重建的時候,咱們就不須要再去從新建立一個新的Loader
,也免去了從新讀取數據的過程。rest
restartLoader
code
調用這個方法,將會從新建立一個指定ID
的Loader
,若是當前已經有一個和這個ID
關聯的Loader
,那麼會對它進行canceled/stopped/destroyed
等操做,以後,使用新傳入的Bundle
參數來建立一個新的Loader
,並在數據加載完畢後遞交給調用者。xml
而且,在調用完這個函數以後,全部以前和這個ID
關聯的Loader
都會失效,咱們將不會收到它們傳遞過來的任何數據。
總結下來就是:
ID
關聯的Loader
,那麼這兩個方法是徹底相同的。Loader
,那麼init
方法除了替代Callback
,不會作任何其它的事情,包括取消/中止等。而restart
方法將會建立一個新的Loader
,而且從新開始查詢。爲了方便你們更直觀地理解,咱們截取一部分的源碼來看一下:
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
可以被初始化,若是已經存在相同ID
的Loader
,那麼它會複用以前的。restartLoader
的應用場景則是咱們的查詢條件發生了改變。由於LoaderManager
是用ID
關聯的,當這個Loader
已經獲取到了數據,那麼就不須要再啓動它了。所以當咱們的需求發生了改變,就須要從新建立一個Loader
。也就是說:
- 查詢條件一直不變時,使用
initLoader
restartLoader
。當咱們在Manifest.xml
沒有給Activity
配置configChanged
的時候,旋轉屏幕會致使的Activity/Fragment
重建,這時候有兩點須要注意的:
因爲此時咱們的查詢條件並不會發生改變,而且LoaderManager
會幫咱們恢復Loader
的狀態。所以,咱們沒有必要再去調用restartLoader
來從新建立Loader
來執行一次耗時的查詢操做。
LoaderManager
雖然會恢復Loader
,可是它不會保存Callback
實例,所以,若是咱們但願在旋轉完以後得到數據,那麼至少要調用一次initLoader
來傳入一個新的Callback
進行監聽。
在這種狀況下,假如咱們在旋轉以前Loader
已經加載數據完畢了,那麼onLoadFinished
會當即被會調。
當沒有重建時,不會走onCreate
方法,所以須要在別的地方來初始化Loader
。
LoaderId
針對上面的這兩種狀況,咱們都須要本身去保存LoaderId
,在組件恢復以後,經過這個保存的id
去調用init/restart
方法,通常狀況下,是經過savedInstanceState
來保存的。
如今,咱們經過一個很簡單的例子,來看一下,initLoader/restartLoader
的區別,咱們的Demo
中有一個EditText
和一個TextView
,當EditText
發生改變時,咱們將當前EditText
的內容做爲查詢的Key
,查詢任務就是調用Loader
,延時2s
,並將這個key
做爲查詢的結果展現在TextView
上。
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
,咱們看看會發生什麼,能夠看到,它並不會考慮後來的任務:
restartLoader
查詢仍是按照前面的方式,咱們先輸入a
:
initLoader
的結果徹底相同,接下來再輸入
b
:
restartLoader
以後,
LoaderManager
回調了
onCreateLoader
方法讓咱們傳入新的
Loader
,而且以後從新進行了查詢,併成功地返回告終果。 接下來,咱們看一下連續輸入的狀況:
a
觸發的任務已經開始了,可是當咱們輸入
b
的時候,最終獲得的時
ab
這個結果,而且
a
所觸發的任務的結果並無返回給調用者,這也是咱們所但願的,由於咱們的結果必定是要以用戶最後輸入的爲準。
最後,咱們再加上保證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);
}
複製代碼