通過 Andriod---MVP學(xué)習(xí)之路(二)亲雪,我們通過attachview和detachView很好解決了空指針問題。現(xiàn)在還有一個(gè)問題惨好,如果手機(jī)屏幕旋轉(zhuǎn)等,觸發(fā)了activity的重啟随闺,該怎么處理Presenter呢日川?
有如下幾種方法:
通過onSaveInstanceState恢復(fù)數(shù)據(jù)
通過onSaveInstanceState保存界面和presenter的數(shù)據(jù)。橫豎屏切換后通鍋onCreated中的onSaveInstanceState恢復(fù)數(shù)據(jù)
將Presenter保存在一個(gè)地方板壮,再次onCreate時(shí)還原
很多實(shí)現(xiàn)方法
最佳方案逗鸣,通過Loader延長Presenter生命周期
什么是Loader合住?它有什么用绰精?
我們都知道,當(dāng)手機(jī)狀態(tài)發(fā)生改變比如旋轉(zhuǎn)時(shí)透葛,Activity會(huì)重新啟動(dòng)笨使。Loader是Android框架中提供的在手機(jī)狀態(tài)改變時(shí)不會(huì)被銷毀的工具。Loader的生命周期是是由系統(tǒng)控制的僚害,只有在向Loader請求數(shù)據(jù)的Activity/Fragment被永久銷毀時(shí)才會(huì)被清除硫椰,所以也不需要自己寫代碼來清空它。
一般Loader是用來在后臺(tái)加載數(shù)據(jù)的萨蚕,而且是用它的子類CursorLoader或AsyncTaskLoader靶草,尤其是CursorLoader,直接就綁定了Content Provider和數(shù)據(jù)庫岳遥。當(dāng)然如果寫個(gè)類繼承Loader基類的話也不需要開啟后臺(tái)線程奕翔。
聽起來好厲害。但這和Presenter有什么關(guān)系浩蓉?
就像剛才說的一樣派继,關(guān)鍵問題就是在哪里存儲(chǔ)Presenter以及什么時(shí)候銷毀它們。而我們剛剛就看到了Loader的強(qiáng)大之處:由安卓系統(tǒng)框架提供捻艳,有單獨(dú)生命周期驾窟,會(huì)被自動(dòng)回收且不必在后臺(tái)運(yùn)行。
所以思考一下需求以及Loader的功能认轨,我們可以讓Loader作為Presenter的提供者绅络,而不需要擔(dān)心手機(jī)狀態(tài)改變。
將同步的Loader作為存放Presenter的緩存嘁字。
這里的重點(diǎn)就在于同步使用Loader時(shí)昨稼,我們可以知道在生命周期的哪個(gè)階段Presenter被創(chuàng)建了并且可以工作了。甚至是在Activity/Fragment可見之前拳锚。
要注意的是假栓,Activity和Fragment在何時(shí)傳遞Presenter對象這個(gè)問題上是有區(qū)別的。對于任何一個(gè)Activity實(shí)例霍掺,只需要在調(diào)用super.onStart()之后就可以使用Presenter了匾荆,但對于Fragment實(shí)例來說拌蜘,首次創(chuàng)建時(shí)可以在super.onStart()之后傳入,但在Fragment被重新create時(shí)牙丽,就必須要在super.onResume()之后傳入了简卧。所以在Fragment中,只需要在onResume()方法執(zhí)行后將Presenter傳入就行了烤芦。
使這種方法可行的另外一個(gè)要點(diǎn)就是系統(tǒng)對Loader的處理方式举娩,每一個(gè)Activity/Fragment都有一個(gè)LoaderManager,而且只有這個(gè)LoaderManager可以管理與Activity/Fragment相關(guān)聯(lián)的Loader构罗,這就使得相同的Fragment與其Presenter可以同時(shí)存在多個(gè)實(shí)例铜涉。
上代碼,繼上篇文章中的項(xiàng)目代碼遂唧,進(jìn)行改造芙代。
先看以下步驟完成后的項(xiàng)目目錄:
步驟1:創(chuàng)建PresenterLoader
public class PresenterLoader<T extends BaseMVPPresenterImp> extends Loader<T> {
public static String TAG = "PresenterLoader";
private final PresenterFactory<T> factory;
private T presenter;
public PresenterLoader(Context context, PresenterFactory factory) {
super(context);
this.factory = factory;
}
@Override
protected void onStartLoading() {
Log.i(TAG, "hpdhpd11 PresenterLoader onStartLoading");
if (presenter != null) {
// 如果已經(jīng)有Presenter,就直接返回盖彭,其實(shí)不用這個(gè)方法也行纹烹,因?yàn)榉祷氐氖峭粋€(gè)對象
//deliverResult(presenter)不會(huì)觸發(fā)onLoadFinished。
deliverResult(presenter);
return;
}
// 如果沒有召边,需要執(zhí)行onForceLoad
forceLoad();
}
@Override
protected void onForceLoad() {
Log.i(TAG, "hpdhpd11 PresenterLoader onForceLoad");
// 通過工廠來實(shí)例化Presenter
presenter = factory.create();
// 返回Presenter
deliverResult(presenter);
}
@Override
protected void onReset() {
Log.i(TAG, "hpdhpd11 PresenterLoader onReset");
presenter = null;
}
}
onStartLoading():會(huì)在Activity的onStart()調(diào)用之后被系統(tǒng)調(diào)用來獲取一個(gè)Loader實(shí)例铺呵。在這里先判斷是否已經(jīng)有Presenter對象了還是需要?jiǎng)?chuàng)建。
onForceLoad():在調(diào)用forceLoad()方法后自動(dòng)調(diào)用隧熙,我們在這個(gè)方法中創(chuàng)建Presenter并返回它片挂。
deliverResult():會(huì)將Presenter傳遞給Activity/Fragment。
onReset():會(huì)在Loader被銷毀之前調(diào)用贱鼻,我們可以在這里告知Presenter以終止某些操作或進(jìn)行清理工作宴卖。
PresenterFactory:這個(gè)接口可以隱藏創(chuàng)建Presenter所需要的參數(shù)。通過這個(gè)接口我們可以調(diào)用各種構(gòu)造器邻悬,這樣可以避免寫一堆PresenterLoader的子類來返回不同類型的Presenter症昏。
這個(gè)接口形式上大概就是這樣:
public interface PresenterFactory<T extends BaseMVPPresenterImp> {
T create();
}
在Activity/Fragment中如何實(shí)現(xiàn)呢?
現(xiàn)在我們已經(jīng)實(shí)現(xiàn)了Loader父丰,下面要將Loader和Activity/Fragment連接起來肝谭,剛才說過,這個(gè)連接點(diǎn)就是LoaderManager蛾扇。我們需要調(diào)用FragmentActivity的getSupportLoaderManager()或Fragment的getLoaderManager()方法來獲得LoaderManager實(shí)例并調(diào)用其initLoader()方法攘烛。Google建議在Activity的onCreate()與Fragment的onActivityCreated()中調(diào)用此方法。
當(dāng)調(diào)用initLoader()方法時(shí)要傳入一個(gè)id镀首,只需要保證在一個(gè)Activity/Fragment內(nèi)單一即可坟漱,不需要全局單一。這個(gè)id就是用來識(shí)別Loader的更哄。還可以選擇傳入一個(gè)Bundle芋齿,但在這個(gè)例子中不需要腥寇。還要穿入一個(gè)LoaderCallbacks實(shí)例。
剛才說過觅捆,不要再Fragment的onCreate()方法中調(diào)用initLoader()方法赦役,要在onActivityCreated()中調(diào)用它,不然就會(huì)遇到不同F(xiàn)ragment共享一個(gè)Loader的問題栅炒。
如果你發(fā)現(xiàn)在手機(jī)狀態(tài)改變時(shí)onLoadFinished()會(huì)被調(diào)用兩次掂摔,不妨參考stackoverflow下的這個(gè)問題。Presenter在被傳入Activity后的邏輯可能會(huì)使這個(gè)成為一個(gè)問題赢赊,此時(shí)可以嘗試在Fragment的onResume()方法中調(diào)用initLoader()方法乙漓,或者在onActivityCreate()方法中存一個(gè)flag以防多次獲取Presenter。
當(dāng)我們調(diào)用了initLoader()后Loader和Activity/Fragment的生命周期就綁定了:執(zhí)行onStart()方法時(shí)會(huì)調(diào)用onStartLoading()域携,執(zhí)行onStop()方法時(shí)會(huì)調(diào)用onStopLoading()簇秒。但onReset()方法只會(huì)在Activity/Fragment被銷毀或主動(dòng)調(diào)用destroyLoader()時(shí)被調(diào)用鱼喉。
LoaderManager有一個(gè)restartLoader()方法可以強(qiáng)制重新加載秀鞭。不過除非我們需要重新創(chuàng)建Presenter,不然不需要調(diào)用這個(gè)方法扛禽。
通過LoaderCallbacks獲取Presenter
LoaderCallbacks是Activity/Fragment和Loader之間的橋梁锋边。共有三個(gè)回調(diào)方法:
onCreateLoader():在這里構(gòu)造Loader實(shí)例。
onLoadFinished():Loader在這里傳入數(shù)據(jù)编曼,在這個(gè)例子中豆巨,也就是Presenter。
onLoadReset():在這里清除對于數(shù)據(jù)的引用掐场。
修改BaseMVPActivity
public class BaseMVPActivity<P extends BaseMVPPresenterImp<V>, V extends BaseMVPViewInterface> extends HPDBaseActivity implements BaseMVPViewInterface, LoaderManager.LoaderCallbacks<P> {
public final static int BASE_LOADER_ID = 100;
protected ProgressDialog progressDialog;
protected P presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, "hpdhpd11 BaseMVPActivity onCreate");
progressDialog = new ProgressDialog(this);
getSupportLoaderManager().initLoader(BASE_LOADER_ID, null, this);
}
@Override
protected void onStart() {
super.onStart();
Log.i(TAG, "hpdhpd11 BaseMVPActivity onStart");
if (presenter != null) {
presenter.attachView((V) this);
}
}
@Override
protected void onDestroy() {
Log.i(TAG, "hpdhpd11 BaseMVPActivity onDestroy");
if (presenter != null) {
presenter.detachView();
}
super.onDestroy();
}
@Override
public void showLoading() {
progressDialog.setMessage("showLoading");
if (!progressDialog.isShowing()) {
progressDialog.show();
}
}
@Override
public void hideLoading() {
if (progressDialog.isShowing()) {
progressDialog.dismiss();
}
}
@Override
public void showFailError(String message) {
if (TextUtils.isEmpty(message)) {
message = "發(fā)生錯(cuò)誤";
}
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
@Override
public Loader<P> onCreateLoader(int id, Bundle args) {
return null;
}
@Override
public void onLoadFinished(Loader<P> loader, P data) {
Log.i(TAG, "hpdhpd11 BaseMVPActivity onLoadFinished");
presenter = data;
}
@Override
public void onLoaderReset(Loader<P> loader) {
Log.i(TAG, "hpdhpd11 BaseMVPActivity onLoaderReset");
presenter = null;
}
}
修改LoginActivity
public class LoginActivity extends BaseMVPActivity<LoginPresenter, LoginViewInterface> implements LoginViewInterface {
private EditText etName, etPassword;
private Button btnLogin, btnClear;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
Log.i(TAG, "hpdhpd11 LoginActivity onCreate");
initView();
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.i(TAG, "hpdhpd11 LoginActivity onDestroy");
}
@Override
public Loader<LoginPresenter> onCreateLoader(int id, Bundle args) {
return new PresenterLoader(this, new PresenterFactory<LoginPresenter>() {
@Override
public LoginPresenter create() {
Log.i(TAG, "hpdhpd11 LoginActivity onCreateLoader");
return new LoginPresenter();
}
});
}
private void initView() {
etName = findViewById(R.id.et_name);
etPassword = findViewById(R.id.et_password);
btnLogin = findViewById(R.id.btn_login);
btnClear = findViewById(R.id.btn_clear);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
presenter.login();
}
});
btnClear.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
presenter.clear();
}
});
}
@Override
public String getUserName() {
return etName.getText().toString().trim();
}
@Override
public String getUserPassword() {
return etPassword.getText().toString().trim();
}
@Override
public void clearName() {
etName.setText("");
}
@Override
public void clearPassword() {
etPassword.setText("");
}
@Override
public void go2OtherActivity() {
Toast.makeText(this, "go2OtherActivity", Toast.LENGTH_SHORT).show();
}
}
我們再看看打印的Log往扔,看看Log是怎么走的?
進(jìn)入這個(gè)LoginActivity后:可以看到熊户,onLoadFinished之后萍膛,也就是presenter已經(jīng)是綁定了。然后activity的onStart執(zhí)行了嚷堡, 就可以交互了
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onCreate
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 LoginActivity onCreate
com.example.hpd.hpdmvpdemo I/PresenterLoader: hpdhpd11 PresenterLoader onStartLoading
com.example.hpd.hpdmvpdemo I/PresenterLoader: hpdhpd11 PresenterLoader onForceLoad
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 LoginActivity onCreateLoader
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onLoadFinished
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onStart
當(dāng)我們關(guān)閉屏幕后蝗罗,再打開APP,看看Log是怎么走的蝌戒?
可以看到串塑,執(zhí)行了PresenterLoader的onStartLoading方法,就是去檢查presenter是否存在北苟,不存在則創(chuàng)建桩匪,存在則返回,因?yàn)榘l(fā)返回的是同一個(gè)對象友鼻,deliverResult(presenter)不會(huì)觸發(fā)onLoadFinished傻昙。
com.example.hpd.hpdmvpdemo I/PresenterLoader: hpdhpd11 PresenterLoader onStartLoading
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onStart
當(dāng)我們橫豎屏切換時(shí)瑟慈,看看Log是怎么走的?
可以看到,橫豎屏切換后屋匕,activity會(huì)重啟葛碧,getSupportLoaderManager().initLoader(BASE_LOADER_ID, null, this);執(zhí)行后过吻,因?yàn)橐呀?jīng)存在這個(gè)對應(yīng)ID的loader进泼,會(huì)直接執(zhí)行onLoadFinished(),返回這個(gè)presenter.
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onDestroy
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 LoginActivity onDestroy
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onCreate
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 LoginActivity onCreate
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onLoadFinished
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onStart
當(dāng)我們推出這個(gè)activity后纤虽,看看Log是怎么走的乳绕?
可以看到:activity后,會(huì)執(zhí)行onLoaderReset和onReset方法逼纸,我們可以在這個(gè)方法里面釋放presenter洋措。
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onDestroy
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onLoaderReset
com.example.hpd.hpdmvpdemo I/PresenterLoader: hpdhpd11 PresenterLoader onReset
com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 LoginActivity onDestroy
我們之后寫Activity的時(shí)候,可以直接使用繼承BaseMVPActivity,并指定泛型杰刽。如:
public class LoginActivity extends BaseMVPActivity<LoginPresenter, LoginViewInterface> implements LoginViewInterface
并且實(shí)現(xiàn)onCreateLoader方法菠发,如:
@Override
public Loader<LoginPresenter> onCreateLoader(int id, Bundle args) {
return new PresenterLoader(this, new PresenterFactory<LoginPresenter>() {
@Override
public LoginPresenter create() {
Log.i(TAG, "hpdhpd11 LoginActivity onCreateLoader");
return new LoginPresenter();
}
});
}
就不用再去管presenter的狀態(tài)。
參考文章:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0314/4050.html