天天看点

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);
    }
}