天天看點

Android架構之MVC、MVP、MVVM(附源碼)

Android架構模式主要有MVC、MVP和MVVM,根據業務選擇合适的架構。

(一)MVC(模型-視圖-控制器)

        業務、資料、界面分離的方法組織代碼,在改進和個性化定制界面和使用者互動時,無須重新編寫業務邏輯。

        模型層:針對業務模型, 建立資料結構和相關的類,可以了解為Model,Model與View無關,而與業務相關;

        視圖層:界面顯示結果。一般以XML檔案或者Java代碼進行界面的描述;JS+HTML也可以作為View層;

        控制器:橋梁效果。控制層通常位于Activity、Fragment或者由他們控制的其他業務類中。用來View層和Model層的通信。

        MVC簡單來說:使用Contoller來操作Model層的資料,并傳回給View層顯示。

Android架構之MVC、MVP、MVVM(附源碼)

        我們往往把Android中界面部分的實作也了解為采用了MVC架構,常常把Activity了解為MVC模式中的Controller。

        優點:

        1、把業務邏輯全部分離到Controller中,子產品化程度高。當業務邏輯變更的時候,不需要變更View和Model,隻需要替換Controller,換成另外一個 Controller就行了(Swappable Controller)。

        2、觀察者模式可以做到多視圖同時更新。

        缺點:

       1.随着界面及其邏輯的複雜度不斷提升,Activity類的職責不斷增加,導緻它特别龐大備援。

       2.View層和Model層互相耦合,不宜開發和維護。即:如果需要把這個View抽出來作為一個另外一個應用程式可複用的元件就困難了,因為不同程式的的Domain Model是不一樣的。

(二)MVP模式

       MVP模式将MVC中的Controller改為了Presenter,View通過接口與Presenter進行互動,降低耦合,友善進行單元測試。

       View:負責處理點選事件,繪制UI元素(Activity、View、Fragment都可以做為View層);

       Model:對資料存取操作、對網絡的操作,通過Model來擷取、存儲資料;

       Presenter:作為View與Model互動的中間紐帶,它從Model層檢索資料後傳回給View層,使得View層和Model之間沒有耦合。          

       MVP的特點:(1)實作View和Model的分離,決不允許View直接通路Model。(2)Presenter與具體View沒有直接關聯,通過定義好的接口進行互動。保證變更View時候保持Presenter不變,View中隻有簡單的get、set方法。

Android架構之MVC、MVP、MVVM(附源碼)
Android架構之MVC、MVP、MVVM(附源碼)

       優點:

       1、便于測試。Presenter對View是通過接口進行,在對Presenter進行不依賴UI環境的單元測試的時候。可以通過Mock一個View對象,這個對象隻需要實作了View的接口即可。然後依賴注入到Presenter中,單元測試的時候就可以完整的測試Presenter業務邏輯的正确性。

       2、View可以進行元件化。在MVP當中,View不依賴Model。這樣就可以讓View從特定的業務場景中脫離出來,可以說View可以做到對業務邏輯完全無知。它隻需要提供一系列接口提供給上層操作。這樣就可以做高度可複用的View元件。

       缺點:

       Presenter中除了業務邏輯以外,還有大量的View->Model,Model->View的手動同步邏輯,造成Presenter比較笨重,維護起來會比較困難。

       MVP結合RxJava和Dagger2效果較好。

       以下代碼參考文章:https://www.jianshu.com/p/479aca31d993

代碼示例:
==================================View=======================================
所有的view(Activity、FragmentActivity、Fragment...)都必須實作這個接口
public interface IView {
    // 此方法是為了當Presenter中需要擷取上下文對象時,傳遞上下文對象,而不是讓Presenter直接持有上下  文對象
    Activity getSelfActivity();
}

這是Activity的基類:

public abstract class BaseActivity<P extends IPresenter> extends Activity implements IView {
    // Presenter對象
    protected P MvpPre;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MvpPre = bindPresenter();
    }
    
    // 綁定Presenter
    protected abstract P bindPresenter();

    public <T> T $(int resId) {
        return (T) findViewById(resId);
    }

    public <T> T $(int resId, View parent) {
        return (T) parent.findViewById(resId);
    }

    @Override
    public Activity getSelfActivity() {
        return this;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        /**
         * 在生命周期結束時,将presenter與view之間的聯系斷開,防止出現記憶體洩露
         */
        if (MvpPre != null) {
            MvpPre.detachView();
        }
    }
}

==================================Presenter=======================================
public interface IPresenter {
    void detachView();
}

Presenter的基類:

public abstract class BasePresenter<V extends IView> implements IPresenter {
    // 此處使用弱引用是因為,有時Activity關閉不一定會走onDestroy,是以這時使用弱引用可以及時回收IView
    protected Reference<V> MvpRef;

    public BasePresenter(V view) {
        attachView(view);
    }

    private void attachView(V view) {
        MvpRef = new WeakReference<V>(view);
    }

    protected V getView() {
        if (MvpRef != null) {
            return MvpRef.get();
        }
        return null;
    }

    /**
     * 主要用于判斷IView的生命周期是否結束,防止出現記憶體洩露狀況
     *
     * @return
     */
    protected boolean isViewAttach() {
        return MvpRef != null && MvpRef.get() != null;
    }

    /**
     * Activity生命周期結束時,Presenter也清除IView對象,不在持有
     */
    @Override
    public void detachView() {
        if (MvpRef != null) {
            MvpRef.clear();
            MvpRef = null;
        }
    }
}

===================================demo========================================
接口:
/**
 * 建立一個類作為紐帶,将view、presenter、model的接口方法都串聯在一起,更加便于管理
 */
public final class MainContacts {
    public interface IMain extends IView {
        void showTips(boolean isSucceess);
    }

    public interface IMainPre extends IPresenter {
        void login(String username, String password);
    }

    public interface IMainLgc {
        boolean login(String username, String password);
    }
}
Model部分:
public class MainLogic implements MainContacts.IMainLgc {
    public boolean login(String username, String password) {
        if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
            return false;
        }
        return true;
    }
}
View部分:
public class MainActivity extends BaseActivity<MainPresnter> implements MainContacts.IMain {
    private EditText editT_username, editT_password;
    private Button btn_login;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initUI();
        addListeners();
    }
    @Override
    protected MainPresnter bindPresenter() {
        return new MainPresnter(this);
    }
    private void initUI() {
        editT_username = $(R.id.editT_username);
        editT_password = $(R.id.editT_password);
        btn_login = $(R.id.btn_login);
    }
    private void addListeners() {
        btn_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MvpPre.login(editT_username.getText().toString(), editT_password.getText().toString());
            }
        });
    }
    @Override
    public void showTips(boolean isSucceess) {
        if (isSucceess) {
            Toast.makeText(this, "登入成功!", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "登入失敗!", Toast.LENGTH_SHORT).show();
        }
    }
}
Presenter部分:
public class MainPresnter extends BasePresenter<MainContacts.IMain> implements MainContacts.IMainPre {
    private MainLogic mMainLogic;
    public MainPresnter(MainContacts.IMain view) {
        super(view);
        this.mMainLogic = new MainLogic();
    }
    @Override
    public void login(String username, String password) {
        // 判斷activity的生命周期是否結束,不判斷的話在極端情況下可能會出現記憶體洩露
        if (isViewAttach()) {
            MvpRef.get().showTips(mMainLogic.login(username, password));
        }
    }
}
           

(三)MVVM

       MVVM模式(Model--View--ViewModel模式),和MVP模式相比,MVVM 模式用ViewModel替換了Presenter,其他層基本上與 MVP 模式一緻,ViewModel可以了解成是View的資料模型和Presenter的合體。

       MVVM是的ViewModel與Mode和View采用雙向綁定技術(data-binding):View的變動,自動反映在ViewModel,ViewModel通知Model來改變資料,反之亦然。這種模式實際上是架構替應用開發者做了一些工作(相當于ViewModel類是由庫幫我們生成的),開發者隻需要較少的代碼就能實作比較複雜的互動。    

Android架構之MVC、MVP、MVVM(附源碼)

       MVVM的調用關系

       MVVM的調用關系和MVP一樣。但是,在ViewModel當中會有一個叫Binder,或者是Data-binding engine的東西。以前全部由Presenter負責的View和Model之間資料同步操作交由給Binder處理。你隻需要在View的模版文法當中,指令式地聲明View上的顯示的内容是和Model的哪一塊資料綁定的。當ViewModel對進行Model更新的時候,Binder會自動把資料更新到View上去,當使用者對View進行操作(例如表單輸入),Binder也會自動把資料更新到Model上去。這種方式稱為:Two-way data-binding,雙向資料綁定。可以簡單而不恰當地了解為一個模版引擎,但是會根據資料變更實時渲染。MVVM把View和Model的同步邏輯自動化了。

        優點:

       1、提高可維護性。解決了MVP大量的手動View和Model同步的問題,提供雙向綁定機制。提高了代碼的可維護性。

       2、簡化測試。因為同步邏輯是交由Binder做的,View跟着Model同時變更,是以隻需要保證Model的正确性,View就正确。大大減少了對View同步更新的測試。

      缺點:

     1、過于簡單的圖形界面不适用,或說牛刀殺雞。

     2、對于大型的圖形應用程式,視圖狀态較多,ViewModel的建構和維護的成本都會比較高。

     3、資料綁定的聲明是指令式地寫在View的模版當中的,這些内容是沒辦法去打斷點debug的。

     參考自:https://mp.weixin.qq.com/s/JMjERFMA-ZetM7nU-0378Q

      View

      View層,不再是我們之前了解的是一個TextView、LinearLayout等View控件,隻要是可以和使用者進行互動的都可以歸屬到View層,比如:Activity、Fragment、Dialog、PopupWindow、XML布局、布局Adapter、系統及自定義View控件等等,隻要是使用者看到的、摸到的都歸View層管。也就是說禁止在這裡做業務邏輯、資料操作等和View無直接相關的事情。

      Model

      model層,與我們之前把定義的實體bean對象稱為model不同的是,這裡的model被賦予了資料管理的職責。資料的管理包括資料存儲與資料擷取,這裡存儲的位置不僅限于本地(SharedPreferences、SQLite),而且也會是網絡上的任何存儲方式。

      ViewModel

      ViewModel層,說是隻處理業務邏輯,更準确的說法是Model層和View層的粘合劑,從Model中擷取資料整合之後提供給View層進行顯示,響應View層的事件調用Model層進行響應的落地。

      MVVM的步驟:

      https://www.jianshu.com/p/545af5bbb93f

      https://blog.csdn.net/xuehuayous/article/details/82777022 

      步驟一:gradle裡面啟用dataBinding,我們使用mvvm,必然會用到android為我們提供的dataBinding支援包。

dataBinding{
        enabled true
    }
           

      步驟二:修改View布局,引入layout進行外包,引入Data,import變量引入Model的包名,type是變量類型,name是是變量名稱。Class是自定義類名的方法。android:text="@{user.phoneNumber}"引入雙向綁定時改變的域。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
  >
    <!--如果想使用databinding架構,需要按照mvvm的規矩來辦,而mvvm的規矩就是,需要布局檔案
     按照 「固定的寫法」 來編寫。-->
    <!--最外層用<layout>标簽嵌套,注意layout的首字母是小寫的“l”-->
    <!--* <layout>标簽的下面緊跟着一個<data>标簽,這個标簽其實就是讓我們進行資料綁定的一個标簽-->
    <!--* <data>标簽中,包含着<variable>标簽,這個标簽就是我們将“變量”放置的位置-->
    <!--* <variable>标簽裡面分别有<type>  <name>兩個标簽,分别來辨別變量類型和變量名稱-->
    <!--* <type>标簽 辨別變量類型,比如java.lang.String這就是String類型,com.guaju.mvvm.bean.User 這個就是一個我自定義的一個User類型-->
    <!--* <name>标簽 表示的就是我們定義的一個變量名稱,這個變量名稱我們會在下方的布局和對應的java代碼中引用到-->
        <data class = "Testing">
       <import type="com.example.hzk.myapplication.User"/>
       <variable
           name="user"
           type="User"/>
   </data>
   <LinearLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:orientation="vertical">
       <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:textSize="19sp"
           android:text="@{user.name}"
           android:textColor="#000"/>
       <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:textSize="24sp"
           android:text="@{user.phoneNumber}"
           android:textColor="#666"/>
   </LinearLayout>
</layout>
           

         步驟三:修改Model域。記得Rebuild一下。

/**
 * 充當Model的作用:我們就提供一個User類,存儲人物和電話。
 */
public class User {
    public String name;
    public String phoneNumber;
    public User(String name, String phoneNumber) {
        this.name = name;
        this.phoneNumber = phoneNumber;
    }
    public String getName() {
        return name;
    }
    public String getPhoneNumber() {
        return phoneNumber;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }
}
           

       步驟四:ModelView域,通過DataBindingUtil來傳回。在java代碼中直接設定資料,改變布局中的顯示。

/**
 * 編寫字元串,儲存到本地,再讀取出來修改下并儲存。
 */
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //雙向綁定機制,Testing為布局裡面的Class,記得rebuild生成下。
        Testing test = DataBindingUtil.setContentView(this,R.layout.activity_main);
//        在java代碼中直接設定資料,改變布局中的顯示
        User xiaoming = new User("小明","110");
        test.setUser(xiaoming);
    }
}