一芋浮、概述
在前面的一篇文章中絮蒿,我們分析了LoaderManager
的實(shí)現(xiàn)尊搬,里面涉及到了很多的細(xì)節(jié)問(wèn)題,我們并不需要刻意地記住每個(gè)流程土涝,之所以需要分析佛寿,以后在使用的過(guò)程中,如果遇到問(wèn)題了但壮,再去查看相關(guān)的源代碼就好了冀泻。
對(duì)于Loader
框架的理解,我認(rèn)為掌握以下四個(gè)方面的東西就可以了:
- 對(duì)
LoaderManager
的實(shí)現(xiàn)原理有一個(gè)大概的印象蜡饵。 -
LoaderManager
的三個(gè)關(guān)鍵方法initLoader/restartLoader/destroyLoader
的使用場(chǎng)景弹渔。 - 學(xué)會(huì)自定義
Loader
來(lái)實(shí)現(xiàn)數(shù)據(jù)的異步加載。 - 總結(jié)一些
App
開發(fā)中常用的場(chǎng)景溯祸。
第一點(diǎn)可以參考前面的這篇文章:
今天這篇肢专,我們將專注于分析第二點(diǎn):initLoader/restartLoader
的區(qū)別。
二焦辅、基本概念的區(qū)別
首先博杖,我們回顧一下,對(duì)于init/restart
的定義:
initLoader
通過(guò)調(diào)用這個(gè)方法來(lái)初始化一個(gè)特定
ID
的Loader
筷登,如果當(dāng)前已經(jīng)有一個(gè)和這個(gè)ID
關(guān)聯(lián)的Loader
剃根,那么并不會(huì)去回調(diào)onCreateLoader
來(lái)通知使用者傳入一個(gè)新的Loader
實(shí)例替代那個(gè)舊的實(shí)例,僅僅是替代Callback
前方,也就是說(shuō)狈醉,上面的Bundle
參數(shù)被丟棄了;而假如不存在一個(gè)關(guān)聯(lián)的實(shí)例惠险,那么會(huì)進(jìn)行初始化苗傅,并啟動(dòng)它。這個(gè)方法通常是在
Activity/Fragment
的初始化階段調(diào)用班巩,因?yàn)樗鼤?huì)判斷之前是否存在相同的Loader
渣慕,這樣我們就可以復(fù)用之前已經(jīng)加載過(guò)的數(shù)據(jù),當(dāng)屏幕裝轉(zhuǎn)導(dǎo)致Activity
重建的時(shí)候趣竣,我們就不需要再去重新創(chuàng)建一個(gè)新的Loader
,也免去了重新讀取數(shù)據(jù)的過(guò)程旱物。restartLoader
調(diào)用這個(gè)方法遥缕,將會(huì)重新創(chuàng)建一個(gè)指定
ID
的Loader
,如果當(dāng)前已經(jīng)有一個(gè)和這個(gè)ID
關(guān)聯(lián)的Loader
宵呛,那么會(huì)對(duì)它進(jìn)行canceled/stopped/destroyed
等操作单匣,之后,使用新傳入的Bundle
參數(shù)來(lái)創(chuàng)建一個(gè)新的Loader
,并在數(shù)據(jù)加載完畢后遞交給調(diào)用者户秤。并且码秉,在調(diào)用完這個(gè)函數(shù)之后,所有之前和這個(gè)
ID
關(guān)聯(lián)的Loader
都會(huì)失效鸡号,我們將不會(huì)收到它們傳遞過(guò)來(lái)的任何數(shù)據(jù)转砖。
總結(jié)下來(lái)就是:
- 當(dāng)調(diào)用上面這兩個(gè)方法時(shí),如果不存在一個(gè)和
ID
關(guān)聯(lián)的Loader
鲸伴,那么這兩個(gè)方法是完全相同的府蔗。 - 如果已經(jīng)存在相關(guān)聯(lián)的
Loader
,那么init
方法除了替代Callback
汞窗,不會(huì)做任何其它的事情姓赤,包括取消/停止等。而restart
方法將會(huì)創(chuàng)建一個(gè)新的Loader
仲吏,并且重新開始查詢不铆。
三、代碼的區(qū)別
為了方便大家更直觀地理解裹唆,我們截取一部分的源碼來(lái)看一下:
initLoader
LoaderInfo info = mLoaders.get(id);
if (info == null) {
//如果不存在關(guān)聯(lián)的Loader誓斥,那么創(chuàng)建一個(gè)新的Loader
info = createAndInstallLoader(id, args, LoaderManager.LoaderCallbacks<Object>)callback);
} else {
//如果已經(jīng)存在,那么僅僅替代Callback品腹,傳入的Bundle參數(shù)會(huì)被丟棄岖食。
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}
restartLoader
LoaderInfo info = mLoaders.get(id);
//如果已經(jīng)存在一個(gè)相關(guān)聯(lián)的Loader,那么執(zhí)行操作舞吭。
if (info != null) {
//mInactiveLoaders列表就是用來(lái)跟蹤那些已經(jīng)被拋棄的Loader
LoaderInfo inactive = mInactiveLoaders.get(id);
if (inactive != null) {
//對(duì)跟蹤列表進(jìn)行一系列的操作泡垃。
} else {
//取消被拋棄的Loader,并加入到跟蹤列表當(dāng)中羡鸥,以便在新的Loader完成任務(wù)之后銷毀它蔑穴。
info.mLoader.abandon();
mInactiveLoaders.put(id, info);
}
}
//通知調(diào)用者,創(chuàng)建一個(gè)新的Loader惧浴,這個(gè)Loader將會(huì)和新的Bundle和Callback相關(guān)聯(lián)存和。
info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
通過(guò)上面的代碼,就印證了前面第二節(jié)我們的說(shuō)話衷旅,我們根據(jù)這些區(qū)別可以知道它們各自的職責(zé):
-
initLoader
是用來(lái)確保Loader
能夠被初始化捐腿,如果已經(jīng)存在相同ID
的Loader
,那么它會(huì)復(fù)用之前的柿顶。 -
restartLoader
的應(yīng)用場(chǎng)景則是我們的查詢條件發(fā)生了改變茄袖。因?yàn)?code>LoaderManager是用ID
關(guān)聯(lián)的,當(dāng)這個(gè)Loader
已經(jīng)獲取到了數(shù)據(jù)嘁锯,那么就不需要再啟動(dòng)它了宪祥。因此當(dāng)我們的需求發(fā)生了改變聂薪,就需要重新創(chuàng)建一個(gè)Loader
。
也就是說(shuō):
- 查詢條件一直不變時(shí)蝗羊,使用
initLoader
- 查詢條件有可能發(fā)生改變時(shí)藏澳,采用
restartLoader
。
五耀找、對(duì)于屏幕旋轉(zhuǎn)的情況
5.1 重建
當(dāng)我們?cè)?code>Manifest.xml沒(méi)有給Activity
配置configChanged
的時(shí)候翔悠,旋轉(zhuǎn)屏幕會(huì)導(dǎo)致的Activity/Fragment
重建,這時(shí)候有兩點(diǎn)需要注意的:
由于此時(shí)我們的查詢條件并不會(huì)發(fā)生改變涯呻,并且
LoaderManager
會(huì)幫我們恢復(fù)Loader
的狀態(tài)凉驻。因此,我們沒(méi)有必要再去調(diào)用restartLoader
來(lái)重新創(chuàng)建Loader
來(lái)執(zhí)行一次耗時(shí)的查詢操作复罐。LoaderManager
雖然會(huì)恢復(fù)Loader
涝登,但是它不會(huì)保存Callback
實(shí)例,因此效诅,如果我們希望在旋轉(zhuǎn)完之后獲得數(shù)據(jù)胀滚,那么至少要調(diào)用一次initLoader
來(lái)傳入一個(gè)新的Callback
進(jìn)行監(jiān)聽。
在這種情況下乱投,假如我們?cè)谛D(zhuǎn)之前Loader
已經(jīng)加載數(shù)據(jù)完畢了咽笼,那么onLoadFinished
會(huì)立即被會(huì)調(diào)。
5.2 沒(méi)有重建
當(dāng)沒(méi)有重建時(shí)戚炫,不會(huì)走onCreate
方法剑刑,因此需要在別的地方來(lái)初始化Loader
。
5.3 LoaderId
針對(duì)上面的這兩種情況双肤,我們都需要自己去保存LoaderId
施掏,在組件恢復(fù)之后,通過(guò)這個(gè)保存的id
去調(diào)用init/restart
方法茅糜,一般情況下七芭,是通過(guò)savedInstanceState
來(lái)保存的。
六蔑赘、示例
現(xiàn)在狸驳,我們通過(guò)一個(gè)很簡(jiǎn)單的例子,來(lái)看一下缩赛,initLoader/restartLoader
的區(qū)別耙箍,我們的Demo
中有一個(gè)EditText
和一個(gè)TextView
,當(dāng)EditText
發(fā)生改變時(shí)酥馍,我們將當(dāng)前EditText
的內(nèi)容作為查詢的Key
辩昆,查詢?nèi)蝿?wù)就是調(diào)用Loader
,延時(shí)2s
物喷,并將這個(gè)key
作為查詢的結(jié)果展示在TextView
上卤材。
6.1 采用initLoader
來(lái)查詢:
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í),成功地獲取到了數(shù)據(jù):
之后峦失,我們繼續(xù)輸入
b
扇丛,發(fā)現(xiàn)onLoadFinished
立即被回調(diào)了,但是結(jié)果還是之前地a
我們通過(guò)
AS
的斷電發(fā)現(xiàn)尉辑,整個(gè)調(diào)用過(guò)程如下:也就是在
initLoader
之后帆精,立即執(zhí)行了:
public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
//按照前面的分析,此時(shí)的info不為null隧魄。
if (info.mHaveData && mStarted) {
info.callOnLoadFinished(info.mLoader, info.mData);
}
return (Loader<D>)info.mLoader;
}
而之后卓练,callOnLoadFinished
就會(huì)把之前的數(shù)據(jù)傳回給調(diào)用者,因此沒(méi)有執(zhí)行onCreateLoader
购啄,也沒(méi)有進(jìn)行查詢操作:
void callOnLoadFinished(Loader<Object> loader, Object data) {
//傳遞數(shù)據(jù)給調(diào)用者.
if (mCallbacks != null) {
mCallbacks.onLoadFinished(loader, data);
}
}
假如襟企,我們?cè)?code>a觸發(fā)的任務(wù)還沒(méi)有執(zhí)行時(shí)就輸入b
,我們看看會(huì)發(fā)生什么狮含,可以看到顽悼,它并不會(huì)考慮后來(lái)的任務(wù):
6.2 采用restartLoader
查詢
還是按照前面的方式别凹,我們先輸入a
:
這時(shí)候吐句,和采用
initLoader
的結(jié)果完全相同,接下來(lái)再輸入b
:可以清楚地看到伐债,在執(zhí)行完
restartLoader
之后映胁,LoaderManager
回調(diào)了onCreateLoader
方法讓我們傳入新的Loader
木羹,并且之后重新進(jìn)行了查詢,并成功地返回了結(jié)果解孙。接下來(lái)坑填,我們看一下連續(xù)輸入的情況:
可以看到,雖然
a
觸發(fā)的任務(wù)已經(jīng)開始了妆距,但是當(dāng)我們輸入b
的時(shí)候穷遂,最終得到的時(shí)ab
這個(gè)結(jié)果,并且a
所觸發(fā)的任務(wù)的結(jié)果并沒(méi)有返回給調(diào)用者娱据,這也是我們所希望的蚪黑,因?yàn)槲覀兊慕Y(jié)果一定是要以用戶最后輸入的為準(zhǔn)。
6.3 加上保證正確初始化的代碼
最后中剩,我們?cè)偌由媳WCLoader
能夠正確初始化的代碼忌穿,一個(gè)簡(jiǎn)單的聯(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);
}