Android MVC,MVP和MVVM架構模式的探究

首語

  • Android項目開發中,尤其大型的項目中,模塊內部的高聚合和模塊間的低耦合顯得很是重要。爲此我們需要選擇一種框架模式,Android通常使用到的有MVC,MVP和MVVM。通過框架模式設計的項目能夠極大提高開發效率,提高項目的可維護性和可擴展性,同時在模塊測試和Bug處理上也有很大便利。

需求(查詢用戶賬號信息)

  • 用戶輸入賬號,點擊按鈕可進行查詢賬號信息,如果查詢數據成功,則將數據展示在頁面上;如果查詢失敗,則在頁面上提示獲取數據失敗。

不使用框架

  • Activity主要實現的功能有獲取用戶輸入的信息、展示獲取信息成功頁面、展示獲取信息失敗頁面、查詢用戶數據和業務邏輯。
	private String getUserInput(){//獲取用戶輸入的信息
        return etinput.getText().toString().trim();
    }

    private void showSuccessPage(Account account){//展示獲取信息成功頁面
        tvinformation.setText("用戶賬號:"+account.getName()+"用戶等級:"+account.getLevel());
    }

    private void showFailedPage(){//展示獲取信息失敗頁面
        tvinformation.setText("獲取數據失敗");
    }

    private void getAccountData(String accountName, MCallBack callBack){//查詢用戶數據
        Random random = new Random();
        boolean isSuccess = random.nextBoolean();
        if (isSuccess){
            Account account = new Account();
            account.setName(accountName);
            account.setLevel(100);
            callBack.onSuccess(account);
        }else {
            callBack.onFailed();
        }
    }
     @Override
    public void onClick(View view) {
        String userInput = getUserInput();
        getAccountData(userInput, new MCallBack() {
            @Override
            public void onSuccess(Account account) {
                showSuccessPage(account);
            }

            @Override
            public void onFailed() {
                showFailedPage();
            }
        });
    }
    
    public interface MCallBack {

    void onSuccess(Account account);
    void onFailed();
}

	//Account Bean類
	private String name;//姓名
    private int level;//等級

    public String getName() {
        return name;
    }

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

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

MVC

  • MVC的全名是Model-View-Controller,是模型(Model)-視圖(View)-控制器(Controller)的縮寫。Model層負責數據處理(網絡請求,SQL等),View層負責Layout、View控件。Controller層負責Activity,Fragment。
需求分析
  • Controller層:業務邏輯處理,獲取用戶輸入,展示成功頁面,展示失敗頁面。
  • Model層:查詢賬號數據。
  • View層:layout佈局。
實現
  • 1.將數據的獲取與界面的展示分離 。
  • 2.解決各層之間通信問題(Activity通知Model獲取數據,Model通知Activity更新頁面)。
//Model層
public void getAccountData(String accountName, MCallBack callBack){
        Random random = new Random();
        boolean isSuccess = random.nextBoolean();
        if (isSuccess){
            Account account = new Account();
            account.setName(accountName);
            account.setLevel(100);
            callBack.onSuccess(account);
        }else {
            callBack.onFailed();
        }
    }
//Controller層
	private String getUserInput(){
        return etinput.getText().toString().trim();
    }
    private void showSuccessPage(Account account){
        tvinformation.setText("用戶賬號:"+account.getName()+"用戶等級:"+account.getLevel());
    }

    private void showFailedPage(){
        tvinformation.setText("獲取數據失敗");
    }
    @Override
    public void onClick(View view) {
       mMvcModel.getAccountData(getUserInput(), new MCallBack() {
           @Override
           public void onSuccess(Account account) {
               showSuccessPage(account);
           }

           @Override
           public void onFailed() {
               showFailedPage();
           }
       });
    }    
MVC優缺點
  • 優點:一定程度上實現了Model與View的分離,降低了代碼的耦合性。
  • 缺點:Controller與View難以完全解耦,並且隨着項目複雜度的提升,Controller將越來越臃腫。

MVP

  • MVP的全名是Model-View-Presenter,是模型(Model)-視圖(View)-中間層(Presenter)的縮寫。Model層負責數據處理(網絡請求、SQL等) ,View層負責用戶交互(Activity,Fragment),Presenter層作爲View層與Model層中間的紐帶,負責處理與用戶交互的主要業務邏輯。
需求分析
  • Model層:查詢賬號數據。
  • View層:獲取用戶輸入、展示成功界面、展示失敗界面。
  • Presenter層:業務邏輯處理。
實現
  • 1.Activity負責提供View層面的功能(採用實現接口的方式)
  • 2.Model層負責提供數據方面的功能。
  • Model層與View層不再直接通信,通過Presenter來實現。
//Model層負責提供數據方面的功能
public void getAccountData(String accountName, MCallBack callBack){
        Random random = new Random();
        boolean isSuccess = random.nextBoolean();
        if (isSuccess){
            Account account = new Account();
            account.setName(accountName);
            account.setLevel(100);
            callBack.onSuccess(account);
        }else {
            callBack.onFailed();
        }
    }
//採用實現接口的方式實現View層面的功能
    public interface MVPView {

    String getUserInput();
    void showSuccessPage(Account account);
    void showFailedPage();
}
//中間層Presenter
private MVPView mMVPView;
    private MVPModel mMVPModel;

    public MVPPresenter(MVPView mMVPView) {
        this.mMVPView = mMVPView;
        mMVPModel = new MVPModel();
    }
    public void getData(String accountName){
        mMVPModel.getAccountData(accountName, new MCallBack() {
            @Override
            public void onSuccess(Account account) {
                mMVPView.showSuccessPage(account);
            }

            @Override
            public void onFailed() {
                mMVPView.showFailedPage();
            }
        });
    }
//Activity中Model與View不再直接通信,通過Presenter來實現。繼承View接口層   
	@Override
    public void onClick(View view) {
        mMVPPresenter.getData(getUserInput());
    }

    @Override
    public String getUserInput() {
        return etinput.getText().toString().trim();
    }

    @Override
    public void showSuccessPage(Account account) {
        tvinformation.setText("用戶賬號:"+account.getName()+"用戶等級:"+account.getLevel());
    }

    @Override
    public void showFailedPage() {
        tvinformation.setText("獲取數據失敗");
    }
MVP優缺點
  • 優點:解決了MVC中Controller與View過度耦合的的缺點,職責劃分明顯,更加易於維護。
  • 缺點:接口數量多,項目複雜度提高,隨着項目複雜度的提高,Presenter將越來越臃腫。
建議
  • 1.接口規範化(封裝父類接口以減少接口的使用量)

  • 2.使用MVP插件自動生成MVP的代碼

  • 3.對於一些簡單的頁面,可以選擇不使用框架。

  • 4.根據項目複雜度,部分模塊可以選擇不使用接口。

MVC與MVP的區別
  • 1.Model與View不再直接進行通信,而是通過中間層Presenter來實現。
  • 2.Activity的功能被簡化,不再充當控制器,主要負責View層面的工作。
PS(常見MVP插件)

MVVM

  • MVVM的全名是Model-View-ViewModel,是模型(Model)-視圖(View)-數據視圖管理器(ViewModel)的縮寫。Model層負責數據處理,View層負責Activity,Fragment。VIewModel層負責調用Model,拿到數據更新自身,而View與ViewModel雙向綁定,所以View會自動更新。MVVM在MVP的基礎上實現了數據視圖的綁定(DataBinding),當數據變化時,視圖會自動更新;反之,視圖發生變化時,數據也會自動更新。
DataBinding
使用步驟

1.啓用DataBinding

//編輯app目錄下的build.gradle文件,添加下面的內容。
android {
    ...
    dataBinding {
        enabled = true
    }
}

2.修改佈局文件爲DataBinding佈局

  • 選中佈局文件根佈局(如LinearLayout),按alt+enter,點擊Convert to data binding layout,
    佈局文件轉化爲DataBinding佈局。它最外層是layout,裏面包含data和我們的原始佈局。
  • 在data中我們可以聲明對象,然後我們就可以在佈局文件中使用對象。@={}表示雙向綁定(具體見代碼)
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="account"
            type="com.yhj.framedemo.bean.Account" />

        <variable
            name="activity"
            type="android.app.Activity" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".databinding.DemoActivity">

        <TextView
            android:id="@+id/tv_level"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="80dp"
            android:text="@{account.name+'|'+account.level}" />

        <Button
            android:id="@+id/bt_level"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="80dp"
            android:onClick="@{activity.onClick()}"
            android:text="等級+1" />

    </LinearLayout>
</layout>
  • 生成DataBinding佈局以後,setContentView(view)也要發生改變,使用DataBindingUtil.setContentView,同時使用binding省略了findViewById。
  ActivityMvvmBinding binding = 
  DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
  binding.etInput.setText("省略findViewById()");

3.數據綁定

  • 讓Bean類Account繼承BaseObservable,爲了使數據發生變化視圖自動更新,在getLevel()上加註解Binable,在setLevel()中notifyPropertyChanged(BR.level)。
	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_demo);
        account = new Account();
        account.setName("Tom");
        account.setLevel(100);
        binding.setAccount(account);
        binding.setActivity(this);
    }

    public void onClick(View view){
        Toast.makeText(this, "點擊了!", Toast.LENGTH_SHORT).show();
        int level = account.getLevel();
        account.setLevel(level+1);
	    //binding.setAccount(account);
    }
public class Account extends BaseObservable {

    private String name;//姓名
    private int level;//等級
    
    public String getName() {
        return name;
    }

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

    @Bindable
    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
        notifyPropertyChanged(BR.level);
    }
}
需求分析
  • Model層:查詢賬號數據。
  • View層:獲取用戶輸入,展示成功界面,展示失敗界面。
  • ViewModel層:業務邏輯處理,數據更新。
實現
  • 提供View,ViewModel以及Model三層。
  • 將佈局修改爲DataBinding佈局
  • View與ViewModel之間通過DataBinding進行通信。
  • 獲取數據展示在界面上。
//首先是DataBinding佈局
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name="viewModel"
            type="com.yhj.framedemo.mvvm.MVVMViewModel"/>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".mvvm.MVVMActivity">

        <EditText
            android:hint="請輸入要查詢的賬號"
            android:text="@={viewModel.userInput}"
            android:layout_marginTop="20dp"
            android:id="@+id/et_input"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <Button
            android:layout_gravity="center"
            android:layout_marginTop="80dp"
            android:id="@+id/bt_get"
            android:onClick="@{viewModel.getData}"
            android:text="獲取賬號信息"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <TextView
            android:layout_gravity="center"
            android:layout_marginTop="80dp"
            android:id="@+id/tv_information"
            android:text="@{viewModel.result}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>
</layout>
//Model層數據處理
public void getAccountData(String accountName, MCallBack callBack){
        Random random = new Random();
        boolean isSuccess = random.nextBoolean();
        if (isSuccess){
            Account account = new Account();
            account.setName(accountName);
            account.setLevel(100);
            callBack.onSuccess(account);
        }else {
            callBack.onFailed();
        }

    }
//ViewModel層
public class MVVMViewModel extends BaseObservable {
    private String result;
    private String userInput;

    @Bindable
    public String getUserInput() {
        return userInput;
    }

    public void setUserInput(String userInput) {
        this.userInput = userInput;
        notifyPropertyChanged(BR.userInput);
    }

    private final MVVMModel mvvmModel;
    private MVVMModel mvvmModel1;
    private ActivityMvvmBinding binding;

    @Bindable
    public String getResult() {
        return result;
    }

    public void setResult(String result) {
        this.result = result;
        notifyPropertyChanged(BR.result);
    }

    //一般需要傳入Application對象,方便在ViewModel中使用Application
    //比如SharedPreference
    public MVVMViewModel(Application application) {
        mvvmModel = new MVVMModel();
    }

    public void getData(View view) {
        mvvmModel.getAccountData(userInput, new MCallBack() {
            @Override
            public void onSuccess(Account account) {
                String info = account.getName() + "|" + account.getLevel();
                setResult(info);
            }

            @Override
            public void onFailed() {
                setResult("獲取失敗!!!");
            }
        });
    }
}
//View層Activity
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMvvmBinding binding =
                DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
        MVVMViewModel mvvmViewModel = new MVVMViewModel(getApplication());

        binding.setViewModel(mvvmViewModel);

    }
  • View可以向ViewModel傳遞信息,ViewModel改變數據通知View自動更新,但是有些功能我們要在Activity中實現(比如點擊按鈕獲取應用權限),ViewModel如何通知View呢?要實現這個功能,我們可以讓ViewModel執有Activity的引用,或者借用EventBus。對於MVVM模式來說,我們可以使用LiveData+ViewModel來實現。
LiveData
MVVM優缺點
  • 優點:實現了數據與視圖的雙向綁定,極大的簡化代碼。
  • 缺點:bug難以調試,並且DataBinding目前還存在一些編譯問題。

總結

  • Android開發中,一直在用MVC,甚至可以說沒有使用,一些大型項目隨着不斷的迭代,出現了許多問題,推出了MVP,再到現在主推的MVVM。架構演進代碼質量也更高,但學習難度也增大。
  • Android開發語言,一直在用Java,如今又推出了Kotlin語言,已經成爲Android官方支持開發語言。以及跨平臺開發方案Flutter使用的Dart語言,想想哇,Android之路漫漫啊,還有許多東西要學習使用。需要一直提升自己,做未來的T型人才
  • 項目源碼:FrameDemo
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章