一个简单的MVP模式

来源:互联网 发布:mac如何剪辑音频 编辑:程序博客网 时间:2024/06/06 00:42

 MVP相信大家都不陌生,不论是程序员还是经常打游戏都会经常听到这个词,好了废话完毕,直入正题!关于安卓开发的MVP设计模式简单了解。

简介

MVP是 Model (模型) 、View(视图)、Presenter(主持人)的缩写,代表中项目中不同层次逻辑关系。

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

一部分新手看到这里可能会稍有懵逼的赶脚,在这里我先说面下这里需要构建BasePresenter和BaseViewController。

先来完成BasePresenter吧,

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

接下来 就是接口BaseViewController

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(this);    //实例化LoginActivity的Presenter    }    @Override    protected void initEvent() {        mBt_login.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                MyApplication.getInstance().outLog(TAG, "MDZZ");    //日志输出                //调用Presenter的登录的网络请求,将用户名和密码传递过去                mPresenter.doLogin(mEt_username.getText().toString(), mEt_password.getText().toString());             }        });    }    @Override    protected void initView() {        MyApplication.getInstance().addActivity(this);  //将Activity加入堆栈管理        mEt_username = (TextView) findViewById(R.id.mEt_username);        mEt_password = (TextView) 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() > 0) {                if (permissionsNeeded.size() > 0) { //待申请的权限列表                    // Need Rationale                    String message = "你必须允许本APP使用:" + permissionsNeeded.get(0);                    for (int i = 1; i < permissionsNeeded.size(); i++)                        message = message + ", " + permissionsNeeded.get(i);                    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(this)                .setMessage(message)                .setPositiveButton("允许", okListener)                .setNegativeButton("拒绝", null)                .create()                .show();    }    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(intent);                        }                    });                }            }            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中请求网络然后再通过调用接口实现数据回传。在这里还有要注意的就是分层次这里是View层次,不要将其他的逻辑放入这里 以免造成混乱。在这里主要是视图的呈现,用户可见的东西。接下来就是关于P来做事了。

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(model);    }    @Override    public void initData() {    }    public void doLogin(String name, String pwd) {        //用户名和密码不能为空        if (StringUtils.isEmpty(name) || StringUtils.isEmpty(pwd)) {            model.showDailog("用户名或密码不能为空!"); //调用model的错误提示对话框            return;        }        //密码MD5加密        pwd = CipherUtils.small32md5(pwd);        HttpParams params = new HttpParams();        params.put("userName", name);        params.put("passWord", pwd);        RxVolley.post(MyApi.LoginApi, params, new HttpCallback() {            @Override            public void onSuccess(String t) {                super.onSuccess(t);                //数据不为空再进行数据处理                try {                    if (null != t) {                        MyApplication.getInstance().outLog("输出", t);                        LoginBean bean = new GsonUtils().toBean(t, LoginBean.class);                        if (null != bean) {                            if (bean.getCode() == 0) {                                //请求成功                                model.openHome(bean);                            } else if (bean.getCode() == 1) {                                model.showDailog("登录失败,帐户不存在");                            } else if (bean.getCode() == 2) {                                model.showDailog("登录失败,密码错误");                            } else {                                model.showDailog("登录失败,其他未知错误");                            }                        }                    }                } catch (Exception e) {                    e.printStackTrace();                    model.showDailog("登录失败,其他未知错误");                }            }            @Override            public void onFailure(VolleyError error) {                super.onFailure(error);                model.showDailog("登录失败,其他未知错误");            }            @Override            public void onFinish() {                super.onFinish();                model.disProgressD();   //model的关闭对话框的接口            }            @Override            public void onPreStart() {                super.onPreStart();                model.showProgressD();  //model的进度对话框            }        });    }}

   我们上面可以看到我们现在只要把请求网络的数据传递上去就可以完成单元测试了,这样子我们就达到了我们数据流转的单元测试的标准。既然我们都看到了Presenter对model的调用,那么我们直接贴上model再对比Activity就能明白了我们是怎么完成这个设计的。当然 看到这里 ,还不能少了 一些定义的接口---LoginActivityViewController:

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的方法控制界面 -> 展示给用户.

在基类中会有<>这种括号括起来的东西,恩恩这个是泛型,主要是用来说明他们是哪一类的东西,通过泛型来解耦就可以在基类中整合更多的东西。

本文是转载掘金用户pc859107393的文章,对其稍有添加一些讲解,如有错误还希望读者指出来。原文链接http://gold.xitu.io/post/5834212b88074100579b7080















0 1
原创粉丝点击