一、概述
最近對 googlesamples/android-architecture 中的 MVP-dagger 進行了學習。對照項目的 MVP-dagger 分支,對 MVP-dagger 進行了實踐,不日將會在另一篇文章中進行介紹。
MVP 架構,顧名思義,Model-View-Presenter。其作用是解決 Android 的 MVC 架構中,Activity 的職責不清,過於龐雜,難以維護的缺點。
在眾多對 MVP 的實踐中,Presenter 常有 attachView 和 unattachView 兩個方法,用以建立起 Presenter 同 View 的聯系,便於在 Presenter 中對 View 的接口進行調用。
然而,Presenter 中常常有一些耗時的操作,在某些情況下(諸如用戶退出對應的 View),unAttachView 被調用,此時 Presenter 才完成耗時操作,需要完成對 View 的更新。但此時由於 View 已經被解綁,Presenter 中獲取到的 View 為空,若不進行判空操作,則會引起空指針異常。
在 Presenter 中如何優雅地判空?通過進一步了解 Presenter 的生命周期,能不能找到更好的解決方案?這兩個問題是本文要討論的重點!
二、Presenter 中對 View 判空
2.1 最簡單直接的暴力方式
public class TopicDetailPresenter extends RxPresenter<TopicDetailContract.View> implements TopicDetailContract.Presenter {
private RetrofitHelper mRetrofitHelper;
@Inject
TopicDetailPresenter(RetrofitHelper retrofitHelper) {
this.mRetrofitHelper = retrofitHelper;
}
@Override
public void getTopicDetailData(int topicId) {
mRetrofitHelper.fetchTopicNewsList(topicId).enqueue(new Callback<TopicStoryListBean>() {
@Override
public void onResponse(Call<TopicStoryListBean> call, Response<TopicStoryListBean> response) {
if (response.isSuccessful()) {
mView.showContent(response.body());
}
}
@Override
public void onFailure(Call<TopicStoryListBean> call, Throwable t) {
mView.showError(t.getMessage());
}
});
}
}
在上面這個例子中,並沒有對 mView 進行判空,當網絡狀態不好,用戶退出當前 Presenter 關聯的 View,就極容易引起空指針異常。
為了避免此問題的出現,應當對 mView 進行判空操作。
if(mView != null) mView.showContent(response.body());
...
if(mView != null) mView.showError(t.getMessage());
這是最直接了當的做法。倘若對整個項目進行如是改造,且不說編碼規范和設計原則的問題,單是修改整個項目的 Presenter 就得費老鼻子勁兒,修改過程也極容易出現遺漏等問題。
暴力××不可取呀!!!
2.2 整合抽象的方式
當然了,上面的代碼在代碼規范和設計模式上也有一定的問題。一種更佳的方式是,不直接讓子 Presenter 對 mView 進行操作,而是使用 getView 方法對 mView 進行暴露,用戶使用 getView 獲取綁定的 view
if(getView() != null) getView().showContent(response.body());
...
if(getView() != null) getView().showError(t.getMessage());
2.3 優雅的判空方式
來自知乎專欄的一片文章 『極光日報 - 不要再在你的 Presenter 中檢查 view != null 啦』 中介紹了一種優雅的方式,拋異常/使用第三方庫。
有另一種我認為更好的方式,就是將 2.2 與 拋異常結合起來。畢竟,誰都不想為了一些小的細節,而引入一個第三方庫。
public class TopicDetailPresenter extends RxPresenter<TopicDetailContract.View> implements TopicDetailContract.Presenter {
private RetrofitHelper mRetrofitHelper;
@Inject
TopicDetailPresenter(RetrofitHelper retrofitHelper) {
this.mRetrofitHelper = retrofitHelper;
}
@Override
public void getTopicDetailData(int topicId) {
mRetrofitHelper.fetchTopicNewsList(topicId).enqueue(new Callback<TopicStoryListBean>() {
@Override
public void onResponse(Call<TopicStoryListBean> call, Response<TopicStoryListBean> response) {
if (response.isSuccessful()) {
getView().showContent(response.body());
}
}
@Override
public void onFailure(Call<TopicStoryListBean> call, Throwable t) {
getView().showError(t.getMessage());
}
});
}
}
在父 Presenter 中
protected View getView(){
if(mView == null) throw new IllegaStateException("view not attached");
else return mView;
}
三、Presenter 的生命周期
這個話題源自一篇文章 『Android:聊聊 MVP 中 Presenter 的生命周期』
當然,這篇文章涵蓋了處理 Presenter 的生命周期 與 Activity/Fragment 生命周期同步的問題的幾個框架。同步 Presenter 和 Activity/Fragment 生命周期,從而保證在 View 層(這里姑且 Activity/Fragment 歸類到 View 層吧)生命結束后,Presenter 也被終止生命,故而避免了空指針異常的問題!
這里只引用文中的幾個框架,詳細分析內容,可參見原文!
-
Beam 框架
『不要再給MVP中Presenter寫接口了』觀點不敢苟同 -
Loader 框架
原文中詳細介紹了這種方案
此文在我的 Github Pages 上同步發布,地址為:Android MVP Presenter 中引發的空指針異常