從 0 到 1,帶你解剖 MVP 的神祕之處,並本身動手實現 MVP !

Android架構設計之一:MVP

對於新入門或者以及有過一段開發經驗對 MVP 仍有困惑的 Android 開發者,這篇文章,但願你不要錯過。git

本文主要講解了最基礎 MVP ,從 0 到 1 的實現過程,以及如何解決實現過程當中遇到的問題。github

簡介

關於 Android 架構,目前主要有 MVC、MVP、MVVM、模塊化、組件化等。數據庫

1. MVC : Model - View - Controller

M:邏輯模型,V:視圖模型,C:控制器bash

但這種架構,相較於其餘幾種,比較落後。 並且耦合性嚴重,網絡

職責相對不夠明確不利於後期維護,架構

適用於小型的一次性項目。app

2. MVP : Model - View - Presenter

Model:包含具體的數據源以及數據請求框架

View:負責 UI 處理,對應 Activity、Fragmentide

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 獲取數據,並回調給 ViewView 拿到數據後進行展現

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 下載地址:

MVPDemo

相關文章
相關標籤/搜索