Android架構設計之一:MVP
對於新入門或者以及有過一段開發經驗對 MVP 仍有困惑的 Android 開發者,這篇文章,希望你不要錯過。
本文主要講解了最基礎 MVP ,從 0 到 1 的實現過程,以及如何解決實現過程中遇到的問題。
簡介
關於 Android 架構,目前主要有 MVC、MVP、MVVM、模組化、元件化等。
1. MVC : Model - View - Controller
M:邏輯模型,V:檢視模型,C:控制器
但這種架構,相較於其他幾種,比較落後。 而且耦合性嚴重,
職責相對不夠明確不利於後期維護,
適用於小型的一次性專案。
2. MVP : Model - View - Presenter
Model:包含具體的資料來源以及資料請求
View:負責 UI 處理,對應 Activity、Fragment
Presenter:負責收取 View 發起的操作指令,並根據指令呼叫 Model 方法,
獲取資料, 並對獲取的資料進行整合,再回撥給 View。
目前 MVP 是應用比較廣泛的一種架構,
層次清晰,耦合度降低,同時 View 只負責 UI 即可,釋放了 View。
但是,在加入 Presenter 作為 View 和 Model 的橋樑的同時,
也導致了 Presenter 會越來越臃腫,也不利於後期的維護。
並且,每一個包含網路請求的 View 都需要對應一個或多個 Presenter。
3. MVVM: Model - View - ViewModel
相對來說,MVVM 實際上是 MVP 的改進版,
將 Presenter 改為 ViewModel,並配合 Databinding,
通過雙向資料繫結來實現檢視與資料的互動。
MVVM 目前相較 MVP,應用較少,除錯不夠方便,
架構的實現方式不夠完善,常見的只有 Databinding 框架,
中小型專案不適用用這種架構。但它簡化了開發,資料和檢視只需要繫結一次即可。
4. 模組化:獨立、解耦、可重性
對一系列具有內聚性的業務進行整理,將其與其他業務進行切割、拆分,
從主工程或者原位置抽離為一個相對獨立的部分。
不同的模組之間,相互獨立,不存在依賴與被依賴的關係。大大減少了耦合度。
既可以以 Library 的形式供主工程依賴,又可以以 Application 的形式,
脫離主工程獨立執行,獨立除錯。這樣就i使得在以後的版本維護及迭代中,
各個業務線的開發人員的職責更加明確。
各個模組之間還可以組合執行,能夠及時適應產品的需求,靈活拆分組合打包上線。
目前應用較多的框架主要有:
阿里的 ARouter、
得到開源的 DDComponentForAndroid
5. 元件化
將通用的一個功能或 UI 庫做成一個元件。
比如及時通訊、支付、分享、推送、下拉重新整理等。
模組化是根據業務抽離,元件化是根據功能 UI 抽離。
一個模組可以依賴多個元件,元件與元件之間不可相互依賴。
MVP 具體實現
假設現在有這樣一個需求,在某一個頁面,當使用者點選按鈕,從網路獲取資料並展示在當前頁。
這是一個很簡單的需求,讓我們拆分一下,整理一下實現思路:
- View:對應某一個頁面,按鈕的點選操作,屬於和使用者的互動
- Model:對應從網路獲取資料
- Presenter:負責從 Model 獲取資料,並回撥給 View,View 拿到資料後進行展示
ok,思路有了,現在用程式碼進行實現:
1. 建立 Model 類,封裝通過網路請求獲取資料的過程,即 M 層
/**
* model 層:從資料來源(網路、資料庫)獲取資料
*/
public class DataModel {
private DataApi mApi;
public DataModel() {
mApi = RetrofitHelpter.createApi(DataApi.class);
}
public void getData(String appKey, Callback<BaseResponse> callback) {
Call<BaseResponse> responseCall = mApi.getData(appKey);
// 發起請求
responseCall.enqueue(callback);
}
}
複製程式碼
2. 在建立 Presenter 層之前,我們需要建立一個介面,取名 DataView,負責向 V 層回撥資料
public interface DataView {
void getDataSuccess(List<ArticleBean> articleList);
void getDataFail(String failMsg);
}
複製程式碼
3. 現在建立 Presenter,取名 DataPresenter
/**
* 負責 View 層和 Model 層之間的通訊,並對從 Model 層獲取的資料進行處理
*/
public class DataPresenter {
private DataView mView;
private DataModel mModel;
public DataPresenter(DataView dataView) {
this.mView = dataView;
this.mModel = new DataModel();
}
/**
* 定義 View 層需要進行的 action
*/
public void getData(String appKey) {
mModel.getData(appKey, new Callback<BaseResponse>() {
@Override
public void onResponse(Call<BaseResponse> call, Response<BaseResponse> response) {
mView.getDataSuccess(response.body().getResult().getList());
}
@Override
public void onFailure(Call<BaseResponse> call, Throwable t) {
mView.getDataFail(t.getMessage());
}
});
}
}
複製程式碼
4. 在我們的 View 層,實現 DataView 介面,並重寫方法,這樣就能接收到回撥資料了
/**
* View 層,負責 UI 繪製以及與使用者的互動
*/
public class MVPDemoAty extends AppCompatActivity implements DataView {
private static final String APP_KEY = "dbb6893ab0913b02724696504181fe39";
private Button btnGet;
private RecyclerView recyclerView;
private DataPresenter mPresenter;
private DataAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvp_demo);
btnGet = findViewById(R.id.btnGet);
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
mPresenter = new DataPresenter(this);
mAdapter = new DataAdapter(this, new ArrayList<ArticleBean>());
recyclerView.setAdapter(mAdapter);
btnGet.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.getData(APP_KEY);
}
});
}
@Override
public void getDataSuccess(List<ArticleBean> articleList) {
mAdapter.setNewData(articleList);
}
@Override
public void getDataFail(String failMsg) {
Toast.makeText(this, failMsg, Toast.LENGTH_SHORT).show();
}
}
複製程式碼
ok,敲完上面的程式碼,執行專案,點選獲取,就會看到下面的介面:
說明我們已經完成了最簡單 MVP 的編寫。
MVP 解決記憶體洩漏
細心的同學可能會發現,我們在 DataPresenter 中持有了 V 層的引用。
這個問題就很嚴重了,如果在獲取網路資料的時候,當前的 Activity 就被銷燬了,那麼就會引起記憶體洩漏。
如何避免呢?解決方法也很簡單,只需要在 Activity 銷燬時,將 V 層的引用置空不就可以了?
ok,思路有了,往下看程式碼實現:
1. 編寫 IPresenter 介面,提供繫結和解綁兩個方法
public interface IPresenter {
void attach(DataView dataView);
void detach();
}
複製程式碼
2. DataPresenter 類實現 IPresenter
/**
* 負責 View 層和 Model 層之間的通訊,並對從 Model 層獲取的資料進行處理
*/
public class DataPresenter implements IPresenter {
private DataView mView;
private DataModel mModel;
public DataPresenter() {
this.mModel = new DataModel();
}
/**
* 定義 View 層需要進行的 action
*/
public void getData(String appKey) {
mModel.getData(appKey, new Callback<BaseResponse>() {
@Override
public void onResponse(Call<BaseResponse> call, Response<BaseResponse> response) {
mView.getDataSuccess(response.body().getResult().getList());
}
@Override
public void onFailure(Call<BaseResponse> call, Throwable t) {
mView.getDataFail(t.getMessage());
}
});
}
@Override
public void attach(DataView dataView) {
this.mView = dataView;
}
@Override
public void detach() {
this.mView = null;
}
}
複製程式碼
3. 在 V 層中,建立 Presenter 完成之後,就繫結mPresenter.attach(this);
,
並在銷燬時,呼叫 mPresenter.detach();
修改後的程式碼如下:
/**
* View 層,負責 UI 繪製以及與使用者的互動
*/
public class MVPDemoAty extends AppCompatActivity implements DataView {
private static final String APP_KEY = "dbb6893ab0913b02724696504181fe39";
private Button btnGet;
private RecyclerView recyclerView;
private DataPresenter mPresenter;
private DataAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvp_demo);
btnGet = findViewById(R.id.btnGet);
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
mPresenter = new DataPresenter();
mPresenter.attach(this);
mAdapter = new DataAdapter(this, new ArrayList<ArticleBean>());
recyclerView.setAdapter(mAdapter);
btnGet.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.getData(APP_KEY);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
mPresenter.detach();
}
@Override
public void getDataSuccess(List<ArticleBean> articleList) {
mAdapter.setNewData(articleList);
}
@Override
public void getDataFail(String failMsg) {
Toast.makeText(this, failMsg, Toast.LENGTH_SHORT).show();
}
}
複製程式碼
MVP 進一步優化
寫到這裡,對於記憶體洩漏問題,我們已經完美的解決了。但對於整個 MVP 的實現,貌似還不是那麼完美。
有什麼問題呢?在上面的程式碼中,我們在 V 層實現了 Presenter 的繫結與解綁操作。但是,在實際應用開發
過程中,會有很多個涉及網路請求操作的 Activity,難不成每個 Activity 都要去實現重寫繫結與解綁?
很明顯,這樣做是可以的!哈哈哈!但是出於對自己的嚴格要求以及對程式碼質量的不斷追求,
顯然,優化工作是一定要做的!那麼,下面,我們的任務就是如何優化、如何掃除多餘臃腫程式碼?
1. 建立一個抽象基類,BaseMVPActivity
提供 createPresenter(),並返回 Presenter 物件,這樣拿到了子類的 Presenter
物件,就可以進行繫結解綁操作了。
public abstract class BaseMVPActivity<T extends IPresenter> extends AppCompatActivity implements IView {
protected T mPresenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(createView());
mPresenter = createPresenter();
if (null == mPresenter) {
throw new IllegalStateException("Please call mPresenter in BaseMVPActivity(createPresenter) to create!");
} else {
mPresenter.attach(this);
}
viewCreated();
}
protected abstract void viewCreated();
@Override
protected void onDestroy() {
super.onDestroy();
if (null != mPresenter){
mPresenter.detach();
}
}
protected abstract int createView();
protected abstract T createPresenter();
@Override
public void showLoading() {
}
@Override
public void hideLoading() {
}
}
複製程式碼
2. 建立基類: BasePresenter,並實現 IPresenter,重寫繫結解綁方法。
public class BasePresenter<V extends IView> implements IPresenter<V> {
protected V view;
@Override
public void attach(V view) {
this.view = view;
}
@Override
public void detach() {
this.view = null;
}
}
複製程式碼
3. 建立介面:IView,可以是空實現,也可以宣告一些共用的方法。
public interface IView {
void showLoading();
void hideLoading();
}
複製程式碼
修改之後的 Activity:
/**
* View 層,負責 UI 繪製以及與使用者的互動
*/
public class MVPDemoAty extends BaseMVPActivity<DataPresenter> implements DataView {
private static final String APP_KEY = "dbb6893ab0913b02724696504181fe39";
private Button btnGet;
private RecyclerView recyclerView;
private DataAdapter mAdapter;
private ProgressDialog mDialog;
@Override
protected void viewCreated() {
mDialog = new ProgressDialog(this);
mDialog.setMessage("玩命載入中...");
btnGet = findViewById(R.id.btnGet);
recyclerView = findViewById(R.id.recyclerView);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
// linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(linearLayoutManager);
mAdapter = new DataAdapter(this, new ArrayList<ArticleBean>());
recyclerView.setAdapter(mAdapter);
btnGet.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDialog.show();
mPresenter.getData(APP_KEY);
}
});
}
@Override
protected int createView() {
return R.layout.activity_mvp_demo;
}
@Override
protected DataPresenter createPresenter() {
return new DataPresenter();
}
@Override
public void getDataSuccess(List<ArticleBean> articleList) {
mDialog.dismiss();
mAdapter.setNewData(articleList);
}
@Override
public void getDataFail(String failMsg) {
mDialog.dismiss();
Toast.makeText(this, failMsg, Toast.LENGTH_SHORT).show();
}
}
複製程式碼
到此,一個較完善的 MVP 架構,已經實現的差不多了。上面實現的是 Activity 的MVP實現,
Fragment 也是一樣,在這裡就不實現了。當然,對於上面實現的 MVP 仍然還有很多可以優化之處,
時間有限,就先實現到這裡,以後有時間再改造。最後,附上 Github 下載地址: