MVP在Android中的使用

概述

關於Android MVP設計模式已經出來很久了,在項目中也普遍使用這個設計模式,但是MVP對於初學者來說的話還是有點難以捉摸的!每次看上去都感覺這樣寫很不錯,很好理解,就是增加了很多類,看完之後自己去寫又很難寫出來。再講MVP設計模式之前,我們先來講一下MVC設計模式。

MVC模式

MVC(模型-視圖-控制器)用一種業務邏輯、數據、界面顯示分離的方法組織代碼,在改進和個性化定製界面及用戶交互時,無須重新編寫業務邏輯,MVC模式結構分爲三個部分:

  • Model:實體模型,我們針對業務模型,建立的數據結構和相關類,對應着Bean實體類。Model與View無關,但是與業務有關。
  • View:一般採用XML文件或者Java代碼進行界面的描述,也可以使用JS+HTML的方式作爲View。
  • Controller:Android的控制器通常在Activity、Fragment或者由它們控制的其他業務類中。

MVC用一句話來概括就是通過Controller來操作Model層的數據,並且返回給View層來展示。如圖所示:
這裏寫圖片描述

看起來像是那麼回事,但是MVC也有它的缺點,不然就沒我們MVP模式什麼事了。

  • Activity並不是一個標準的MVC模式中的Controller,它的首要職責是加載應用佈局和初始化界面,接受並出來來自用戶的請求,並作出相應,隨着界面及其邏輯複雜度的不斷提升,Activity類的職責不斷增加,導致其變得龐大而臃腫。
  • Model層和View層相互耦合,不易於開發和維護。

MVP模式

MVP模式是MVC模式的一個演化版本,它的結構同樣分爲三個部分:

  • Mode:實體模型,同樣對應着Bean實體類主要提供數據的存取的功能,Present需要通過Model層來存儲和或者獲取數據

    從網絡,數據庫,文件,傳感器,第三方等數據源讀寫數據。
    對外部的數據類型進行解析轉換爲APP內部數據交由上層處理。
    對數據的臨時存儲,管理,協調上層數據請求。

  • View:負責處理用戶事件交互和視圖部分的展示,在Android中它可以使Activity和Fragment或者是某個View控件

    提供UI交互
    在presenter的控制下修改UI。
    將業務事件交由presenter處理。
    注意: View層不存儲數據,不與Model層交互。View和Presenter通過接口進行交互 面向接口編程

  • Presenter:作爲View與Model交互的中間紐帶,處理與用戶交互的負責邏輯。它還從Model中去獲取數據然後返回給View,使得Model和View沒有耦合。
    這裏寫圖片描述

MVP的核心思想

MVP 把 Activity 中的 UI 邏輯抽象成 View 接口,把業務邏輯抽象成 Presenter 接口,Model 類還是原來的 Model。這就是 MVP 模式,現在這樣的話,Activity 的工作的簡單了,只用來響應生命週期,其他工作都丟到 Presenter 中去完成。從上圖可以看出,Presenter 是 Model 和 View 之間的橋樑,爲了讓結構變得更加簡單,View 並不能直接對 Model 進行操作,這也是 MVP 與 MVC 最大的不同之處。

MVP的優點

  • 分離了視圖邏輯和業務邏輯,降低了耦合
  • Activity 只處理生命週期的任務,代碼變得更加簡潔
  • 視圖邏輯和業務邏輯分別抽象到了 View 和 Presenter 的接口中去,提高代碼的可閱讀性
  • Presenter 被抽象成接口,可以有多種具體的實現,所以方便進行單元測試
  • 把業務邏輯抽到 Presenter 中去,避免後臺線程引用着 Activity 導致 Activity 的資源無法被系統回收從而引起內存泄露和 OOM

使用 MVP,至少需要經歷以下步驟:

創建 IPresenter 接口,把所有業務邏輯的接口都放在這裏,並創建它的實現PresenterCompl(在這裏可以方便地查看業務功能,由於接口可以有多種實現所以也方便寫單元測試)創建 IView 接口,把所有視圖邏輯的接口都放在這裏,其實現類是當前的 Activity/Fragment。Activity 裏包含了一個 IPresenter,而 PresenterCompl 裏又包含了一個 IView 並且依賴了 Model。Activity 只保留對 IPresenter 的調用,其它工作全部留到 PresenterCompl 中實現。Model 並不是必須有的,但是一定會有 View 和 Presenter。

MVP簡單的登陸實例

首先看看我們的項目的結構:
這裏寫圖片描述

就跟我們上面說的一樣將業務邏輯和試圖邏輯抽象成接口,然後創建他的實現類,其中IView的實現類是Activity。

(一) Model

首先就和我們上面說的一樣,實體類User是要有的。然後至少有一個業務方法Login()用來從網絡中讀取數據。

public class User {

    private String name;
    private String pwd;

    public User(String name, String pwd) {
        this.name = name;
        this.pwd = pwd;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
}
public interface IUserBiz {
    void login(String name, String pwd, OnLoginListener onLoginListener);
}
public interface OnLoginListener
{
    void loginSuccess(User user);

    void loginFailed();
}
public class UserBiz implements IUserBiz {
    @Override
    public void login(final String name, final String pwd, final OnLoginListener onLoginListener) {
        //子線程耗時操作
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //登錄成功
                if ("admin".equals(name) && "admin".equals(pwd)) {
                    User user = new User(name,pwd);
                    onLoginListener.loginSuccess(user);
                } else {
                    onLoginListener.loginFailed();
                }
            }
        }.start();
    }
}

(二) View

我們這裏是模擬登陸,有Login和Clear兩個按鈕,用來登陸和清除賬號密碼。所以我們需要清楚賬號密碼的方法,還有ProgressBar的顯示與隱藏和對登陸這個操作的結果做出響應的方法。綜上所述接口爲:

public interface IUserLoginView {

    void clearUserName();

    void clearPassword();

    void showLoading();

    void hideLoading();

    void loginSuccess(User user);

    void loginError();
}

實現類代碼:

public class LoginOptimized1Activity extends AppCompatActivity implements IUserLoginView, View.OnClickListener {


    private EditText editUser;
    private EditText editPass;
    private Button btnLogin;
    private Button btnClear;
    private ProgressBar progressBar;

    private ILoginPresenter mPresenter;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        initViews();

        mPresenter = new LoginPresentCompl(this);
    }

    private void initViews() {
        editUser = (EditText) this.findViewById(R.id.et_login_username);
        editPass = (EditText) this.findViewById(R.id.et_login_password);
        btnLogin = (Button) this.findViewById(R.id.btn_login_login);
        btnClear = (Button) this.findViewById(R.id.btn_login_clear);
        progressBar = (ProgressBar) this.findViewById(R.id.progress_login);

        btnLogin.setOnClickListener(this);
        btnClear.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_login_clear:
                mPresenter.clear();
                break;
            case R.id.btn_login_login:
                mPresenter.login(editUser.getText().toString(), editPass.getText().toString());
                break;
        }
    }

    @Override
    public void clearUserName() {
        editUser.setText("");
    }

    @Override
    public void clearPassword() {
        editPass.setText("");
    }

    @Override
    public void showLoading() {
        progressBar.setVisibility(View.VISIBLE);
    }

    @Override
    public void hideLoading() {
        progressBar.setVisibility(View.INVISIBLE);
    }

    @Override
    public void loginSuccess(User user) {
        Toast.makeText(this, "Login Success", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void loginError() {
        Toast.makeText(this, "Login Error", Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

}

(三) Presenter

Presenter是用作Model和View之間交互的橋樑,那麼應該有什麼方法呢?其實也是主要看該功能有什麼操作,比如本例,兩個操作:login和clear。

public interface ILoginPresenter {
    void login(String name, String pwd);
    void clear();
}

實現類:

public class LoginPresentCompl implements ILoginPresenter {

    private IUserBiz userBiz;
    private IUserLoginView userLoginView;
    private Handler mHandler = new Handler(Looper.getMainLooper());

    public LoginPresentCompl(IUserLoginView userLoginView) {
        this.userLoginView = userLoginView;
        this.userBiz = new UserBiz();
    }

    @Override
    public void login(String name, String pwd) {
        userLoginView.showLoading();
        userBiz.login(name, pwd, new OnLoginListener() {
            @Override
            public void loginSuccess(final User user) {
                //需要在UI線程執行
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        userLoginView.hideLoading();
                        userLoginView.loginSuccess(user);
                    }
                });
            }

            @Override
            public void loginFailed() {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        userLoginView.hideLoading();
                        userLoginView.loginError();
                    }
                });
            }
        });
    }

    @Override
    public void clear() {
        userLoginView.clearUserName();
        userLoginView.clearPassword();
    }

}

解決OOM和內存泄漏

好了以上基本上就是一個簡單的MVP模擬登陸的例子了。但是以上的代碼還有一個問題,就是可能會造成內存泄漏,上面我們說MVP的優點的時候也說了。MVP能避免OOM和內存泄漏。接下來我們就利用弱引用結合Activity的生命週期來解決這個問題。

首先我們定義一個Present的基類,讓其他presenter繼承此類。

public class BasePresenter<V> {
    protected Reference<V> mViewRef;

    public void attachView(V view) {
        mViewRef = new WeakReference<V>(view);
    }

    public V getView() {
        if (mViewRef == null) {
            return null;
        }
        return mViewRef.get();
    }

    public boolean isViewAttached() {
        return mViewRef != null && mViewRef.get() != null;
    }

    public void detachView() {
        if (mViewRef != null) {
            mViewRef.clear();
            mViewRef = null;
        }
    }

}

這是一個泛型類,參數爲V爲要引用的View.
attachView()方法的作用是創建View的虛引用,此方法放在View初始化過程中,例如onCreate()方法中
detachView()方法的作用是銷燬View的虛引用,此方法放在View銷燬化過程中,例如onDestroy()方法中
getView()放用來獲取View

然後我們定義一個View的基類,讓其他的View繼承此類。

public abstract class MVPBaseActivity<V, T extends BasePresenter<V>> extends AppCompatActivity {
    protected T mPresenter;

    @Override
    protected void onCreate(Bundle arg) {
        super.onCreate(arg);
        mPresenter = createPresenter();
        mPresenter.attachView((V) this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mPresenter.detachView();
    }

    protected abstract T createPresenter();
}

這也是一個泛型基類,第一個泛型參數V:代表當前處理的View,第二個泛型參數T:代表當前使用的Presenter,要求此presenter必須繼承至 BasePresenter.要求每個Activity必須繼承至此類。在onCreate()和onDestroy()中的處理邏輯已經在講解 Prestener 時候已經說過。createPresenter() 用來創建presenter實例,便於在View中通過其調用presenter的方法,此方法是abstract的,這就要求MVPBaseActivity子類必須實現此方法。

最後講到這裏基本就結束了。其他的有什麼問題的地方可以去查看本文的源碼去解決。

github源碼地址

寫在最後(參考文章)

https://blog.csdn.net/lmj623565791/article/details/46596109
http://kaedea.com/2015/10/11/android-mvp-pattern/
https://blog.csdn.net/ShuSheng0007/article/details/77938378
https://www.jianshu.com/p/9a6845b26856

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章