是时候换一种架构了

作者:计算机网络

MVP

MVP是模型、视图、主持人(Presenter)的缩写,分别代表项目中3个不同的模块。如图所示:

图片 1image

  • View 对应于Activity、Fragment,负责界面的绘制以及与用户交互
  • Model 依然是业务逻辑和实体模型
  • Presenter 负责完成View于Model间的交互
  • 首先在我们常用的MVC模式中,Activity承载了太多,做了不只是视图层的事情,而程序开发中最重要的 Context 一般也是在视图层才拥有的,所以我们需要把Context保持在视图中。
  • MVP相对于MVC,MVP中是依赖Presenter这个接口任务调度器来实现任务调度,则视图层中所有需要进行数据交互的,都需要将数据交给Presenter,而Presenter将调用Model来加载数据。
  • 在传统的MVC中,我常用 initView()、initData()、initEvent()、doOther() 这几个方法来实现数据流程加载、界面交互实现。现在我们需要拆分出来,Activity从BaseActivity中实现。

经过这样的构思,我们可以先实践一下,我们让View来实现Model的接口,View来调用presenter,presenter利用面向接口编程的思想来调用接口实现对View的操作。实例如下:

import android.content.Context;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import com.acheng.achengutils.mvp.model.BaseViewController;import com.acheng.achengutils.mvp.presenter.BasePresenter;/** * Created by pc859107393 on 2016/6/28. */public abstract class BaseActivity<T extends BasePresenter, M extends BaseViewController> extends AppCompatActivity { public String TAG; //当前Activity的标记 protected T mPresenter; //主持人角色 protected abstract T initPresenter(); //获取到主持人 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TAG = String.format("%s::%s", getPackageName(), getLocalClassName; mPresenter = initPresenter(); //初始化Presenter,提供主持人,拥有主持人后才能提交界面数据给presenter setContentView(setLayoutId; initView(); mPresenter.initData(); initEvent(); doOther(); } protected void doOther() { } public Context getContext() { return this; } protected abstract void initEvent(); protected abstract void initView(); protected abstract int setLayoutId(); @Override protected void onResume() { super.onResume(); //如果presenter为空的时候,我们需要重新初始化presenter if (mPresenter == null) { mPresenter = initPresenter(); } } @Override protected void onPause() { super.onPause(); } @Override public void onBackPressed() { //返回按钮点击事件 //当Activity中的 进度对话框正在旋转的时候(数据正在加载,网络延迟高,数据难以加载),关闭 进度对话框 , 然后可以手动执行重新加载 super.onBackPressed(); } /** * 恢复界面后,我们需要判断我们的presenter是不是存在,不存在则重置presenter * * @param savedInstanceState */ @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); if (mPresenter == null) mPresenter = initPresenter(); } /** * onDestroy中销毁presenter */ @Override protected void onDestroy() { super.onDestroy(); mPresenter = null; }}

既然我们的Activity已经设定好了BaseActivity,我们需要接着完成BasePresenter,如下:

import com.acheng.achengutils.mvp.model.BaseViewController;/** * Created by acheng on 2016/7/14. */public abstract class BasePresenter<D extends BaseViewController> { public D model; /** * 在子类的构造函数中,设定参数为model,这时候可以presenter调用接口来实现对界面的操作。 */ public BasePresenter { this.model = model; } public abstract void initData();}

关于我这个Presenter的设计,我想说的是我们需要将各层解耦,那么我的presenter就不应该持有Android程序流转的必然因子,如Context、Bundle、Intent、View等,如果我们需要实现对界面的操作,必须通过调用我们设定好的Model来实现,关于BaseModel更加简单了,直接是一个空的接口文件,如下:

public interface BaseViewController { //这里面添加实现类需要实现的方法即可}
  • presenter作为主持人,应该随着视图的关闭而关闭,所以我们需要在Activity和Fragment的关闭的时候,注销相应的presenter
  • 在应用程序被销毁的时候,我们重启了程序,但是这时应用的状态如果不恢复到前面的状态那么我们需要把对应的presenter重建
  • 在应用恢复后,如果想保持刚才的状态,那么我们需要在被销毁前把视图的状态保存,并且恢复对应的状态

说了这么多,我们直接手底下见真章:

import android.Manifest;import android.annotation.TargetApi;import android.content.DialogInterface;import android.content.Intent;import android.content.pm.PackageManager;import android.net.Uri;import android.os.Build;import android.provider.Settings;import android.support.v7.app.AlertDialog;import android.view.View;import android.widget.TextView;import com.acheng.achengutils.mvp.view.BaseActivity;import com.acheng.achengutils.utils.SPHelper;import com.acheng.achengutils.widgets.AppUpdateDialog;import com.acheng.achengutils.widgets.MustDoThingDailog;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import acheng1314.cn.a3dbuild.MyApplication;import acheng1314.cn.a3dbuild.R;import acheng1314.cn.a3dbuild.bean.LoginBean;import acheng1314.cn.a3dbuild.view.activity.presenter.LoginActivityPresenter;import acheng1314.cn.a3dbuild.view.activity.viewcontroller.LoginActivityViewController;import acheng1314.cn.a3dbuild.widgets.MyProgressDialog;/** * Created by pc859107393 on 2016/9/12 0012. */public class LoginActivity extends BaseActivity<LoginActivityPresenter, LoginActivityViewController> implements LoginActivityViewController { private View mBt_login; private TextView mEt_username; //用户名 private TextView mEt_password; //密码s final private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124; private AppUpdateDialog appPermission; //权限申请对话框 private MyProgressDialog myProgressDialog; //进度对话框 @Override protected LoginActivityPresenter initPresenter() { return new LoginActivityPresenter; //实例化LoginActivity的Presenter } @Override protected void initEvent() { mBt_login.setOnClickListener(new View.OnClickListener() { @Override public void onClick { MyApplication.getInstance().outLog(TAG, "MDZZ"); //日志输出 //调用Presenter的登录的网络请求,将用户名和密码传递过去 mPresenter.doLogin(mEt_username.getText().toString(), mEt_password.getText().toString; } @Override protected void initView() { MyApplication.getInstance().addActivity; //将Activity加入堆栈管理 mEt_username =  findViewById(R.id.mEt_username); mEt_password =  findViewById(R.id.mEt_password); mBt_login = findViewById(R.id.mBt_login); } @Override protected void doOther() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { List<String> permissionsNeeded = new ArrayList<String>(); final List<String> permissionsList = new ArrayList<String>(); if (!addPermission(permissionsList, Manifest.permission.WRITE_EXTERNAL_STORAGE)) permissionsNeeded.add; if (!addPermission(permissionsList, Manifest.permission.READ_PHONE_STATE)) permissionsNeeded.add; if (!addPermission(permissionsList, Manifest.permission.CAMERA)) permissionsNeeded.add; if (!addPermission(permissionsList, Manifest.permission.ACCESS_COARSE_LOCATION)) permissionsNeeded.add;// if (!addPermission(permissionsList, Manifest.permission.WRITE_SETTINGS))// permissionsNeeded.add; if (permissionsList.size { if (permissionsNeeded.size { //待申请的权限列表 // Need Rationale String message = "你必须允许本APP使用:"   permissionsNeeded.get; for (int i = 1; i < permissionsNeeded.size message = message   ", "   permissionsNeeded.get; showMessageOKCancel(message, new DialogInterface.OnClickListener() { @TargetApi(Build.VERSION_CODES.M) @Override public void onClick(DialogInterface dialog, int which) { requestPermissions(permissionsList.toArray(new String[permissionsList.size, REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS); } }); return; } requestPermissions(permissionsList.toArray(new String[permissionsList.size, REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS); } } super.doOther(); } private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) { new AlertDialog.Builder .setMessage .setPositiveButton("允许", okListener) .setNegativeButton("拒绝", null) .create; } private boolean addPermission(List<String> permissionsList, String permission) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { permissionsList.add(permission); if (!shouldShowRequestPermissionRationale(permission)) return false; } } return true; } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { switch (requestCode) { case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS: { Map<String, Integer> perms = new HashMap<String, Integer>(); // Initial perms.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, PackageManager.PERMISSION_GRANTED); perms.put(Manifest.permission.READ_PHONE_STATE, PackageManager.PERMISSION_GRANTED); perms.put(Manifest.permission.CAMERA, PackageManager.PERMISSION_GRANTED); perms.put(Manifest.permission.ACCESS_COARSE_LOCATION, PackageManager.PERMISSION_GRANTED); // Fill with results for (int i = 0; i < permissions.length; i  ) perms.put(permissions[i], grantResults[i]); // Check for ACCESS_FINE_LOCATION if (perms.get(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED && perms.get(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED && perms.get(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED && perms.get(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) { //经过用户授权,获得所有权限 if (appPermission != null) { appPermission = null; } // All Permissions Granted } else { //未得到用户授权 // Permission Denied appPermission = new AppUpdateDialog(AppUpdateDialog.IMPORTANT, "一些权限未被允许,请在设置中授权!", getContext(), new AppUpdateDialog.NeedDoThing() { @Override public void mustDoThing() { Uri packageURI = Uri.parse("package:"   getPackageName; Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI); startActivity; } }); } } break; default: super.onRequestPermissionsResult(requestCode, permissions, grantResults); } } @Override protected void onResume() { super.onResume(); doOther(); } @Override protected int setLayoutId() { return R.layout.activity_login; } @Override public void showDailog(String msg) { new MustDoThingDailog("提示", msg, getContext(), new MustDoThingDailog.NeedDoThing() { @Override public void mustDoThings; } @Override public void showProgressD() { if (null == myProgressDialog) myProgressDialog = new MyProgressDialog("登陆", "正在登录···", getContext; else myProgressDialog.show(); } @Override public void disProgressD() { if (null != myProgressDialog) myProgressDialog.dismiss(); } @Override public void openHome(LoginBean bean) { SPHelper.setString(getContext(), getContext().getString(R.string.user), getContext().getString(R.string.username), mEt_username.getText().toString; SPHelper.setString(getContext(), getContext().getString(R.string.user), getContext().getString(R.string.password), mEt_password.getText().toString; SPHelper.setString(getContext(), getContext().getString(R.string.user), getContext().getString(R.string.userId), bean.getResult().getUserId; SPHelper.setString(getContext(), getContext().getString(R.string.user), getContext().getString(R.string.token), bean.getResult().getToken; startActivity(new Intent(getContext(), HomeActivity.class)); finish(); }}

其实上面我们当中可以看到我们前台界面拿到用户数据后,调用presenter的doLogin方法,把用户名和密码传递过去,然后我们在Presenter中请求网络然后再通过调用接口实现数据回传。如下:

import com.acheng.achengutils.gsonutil.GsonUtils;import com.acheng.achengutils.mvp.presenter.BasePresenter;import com.acheng.achengutils.utils.CipherUtils;import com.acheng.achengutils.utils.StringUtils;import com.kymjs.rxvolley.RxVolley;import com.kymjs.rxvolley.client.HttpCallback;import com.kymjs.rxvolley.client.HttpParams;import com.kymjs.rxvolley.http.VolleyError;import acheng1314.cn.a3dbuild.MyApplication;import acheng1314.cn.a3dbuild.bean.LoginBean;import acheng1314.cn.a3dbuild.hostApi.MyApi;import acheng1314.cn.a3dbuild.view.activity.viewcontroller.LoginActivityViewController;/** * Created by pc859107393 on 2016/9/12 0012. */public class LoginActivityPresenter extends BasePresenter<LoginActivityViewController> { /** * 在子类的构造函数中,设定参数为model,这时候可以presenter调用接口来实现对界面的操作。 * * @param model */ public LoginActivityPresenter(LoginActivityViewController model) { super; } @Override public void initData() { } public void doLogin(String name, String pwd) { //用户名和密码不能为空 if (StringUtils.isEmpty || StringUtils.isEmpty { model.showDailog("用户名或密码不能为空!"); //调用model的错误提示对话框 return; } //密码MD5加密 pwd = CipherUtils.small32md5; HttpParams params = new HttpParams(); params.put("userName", name); params.put("passWord", pwd); RxVolley.post(MyApi.LoginApi, params, new HttpCallback() { @Override public void onSuccess { super.onSuccess; //数据不为空再进行数据处理 try { if (null != t) { MyApplication.getInstance().outLog; LoginBean bean = new GsonUtils().toBean(t, LoginBean.class); if (null != bean) { if (bean.getCode { //请求成功 model.openHome; } else if (bean.getCode { model.showDailog("登录失败,帐户不存在"); } else if (bean.getCode { model.showDailog("登录失败,密码错误"); } else { model.showDailog("登录失败,其他未知错误"); } } } } catch (Exception e) { e.printStackTrace(); model.showDailog("登录失败,其他未知错误"); } } @Override public void onFailure(VolleyError error) { super.onFailure; model.showDailog("登录失败,其他未知错误"); } @Override public void onFinish() { super.onFinish(); model.disProgressD(); //model的关闭对话框的接口 } @Override public void onPreStart() { super.onPreStart(); model.showProgressD(); //model的进度对话框 } }); }}

我们上面可以看到我们现在只要把请求网络的数据传递上去就可以完成单元测试了,这样子我们就达到了我们数据流转的单元测试的标准。

既然我们都看到了Presenter对model的调用,那么我们直接贴上model再对比Activity就能明白了我们是怎么完成这个设计的。

public interface LoginActivityViewController extends BaseViewController { /** * 显示信息提示对话框 * @param msg message */ void showDailog(String msg); /** * 显示进度对话框 */ void showProgressD(); /** * 关闭对话框 */ void disProgressD(); /** * 登陆成功跳转到其他界面 * @param bean */ void openHome(LoginBean bean);}

我们看到这里,很多哥们可能又会不明白,为什么我们能控制界面呢?如下:

//我们在程序中,presenter直接调用的model,但是model是被View实现了的。public class LoginActivity extends BaseActivity<LoginActivityPresenter, LoginActivityViewController> implements LoginActivityViewController { @Override public void showDailog(String msg) { //实现了model的显示对话框的方法 new MustDoThingDailog("提示", msg, getContext(), new MustDoThingDailog.NeedDoThing() { @Override public void mustDoThings; } @Override public void showProgressD() { //这是显示进度对话框的,实现了model的方法 } @Override public void disProgressD() { //这是实现了moel的关闭进度对话框的方法 } @Override public void openHome(LoginBean bean) { //实现了model的打开其他页面的方法 }}

所以我们的MVP执行的步骤其实就是:用户执行操作 -> 调用presenter(完成独立的数据处理) -> 调用model的方法控制界面 -> 展示给用户

然后应该又有哥们会问我,为什么你的基类中会有<>这种括号括起来的东西,恩恩这个是泛型,主要是用来说明他们是哪一类的东西,通过泛型来解耦就可以在基类中整合更多的东西。具体的要我来说明的话,我只能说“就不!!!”,我需要任性一回。关于MVP更好的介绍可以看下github的项目TheMvp,这个是我的偶像@张涛写的哟。

  • 在mvp架构中,我们需要在基类中拿到每个界面对应的presenter和model,则我们需要让程序知道每个对应的presenter和model.
  • 为了减少不必要的代码开销,我们需要把每个activity和Fragment的公共方法抽取出来,写入基类中.
  • 在基类中,我们需要将具体的presenter和model解耦,则需要泛型进行类型转换来解除耦合.
  • 泛型解除耦合后,我们需要在每个具体的view中来持有presenter和实现model层的接口.并且通过每个view关联的presenter调用model的某个方法来控制view.

MVC ( Model - View - Controller ) 是一种软件框架设计模式,其将业务逻辑,数据,界面显示分离的方式组织代码.彼此分离解耦,实现较好的维护性和可扩展性.其中,Model层负责处理数据和业务逻辑工作;View层负责界面的显示与更新的结果;而Controller则是两者的媒介或是桥梁,控制View层与Model层的通信来达到这两层的分离效果.

图片 2

MVP简介

相信大家对MVC都是比较熟悉了:M-Model-模型、V-View-视图、C-Controller-控制器,MVP作为MVC的演化版本,那么类似的MVP所对应的意义:M-Model-模型、V-View-视图、P-Presenter-表示器。 从MVC和MVP两者结合来看,Controlller/Presenter在MVC/MVP中都起着逻辑控制处理的角色,起着控制各业务流程的作用。而 MVP与MVC最不同的一点是M与V是不直接关联的也是就Model与View不存在直接关系,这两者之间间隔着的是Presenter层,其负责调控 View与Model之间的间接交互,MVP的结构图如下所示,对于这个图理解即可而不必限于其中的条条框框,毕竟在不同的场景下多少会有些出入的。在 Android中很重要的一点就是对UI的操作基本上需要异步进行也就是在MainThread中才能操作UI,所以对View与Model的切断分离是 合理的。此外Presenter与View、Model的交互使用接口定义交互操作可以进一步达到松耦合也可以通过接口更加方便地进行单元测试。

MVP结构图

关于android的MVP模式其实一直没有一个统一的实现方式,不同的人由于个人理解的不同,进而产生了很多不同的实现方式,其实很难去说哪一种更好,哪一种不好,针对不同的场合,不同的实现方式都有各自的优缺点。

而我采用的MVP是Google提出的一种MVP实现方式,个人认为这种方式实现简单,更加适合android项目。传统的MVP中Model起着处理具体逻辑的功能,Presenter起着隔离和解耦的作用,而在Google的MVP中弱化了Model,Model的逻辑由Presenter来实现,spManager、dbManager可以看做是Model,由Fragment实现View实现视图的变化,Activity作为一个全局的控制者,负责创建view以及presenter实例,并将二者联系起来。

human - - > Controller ----> Model ----> View - - > human ( 其中  Controller <----> 数据库 (懒得做图 =.=))

1.MVP简介2.基类抽取3.项目运用4.MVP开源框架

实现步骤

Android中如何运用呢,相对应的角色应该怎么分配的?在Android中,Model层中适合做一些业务逻辑处理,多包括一些数据存取,网络请求,复杂算法和一些耗时任务操作等;Android中的Activity和xml布局可以视为View层;并且Activity也作为Controller的存在,Activity处理用户交互请求,实现View视图,发起Model层请求作业等.

之前做项目一直使用的是MVC架构,后来MVP大热,随之越来越多的项目开始使用这个架构,因为对MVC的使用熟悉,快速开发项目,所以一直还是使用MVC。在闲暇时间也开始接触学习MVP,后来在原有项目的新功能和新项目中开始使用MVP,MVP虽然有一些缺点但确实比MVC好很多。现在各种博客中讲解MVP大都以一个小Demo,比如登入操作来讲解说明它的使用,以至于对于初学者不能快速用于项目中。其实还是推荐通过学习官方给出的Demo,来理解MVP。文本以整体项目来说明理解MVP。谷歌官方Android框架Demo地址因为官方的都是英文的,所以这里推荐一篇博客,对官方Demo的理解和说明和详细,便于理解:官方MVP项目学习

1.BasePresenter

public interface BasePresenter {
    void start();
}

具体实现,举个例子:你可以定义一系列Model层业务数据处理接口

MVP是由MVC演化而来的,所以前提是对MVC的理解,这个不在阐述MVC。

2.BaseView

public interface BaseView<P extends BasePresenter> {
    void setPresenter(P presenter);
}

两个接口分别作为Presenter和View的基类,仅定义了最基本的方法,具体页面的view和presenter则分别定义继承的接口,添加属于自己页面的方法。

图片 3

图片 4

3.Contract 契约类

这是Google MVP与其他实现方式的不同之一,契约类用于定义同一个界面的view和presenter的接口,通过规范的方法命名或注释,可以清晰的看到整个页面的逻辑。

public interface SampleContract {

    interface Presenter extends BasePresenter {
        //获取数据
        void getData(App app, int userId);
        //检查数据是否有效
        void checkData();
        //删除消息
        void deleteMsg(App app, int msgId);
        ...
    }

    interface View extends BaseView<Presenter> {
        //显示加载中
        void showLoading();
        //刷新界面
        void refreshUI(MessageListEntity.CategoryData data);
        //显示错误界面
        void showError();
        ...
    }
}

Model层方法接口

MVP的出发点是关注点分离,将视图和业务逻辑解耦。Model-View-Presenter三个部分可以简单理解为:

4.具体的Impl类

Fragment实现View接口,这里使用Google推荐的创建Fragment实例的方法newInstance(),将fragment必备的参数传入。

public class SampleFragment extends BaseFragment implements SampleContract.View{

    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    private String mParam1;
    private String mParam2;

    private SampleContract.Presenter mPresenter;


    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param param1 Parameter 1.
     * @param param2 Parameter 2.
     * @return A new instance of fragment SampleFragment.
     */
    // TODO: Rename and change types and number of parameters
    public static SampleFragment newInstance(String param1, String param2) {
        SampleFragment fragment = new SampleFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        TextView textView = new TextView(getActivity());
        textView.setText(R.string.hello_blank_fragment);
        return textView;
    }

    @Override
    public void setPresenter(SampleContract.Presenter presenter) {
        mPresenter = presenter;
    }

    @Override
    public void refreshUI(MessageListEntity.CategoryData data) {
        //change UI
    }

    @Override
    public void showError() {
        //change UI
    }
}

Presenter实现类,提供一个参数为对应View的构造器,持有View的引用,并调用View的setPresenter()方法,让View也持有Presenter的引用,方便View调用Presenter的方法。

public class SamplePresenterImpl implements SampleContract.Presenter {

    private SampleContract.View mView;

    public SamplePresenterImpl(SampleContract.View mView) {
        this.mView = mView;
        mView.setPresenter(this);
    }

    ...
}

图片 5

  • Model:获取视图中显示的数据和相应的业务逻辑。
  • View:显示数据的界面,同时将用户指令发送给Presenter来处理。View通常含有Presenter的引用。在Android中Activity,Fragment和ViewGroup都扮演视图的角色。
  • Presenter:中间人,同时有两者的引用。请注意单词model非常有误导性。它应该是获取或处理model的业务逻辑。例如:如果你的数据库表中存储着User,而你的视图想显示用户列表,那么Presenter将有一个数据库业务逻辑类的引用,Presenter通过它来查询用户列表。

5.最后就是Activity

创建view以及presenter实例,并将二者联系起来。

public class SampleActivity extends BaseActivity {

    public static final String FRAGMENT_TAG = "fragment_tag";

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

    private void init() {
        //初始化view
        FragmentManager fragmentManager = getSupportFragmentManager();
        SampleFragment fragment = (SampleFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
        if (fragment == null) {
            fragment = SampleFragment.newInstance(param1, param2);
            fragmentManager.beginTransaction().add(R.id.fl_container, fragment, FRAGMENT_TAG).commit();
        }

        //初始化presenter
        new SamplePresenterImpl(fragment);
    }
}

Model层一个网络请求的实现

简单来说就是:View负责页面展示和传递用户操作,当用户在页面上操作时,比如刷新页面,那么在View中就会调用Presenter的刷新页面方法,但是刷新页面获取数据的操作并不是Presenter完成的,而是在它的刷新方法中调用Model对应的方法来完成的,也就是说真正数据的获取是由Model来获取的,Presenter只是一个中介。

下图是Google官方Demo:todo-mvp模式的架构图

从结构方面观察一下MVP模式

20160510110516376.png

图片 6

所以,View中持有Presenter的引用,Presenter持有Model的引用。View是怎么调用Presenter的呢?其实还有一个角色,就是View interface,View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试。

为什么使用MVP?

文章一开始并没有说MVP的好处,而是先介绍了如何实现MVP,在这里我会通过分析来体现MVP的好处。

Activity中通过Model对象直接调用其方法实现起Controller对Model层的控制作用,Model层调用View层接口实现其对View层的更新.这就是一种简单的MVC框架模式了.以后若是想更换网络请求框架或其他类似的更新扩展操作,就比较好维护.有较好的代码的可维护性与扩展性.

关于项目的分包,没有绝对的好坏,看自己更习惯与哪一种,我喜欢根据功能模块分,也就是将实现某一功能的相关类放在同一包下。

1.解耦

毋庸置疑这是最大的好处,通过上述例子可以看到一个页面被分成了两个部分,一个是视图的逻辑,另一个是业务逻辑。两者持有的引用都是对方的接口,因此可以随意地替换实现(页面大修改),并且单独修改view或者presenter的逻辑并不会影响另一方(小修改)。

MVP ( Model - View - Presenter ) 请先正襟危坐仔细研读官方架构Android架构蓝图. 

图片 7

2.代码清晰

以前的MVC模式,当一个页面的逻辑足够复杂,就会出现一个activity上千行的情况,在这样的一个类中想定位一个bug是十分困难的,有时候自己的敲的代码看着都像别人的代码,得重头捋一捋才能定位,相当耗时。而用MVP模式,一个页面的逻辑都可以从Contract类中直观的看到有哪些,找到对应逻辑的方法再进入实现类中找到问题所在,效率上不可同日而语。

MVP将复杂任务划分为更小的任务, View层变得更加简单.数据业务逻辑与一些界面相关更新操作更加解耦分离开来,代码的灵活性提升.

每一个界面都有对应的Model,Presenter,还有一个Contract,它是协议的意思,规定Model,View ,Presenter之前的关系。很方便通过它查看它们之前的联系以及要实现的功能。

3.灵活

好多MVP的实现都是用Activity来实现View,这里使用Fragment便是出于灵活的考虑,Fragment可以复用,可以替换。面对多变的需求可以更从容的应对,例如:一个页面原本是一个列表,后来要求这个页面显示2个Tab,原来的列表变为其中一个Tab。类似的情况可能很常见,如果是用Activity实现的,可能要修改activity类中的很多代码,而原本就使用Fragment和MVP的话,仅需添加一个Fragment,修改Activity假如切换Tab的逻辑,而不用修改第一个Fragment的任何逻辑,这更符合OOP的编程思想。

1.data <----> presenter <----> view

图片 8

4.简化

开头说过,传统MVP中Model起着处理具体逻辑的功能,Presenter起着隔离和解耦的作用。这种方式实现的MVP中Presenter看上去更像一个代理类,仅仅是不让View直接访问Model。虽然这样做解耦的程度更高,但实际项目中,一个页面逻辑的修改是非常少的,仅仅在产品修改需求是才会发生,大部分情况下仅仅是修复bug之类的小修改,并需要这样彻底的解耦。从另一个方面来说,一个页面的视图逻辑和业务逻辑本就是一体的。因此,Google的MVP弱化的Model的存在,让Presenter代替传统意义上的Model,减少了因使用MVP而剧增的类。这种方式相比不适用MVP仅从1个activity,增加到了1个activity,1个fragment(view),1个presenter,1个contract(如果有传统意义上的Model,还会有1个Model的接口和1个实现类)。

2.data <----> presenter <----> view (其中不同data ----> 1.presenter)

在官方的Demo中没有把Model放入其中,我觉得把Model也放进去更加便于查看和理解它们之间的联系。Contract本身是一个接口,只是规定协议,所以对应的Model,Presenter,View都要实现对应的接口。

注意点!

1.一段逻辑到底是放在View还是放在Presenter?其实从定义我们也可以知道View只处理界面的逻辑,任何逻辑判断都应该在presenter中实现。但在实际过程中很容易出现View处理逻辑的情况,例如:网络请求返回一些数据,需要对数据进行删选或处理,处理的过程很容易发生在View层,这是需要极力避免的情况,所有不涉及界面的判断都要放到presenter层。

2.类名、方法名的规范。页面的契约类都以contract结尾,presenter的实现类都以PresenterImpl结尾。View和Presenter接口中的方法需要见名知意,意义模糊的可以适当添加注释。规范的命名有助于后续的修改和他人的维护。

3.子线程回调。Presenter中处理子线程任务完成后,一般会回到主线程调用View的方法刷新UI,但如果此时activity已经销毁,并且没有取消子线程的任务(例如网络请求),此时去调用View的方法很容易发生空指针,应该在调用之前判断一下,因此建议view增加一个isActive()方法,用于判断当前view是否可见:

public interface BaseView<P extends BasePresenter> {
    void setPresenter(P presenter);
    boolean isActive();
}

在fragment中只需这样实现即可:

    @Override
    public boolean isActive() {
        return isAdded();
    }

除此之外,如果子线程的任务会持续很久,或者由于网络等额外因素导致子线程耗时过久,但此时activity其实已经destroy了,presenter的子线程还在运行则不会被GC,并且presenter持有了fragment(view),导致了内存泄露。
解决这个问题可以通过弱应用的方式解决,下是实现方式:

public abstract class BasePresenterImpl<V extends BaseView> implements BasePresenter {

    protected WeakReference<V> mView;

    public BasePresenterImpl(V view) {
        mView = new WeakReference<V>(view);
        view.setPresenter(this);
    }

    protected boolean isViewActive() {
        return mView != null && mView.get().isActive();
    }

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

}

view也可以抽象abstract类。

基类

public class ArticleDetailActivity extends BaseFrameActivity<DetailPresenter, DetailModel> implements DetailContract.View public class DetailModel implements DetailContract.Model public class DetailPresenter extends DetailContract.Presenter 

图片 9

在Activity需要显示数据的时候是通过Presenter来实现的

图片 10

图片 11

这两个Base基类分别是所有Presenter与View的基类.BaseView中的setPresenter方法作用是将presenter实例传入view中,调用时机是在presenter实现类的构造方法中.BasePresenter中的start方法作用是开始获取数据并调用view中方法刷新界面,其调用时机是在Fragment类的onResume方法中.但其并不是必须用到的.

Presenter中并没有真是实现,而是调用Model对应的方法。

契约类,统一管理所有view与presenter的方法接口,直接可以看到一个页面所有的交互功能接口,一目了然.

图片 12

图片 13

Model真正的去处理业务逻辑获取数据

Contract契约类

图片 14

Activity于MVP中是一个全局的控制者,负责创建view( fragment )以及presenter实例,并将两者联系起来.

每一个功能中都会有Model,Presenter,View Interface,Activity和Fragment中也会有相同的方法,所以抽取基类那是必然的。具体该怎么抽取呢?

图片 15

在项目中并不是所有的功能都会用到MVP的模式,别入闪屏页展示一个固定的图片。所以考虑这种情况不能直接抽取一个MVP的基类,我们可以跟MVC中那样抽取一个基类,然后让MVP基类再继承该基类就可以。下面看具体的代码实现:

全局控制者Activity

//Activity和Fragment公共方法,抽取成了一个接口,各自实现即可。public interface BaseFuncIml {/* 初始化数据方法 */void initData();/* 初始化UI控件方法 */void initView();/* 初始化事件监听方法 */void initListener();/* 初始化界面加载方法 */void initLoad();}

将fragment作为view层的实现类,让Activity作为全局控制者来创建对象,Activity还可以处理一些外层的比如toolbar等外层界面操作.其实这样两者更能各司其职,fragment对于适配也比较灵活.

因为Activity和Fragment有一些公共方法,而它们没有共同的父类,所以这里定义一个接口,在接口中定义公共方法,各自实现即可。

图片 16

//BaseFragmentpublic class BaseFragment extends Fragment implements BaseFuncIml { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { initData(); initView(); initListener(); initLoad();}//BaseActivitypublic class BaseActivity extends AppCompatActivity implements BaseFuncIml, View.OnClickListener{ @Override protected void onPostCreate(@Nullable Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); initData(); initView(); initListener(); initLoad();}

图片 17

这里只是贴出关键的代码,并不是全部的。需要什么方法自己添加即可。

调用view层方法将自身传入

这只是普通的基类,下面看一下MVP中的基类:

presenter的构造方法将自身实例传入,如果需要界面做出相应的变化,直接通过view调用view层方法即可,而在view层中在setPresenter中得到presenter实例,调用presenter中方法处理model层的方法接口.model层的实现被赋予了数据获取的职责.在实例中,数据的获取,存储,数据状态变化都是model层的任务,presenter会根据调用该层的数据处理逻辑并在需要时将回调传入.这样model,presenter,view都只处理各自的任务.

public abstract class BaseFrameActivity<P extends BasePresenter, M extends BaseModel> extends BaseActivity implements BaseView{ public P mPresenter; public M mModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mPresenter = TUtil.getT; mModel = TUtil.getT; if (this instanceof BaseView) { mPresenter.attachVM(this, mModel); }} @Override protected void onDestroy() { if (mPresenter != null) mPresenter.detachVM(); super.onDestroy(); }}

代码的可维护性,可扩展性,定位问题的便捷度,易读性等,其实有很多都是优秀代码框架考虑的因素,没有绝对完美的框架结构,只有在不断学习审其优劣,并能真正将其运用到自己的代码结构里才算是能比较好的运用框架吧.

MVP中基类跟普通基类的区别就是:它要实现对应的View Interface,并且持有Presenter的引用,不同的功能对应View Interface,Presenter和Model都是不一样的,所以它们各自也得抽取基类。因为Presenter持有Model的引用,而Presenter是在View中初始化的,所以这里也把Model传递进来了。从代码中看出在Activity创建的时候分别创建了Presenter和Model的实例,然后将Model,View绑定到Presenter中。在销毁的时候解绑。

reference

mPresenter = TUtil.getT;是怎么初始化的呢?因为使用了泛型,所以做初始化的时候要根据具体使用的类型来做初始化,那么就要获取具体传入泛型的类型。具体获取类型代码如下:

框架模式 MVC 在Android中的使用

public class TUtil { public static <T> T getT(Object o, int i) { try { /** * getGenericSuperclass() : 获得带有泛型的父类 * ParameterizedType : 参数化类型,即泛型 * getActualTypeArguments()[] : 获取参数化类型的数组,泛型可能有多个 */ return ((Class<T>) ((ParameterizedType) (o.getClass() .getGenericSuperclass.getActualTypeArguments .newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassCastException e) { e.printStackTrace(); } return null; }}

Android MVP 详解(上)

Java给我们提供了相应的Api来获取具体泛型的类型。

Android官方MVP架构示例项目解析

下面就是Model,Presenter,View Interface 的基类实现:

public abstract class BasePresenter<M, V> { public M mModel; public V mView; public void attachVM { this.mModel = m; this.mView = v; } public void detachVM() { mRxManager.clear(); mView = null; mModel = null; }}public interface BaseModel {}public interface BaseView { void onRequestStart(); void onRequestError(String msg); void onRequestEnd();}

Model不同的功能,方法不同,所以这里是一个空接口;View Interface:可以把所有界面的一些公共操作抽取出来,要根据具体的业务逻辑抽取。

以上基类抽取完毕,以一个文章详情看一下具体的使用:主要公共获取文章的详细内容展示:定义协议DetailContract:

public interface DetailContract { interface Model extends BaseModel { Observable<StoryContentEntity> getStoryContent; Observable<StoryExtraEntity> getStoryExtras; } interface View extends BaseView { void showContent(StoryContentEntity storyContentEntity); void showStoryExtras(StoryExtraEntity storyExtraEntity); } abstract class Presenter extends BasePresenter<Model, View> { abstract void getStoryContent; abstract void getStoryExtras; }}

通过该类我们就可以看出要实现的功能和下一步要实现的东西。所以要根据业务逻辑定义出协议类,然后再根据协议类逐步去实现。

public class ArticleDetailActivity extends BaseFrameActivity<DetailPresenter, DetailModel> implements DetailContract.View { private static final String TAG = "ArticleDetailActivity"; ActionProviderView commentProvider; ActionProviderView praiseProvider; @BindView(R.id.toolBar) Toolbar mToolbar; @BindView(R.id.detail_bar_image) ImageView detailBarImg; @BindView(R.id.detail_bar_title) TextView detailBarTitle; @BindView(R.id.detail_bar_copyright) TextView detailBarCopyright; @BindView(R.id.wv_detail_content) WebView detailContentWV; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_article_detail); ButterKnife.bind; } @Override public void initData() { Intent intent = getIntent(); int articleId = intent.getIntExtra("articleId", 0); if (articleId != 0) { mPresenter.getStoryContent(articleId); mPresenter.getStoryExtras(articleId); } else { ToastUtils.showToast(this, TAG   "数据加载出错"); } } @Override public void initView() { initToolbar(); initWebViewClient(); }}

因为我们把基类定义好了,在具体的实现中只需要继承基类,然后传入具体的类型即可,因为在基类总自动完成了Presenter和Model的绑定,另外业务逻辑已经在Model中实现好了,只需要在View中方法中通过Presenter调用即可。这样能大大简化Activity的代码,不再像MVC中那样,如果业务逻辑比较多,Activity的代码非常繁多。

使用MVP之后,Activity就能瘦身许多了,基本上只有FindView、SetListener以及Init的代码。其他的就是对Presenter的调用,还有对View接口的实现。这种情形下阅读代码就容易多了,而且你只要看Presenter的接口,就能明白这个模块都有哪些业务,很快就能定位到具体代码。Activity变得容易看懂,容易维护,以后要调整业务、删减功能也就变得简单许多。

Mosby Nucleus Beam TheMVP MVPro

vip视频MVP RxJava Retrofit示例MVP Dagger2 Retrofit Rxjava示例

本文由新葡京8455发布,转载请注明来源

关键词: