欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android开发框架模式(MVC、MVP、MVVM)实例解析

程序员文章站 2022-05-12 15:24:38
...

Android项目中,尤其是比较大型的项目开发中,模块内部的高聚合和模块间的低耦合性就显得尤为重要了。所以我们一般情况下需要为项目设计一种框架模式,通常情况下我们一般用到的三种MVCMVPMVVM
通过框架模式设计的项目能够极大的提高开发效率,提高项目的可维护性和可扩展性,另外在模块测试及问题定位上也提供了较大的便利。

接下来我们就对这三种框架模式一一进行讲解,并会重点讲解下比较常用的MVP框架模式。

一、MVC框架:

Android开发框架模式(MVC、MVP、MVVM)实例解析

                     图片源自网络

MVC全称为Model(模型层)--View(视图层)--Controller(控制层)。
    Model层:主要用于网络请求、数据库、业务逻辑处理等操作。
    View层:用于展示UI,一般采用XML文件进行界面的描述。
    Controller层:控制层的重任落在了众多Activity上,Activity需要交割业务逻辑至Model层处理。

 

下面结合精简的实例来看下MVC框架模式:

Android开发框架模式(MVC、MVP、MVVM)实例解析

1、View层&Controller层

View层:XML布局文件activity_mvcpattern代表的就是View层,用来显示布局,与用户进行交互。

Controller层:MVCActivity代表的是Controller层,View层会传递请求至Controller,Controller控制Model层进行业务的更新。


/**
 * @author hongri
 * @date 2018/9/4
 *
 * Controller层
 */
public class MVCActivity extends AppCompatActivity implements View.OnClickListener {

    private Button btnRequest;
    private ImageView iv;
    private MVCHttpRequestModel mvcHttpRequestModel;
    private static final String requestUrl
        = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1536307707406&di"
        + "=edafdb6ac08325cbabe1bf4fdb930cec&imgtype=0&src=http%3A%2F%2Fpic.qiantucdn"
        + ".com%2F58pic%2F18%2F23%2F07%2F54F58PIC2yq_1024.jpg";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        /**
         * xml文件属于理论上的View层
         */
        setContentView(R.layout.activity_mvcpattern);

        mvcHttpRequestModel = new MVCHttpRequestModel();

        btnRequest = findViewById(R.id.btnRequest);
        iv = findViewById(R.id.iv);
        btnRequest.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btnRequest:

                /**
                 * Controller层将网络请求业务逻辑交由Model层处理
                 */
                mvcHttpRequestModel.onHttpRequest(requestUrl, new MVCRequestCallback() {

                    @Override
                    public void onSuccess(Object successData) {
                        if (successData != null) {
                            iv.setImageBitmap((Bitmap)successData);
                        }
                    }

                    @Override
                    public void onFailure(Object failureData) {
                        Toast.makeText(MVCActivity.this, failureData.toString(), Toast.LENGTH_LONG).show();
                    }
                });
                break;
            default:
                break;
        }
    }
}

 

2、Model层:

这里的MVCHttpRequestModel便属于Model层,Model层主要用于处理数据库、网络请求等一系列复杂的耗时处理,Model数据请求完成后,通知View进行UI的更新。



/**
 * @author hongri
 * @date 2018/9/4
 *
 * Model层
 */

public class MVCHttpRequestModel implements MVCHttpRequestInterface {

    @Override
    public void onHttpRequest(final String urlString, final MVCRequestCallback listener) {

        new RequestTask(listener).execute(urlString);

    }

    public class RequestTask extends AsyncTask<String, Void, Bitmap> {

        private MVCRequestCallback listener;

        public RequestTask(MVCRequestCallback listener) {
            this.listener = listener;
        }

        @Override
        protected Bitmap doInBackground(String... strings) {
            Bitmap bitmap = null;
            InputStream inputStream;
            try {
                URL url = new URL(strings[0]);
                HttpURLConnection conn = (HttpURLConnection)url.openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(10 * 1000);
                int responseCode = conn.getResponseCode();

                if (responseCode == 200) {
                    inputStream = conn.getInputStream();
                    bitmap = BitmapFactory.decodeStream(inputStream);
                } else {
                    bitmap = null;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return bitmap;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);

            /**
             *Model层请求完数据后回调给Controller层,Controller层更新UI
             */
            if (bitmap != null) {
                listener.onSuccess(bitmap);
            } else {
                listener.onFailure("获取失败");
            }
        }
    }
}

 

二、MVP框架

Android开发框架模式(MVC、MVP、MVVM)实例解析

                  图片源自网络

MVP全称为Model(模型层)--View(视图层)--Presenter(协调器/主持者)

MVP是由MVC转化而来的一种框架模式,MVP相比于MVC具有的优点如下:

1、Model层与View层完全分离,我们可以修改视图而不影响模型;

2、可以更高效地使用模型,因为所有的交互都发生在一个地方---Presenter内部;

3、可以将大量的逻辑操作放到Presenter中,避免Activity的臃肿;

4、可以选择将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁。

下面根据代码来具体介绍下MVP框架:

Android开发框架模式(MVC、MVP、MVVM)实例解析

 

1、首先介绍下View层:

View层一般由多个Activity来承担,主要用于显示UI,与用户来进行交互。



/**
 * MVP(Model--View--Presenter)框架模式--参考:http://www.jcodecraeer.com/a/anzhuokaifa/2017/1020/8625.html
 *
 * @author hongri
 *
 * View层
 */
public class MVPActivity extends MVPBaseActivity implements MVPViewInterface, OnClickListener {

    private Button btnName;
    private TextView tvData;
    private MVPPresenter mvpPresenter;
    private String requestUrl = "";

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

        mvpPresenter = new MVPPresenter();
        mvpPresenter.attachView(this);

        btnName = findViewById(R.id.btnName);
        tvData = findViewById(R.id.tvData);

        btnName.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btnName:
                tvData.setText("初始数据");
                mvpPresenter.requestData(requestUrl);
                break;
            default:
                break;
        }
    }

    @Override
    public void showSuccessData(String data) {
        MVPDataInfo dataInfo = ParseJsonUtil.parseJSONData(data);
        tvData.setText(
            "姓名:" + dataInfo.getmName() + "\n" + "性别:" + dataInfo.getmGender() + "\n" + "年龄:" + dataInfo.getmAge());
    }

    @Override
    public void showFailureData(String data) {
        tvData.setText(data);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mvpPresenter != null) {
            mvpPresenter.detachView();
        }
    }
}

 

这里为MVPActivity设置了一个*父类MVPBaseActivity,父类Activity中可以提炼出公用的UI展示、初始化等操作,减少各子Activity的冗余代码,也方便后续的维护。



/**
 * @author hongri
 * BaseActivity用于进行View层的通用的初始化及UI展示工作
 */
public class MVPBaseActivity extends AppCompatActivity implements MVPBaseViewInterface {

    /**
     * 页面通用LoadingDialog
     */
    private LoadingDialog loadingDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvpbase);
        loadingDialog = new LoadingDialog(this, R.style.LoadingTheme);
    }

    @Override
    public void showLoading() {
        if (loadingDialog != null) {
            loadingDialog.showLoading();
        }
    }

    @Override
    public void dismissLoading() {
        if (loadingDialog != null) {
            loadingDialog.dismissLoading();
        }
    }
}

 

接口也采用Base层继承的方式,分别定义了MVPViewInterface,及MVPBaseViewInterface,分别用于MVPActivity及MVPBaseActivity的回调,其中子接口根据业务区别一般是需要建立多个的。

/**
 * @author hongri
 * @date 2018/9/4
 *
 * 记录某个业务方所需的回调方法
 */

public interface MVPViewInterface extends MVPBaseViewInterface {

    /**
     * 业务方数据成功获取后,调用此方法更新UI
     *
     * @param data
     */
    void showSuccessData(String data);

    /**
     * 业务方数据获取失败后,调用此方法展示失败页面UI
     *
     * @param data
     */
    void showFailureData(String data);

}

 


/**
 * @author hongri
 * @date 2018/9/3
 *
 * 该接口记录所有页面通用的回调方法
 */

public interface MVPBaseViewInterface {

    /**
     * 展示通用页面Loading
     */
    void showLoading();

    /**
     * 取消通用页面Loading
     */
    void dismissLoading();
}

 

2、Presenter层:

  Presenter层主要用于业务逻辑操作,避免了Activity的代码臃肿。

/**
 * @author hongri
 * @date 2018/9/3
 *
 * Presenter层
 */

public class MVPPresenter extends MVPBasePresenter<MVPViewInterface> implements MVPLoadDataCallback {
    /**
     * 请求数据入口
     *
     * @param url
     */
    public void requestData(String url) {
        getAttachView().showLoading();
        MVPDataModelManager.newInstance(MVPDataModel.class.getName()).setParams("").executeGetRequest(url, this);
    }

    /**
     * 数据请求成功--更新UI
     *
     * @param successData
     */
    @Override
    public void onSuccess(String successData) {
        if (isViewAttached()) {
            getAttachView().dismissLoading();
            getAttachView().showSuccessData(successData);
        }
    }

    /**
     * 数据请求失败--更新展示错误页面UI
     *
     * @param errorData
     */
    @Override
    public void onFailure(String errorData) {
        if (isViewAttached()) {
            getAttachView().dismissLoading();
            getAttachView().showFailureData(errorData);
        }
    }
}
/**
 * @author hongri
 * @date 2018/9/4
 */

public class MVPBasePresenter<V extends MVPBaseViewInterface> {

    public V mView;

    /**
     * 绑定mView,一般在初始化中调用该方法
     */
    public void attachView(V view) {
        mView = view;
    }

    /**
     * 销毁mView,一般在onDestroy方法中调用
     */
    public void detachView() {
        if (mView != null) {
            mView = null;
        }
    }

    /**
     * 是否与mView建立联系
     * 每次调用业务方请求的时候都要先调用此方法检查是否与mView建立联系
     */
    public boolean isViewAttached() {
        return mView != null;
    }

    /**
     * 获取连接的mView
     */
    public V getAttachView() {
        return mView;
    }
}

 

3、Model层:

 该Model层与MVC模式中的Model层类似,项目中所有数据都由该类流入和流出,负责分发所有的请求数据。

/**
 * @author hongri
 * @date 2018/9/3
 *
 * Model层
 */

public class MVPDataModel extends MVPBaseModel {

    public String data = "";

    @Override
    public void executeGetRequest(String url, final MVPLoadDataCallback callback) {
        /**
         * 模拟网络请求耗时操作
         */
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                /**
                 * 从服务端请求回来的字符串数据:
                 * 为了增加通用性,建议以请求回来的原有数据返回给View层,由View层做具体的数据解析、UI展示处理
                 */
                data = "{\n"
                    + "  \"mData\":\"红日\",\n"
                    + "  \"gender\": \"男士\",\n"
                    + "  \"age\":18\n"
                    + "}";
                if (!TextUtils.isEmpty(data)) {
                    callback.onSuccess(data);
                } else {
                    data = "数据获取失败";
                    callback.onFailure(data);
                }
            }
        }, 1000);
    }
}

 

这里也封装了一个父类base层--MVPBaseModel,MVPBaseModel是所有Model的*父类,负责对外提供数据请求标准,对内为所有Model提供请求的底层支持。

/**
 * @author hongri
 * @date 2018/9/5
 */

public abstract class MVPBaseModel {

    protected String[] mParams;

    /**
     * 设置数据请求参数
     *
     * @param args 参数数组
     */
    public MVPBaseModel setParams(String... args) {
        mParams = args;
        return this;
    }

    /***
     * 执行Get网络请求
     * @param url
     * @param callback
     */
    public abstract void executeGetRequest(String url, MVPLoadDataCallback callback);

    /**
     * 执行Post网络请求
     *
     * @param url
     * @param params
     * @param callback
     */
    protected void executePostRequest(String url, Map params, MVPLoadDataCallback callback) {

    }
}

 

另外这里也定义了一个DataModelManager,是针对多有Model的管理类,把其作为入口,利用反射机制可以获得对应Model对象的引用。

/**
 * @author hongri
 * @date 2018/9/5
 *
 * 统一Model管理器
 */

public class MVPDataModelManager {

    public static MVPBaseModel mvpModel;

    public static MVPBaseModel newInstance(String modelName) {

        try {
            mvpModel = (MVPBaseModel)Class.forName(modelName).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return mvpModel;
    }
}

 

三、MVVM框架

Android开发框架模式(MVC、MVP、MVVM)实例解析

          图片源自网络

MVVM的全称是Model(模型)--View(视图)--ViewModel(视图模型):

MVVM可以算是MVP的升级版,将 Presenter 改名为 ViewModel。关键在于View和Model的双向绑定,当View有用户输入后,ViewModel通知Model更新数据,同理Model数据更新后,ViewModel通知View更新。

MVVM的优势如下:

1、ViewModel双向绑定,一方的改变都会影响另一方,开发者不用再去手动修改UI的数据。

2、不需要findViewById也不需要butterknife,不需要拿到具体的View去设置数据绑定监听器等等,这些都可以用DataBinding完成。

3、ViewModel的双向绑定是支持生命周期检测的,不会担心页面销毁了还有回调发生,这个由lifeCycle完成。

4、不会像MVC一样导致Activity中代码量巨大,也不会像MVP一样出现大量的ViewPresenter接口。项目结构更加低耦合。

下面具体介绍下MVVM框架:

Android开发框架模式(MVC、MVP、MVVM)实例解析

1、VM层:

这里根据MVVMActivity的布局文件activity_mvvmpattern,介绍下Data Binding:

Data Binding对使用的环境还是有一定要求的:Android Studio版本在1.3以上,gradle的版本要在1.5.0-alpha1以上。
需要在Android SDK manager中下载Android Support repository
然后在对应的Module的build.gradle中添加:

android {
  ....
  dataBinding {
      enabled =true
  }
}

然后使用databinding语法 对 xml 进行数据绑定,我们将 Click事件、输出结果都绑定到VM上。

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <import type="android.view.View"/>

        <variable
            name="userViewModel"
            type="com.hongri.model.mvvm.viewmodel.MVVMDataViewModel"/>

        <variable
            name="handlers"
            type="com.hongri.model.mvvm.view.MVVMActivity"/>

        <variable
            name="data"
            type="String"/>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <Button
            android:id="@+id/btnMVVM"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{handlers.onClickLoadData}"
            android:text="点击请求数据"/>

        <Button
            android:id="@+id/btnToast"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{handlers.onClickShowToastName}"
            android:text="点击请求数据并Toast提示"/>

        <TextView
            android:id="@+id/tvData"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="@{userViewModel.data}"/>
    </LinearLayout>
</layout>

VM层具体类:这里注意具体的属性需要加@Bindable,否则绑定无效。

/**
 * @author zhongyao
 * @date 2018/9/3
 * ViewModel层
 */

public class MVVMDataViewModel extends BaseObservable implements MVVMLoadDataCallback {
    private MVVMDataModel model;

    public MVVMDataViewModel() {
        model = new MVVMDataModel();
    }

    /**
     * 必须添加@Bindable注释
     * @return
     */
    @Bindable
    public String getData() {
        return model.mData;
    }

    public void loadUserData() {
        model.requestData(this);
    }

    @Override
    public void onSuccess() {
        notifyPropertyChanged(BR.data);
    }

    @Override
    public void onFailure() {

    }
}

2、View层(MVVMActivity)引入VM:

/**
 * @author hongri
 * View层
 */
public class MVVMActivity extends AppCompatActivity {

    private MVVMDataViewModel userViewModel;
    private TextView tvData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMvvmpatternBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvmpattern);
        userViewModel = new MVVMDataViewModel();
        binding.setUserViewModel(userViewModel);
        binding.setHandlers(this);

        tvData = binding.tvData;

    }

    public void onClickShowToastName(View view) {
        Toast.makeText(this, tvData.getText().toString(), Toast.LENGTH_LONG).show();
    }

    public void onClickLoadData(View view) {
        userViewModel.loadUserData();
    }
}

3、Model层:

 用来进行具体的数据请求操作。

/**
 * @author hongri
 * @date 2018/9/3
 * Model层
 */

public class MVVMDataModel {
    public String mData;

    public MVVMDataModel() {
        this.mData = "初始数据";
    }

    public void requestData(MVVMLoadDataCallback callback) {
        this.mData = "数据请求成功";
        callback.onSuccess();
    }
}

MVVM虽然有这些个优点,但使用起来的坑也是不少的,所以目前MVVM框架在实际开发中应用并不是很多,主要以MVP框架开发为主。

 

最后提供下github源码,供大家参考:

源码下载