MVP 在项目中的最佳实战(封装篇)

作者: 夏至的稻穗 | 来源:发表于2017-06-26 08:40 被阅读473次

    作者 夏至,欢迎转载,但请保留这段申明
    http://blog.csdn.net/u011418943/article/details/73731023

    说到 MVP ,大家应该都不陌生了,由于其高度解耦等优点,越来越多的项目使用这个设计模式;然而,优点虽在,缺点也不少,其中一个就是类多了很多,而且 V 与 P 直接要项目通信,那么 P 就得持有 V 得实例,但如果 activity 挂掉了,如果没有对 V 进行释放,又有导致内存溢出得问题,而且,那么多的接口函数,看得人眼花缭乱,也使得很多人在使用这个模式的时候望而却步。
    如果对 MVP 不了解,可以看我以前的这两篇文章:
    MVP 设计模式理解,实战理解MVP
    MVP+多线程+断电续传 实现app在线升级库 (手把手教你打造自己的lib)

    回归原题,最近把一个项目改成 MVP 的模式,用以前的方式,写完之后,那么多接口类,除了很多重复了之外,和别人共同开发也是个问题;那么,封装一些 base 基类就显得尤为重要了。当然,一千个人中有一千个哈姆雷特,这里就提供一下我的思路,大家可以结合自己的理解参考一下。
    项目类型: 类似一个 雷豹清理大师的清理工具,先上传个简单的 效果图吧,这样会比较好理解一下:

    1、封装 BaseView

    BaseView 的封装中,我们把每个子类都会用到的方法放在这里,由于是清理工具类的项目,那么就有一个扫描的过程,还有加载失败,所以,baseview 如下:

    public interface BaseView {
        void fail(String errorMsg);
        // 扫描状态更新,根据需要更新
        void scanStatusShow(int current,int maxsize,long size,String path);
    }
    

    当然,这里的 baseview 只是我这个项目的,实际上,你还可以在上面添加 夜间模式切换,显示隐藏加载进度条等公有方法。

    2、封装 BasePresenter

    上面中,我们提到,presenter 是持有view 的实例的,假如activity崩溃,而persenter继续持有,那么势必会导致内存泄漏的问题的;所以,我们希望在activity崩溃时,也把 view 去掉;
    那么,basepresenter 时用抽象类好?还是接口类呢?比如网上很多人这样写:

    public abstract class BasePresent<T>{
        public T view;
        public void attach(T view){
            this.view = view;
        }
        public void detach(){
            this.view = null;
        }
    }
    

    这里用了一个抽象类来实现,但我们参考 google 的 mvp 源码时,它的 basepresenter 是一个接口,就一个 start() 方法,然后再用一个契约类来实现的,如下:

    /**
     * This specifies the contract between the view and the presenter.
     */
    public interface AddEditTaskContract {
        interface View extends BaseView<Presenter> {
            void showEmptyTaskError();
            void showTasksList();
            void setTitle(String title);
            void setDescription(String description);
            boolean isActive();
        }
        interface Presenter extends BasePresenter {
            void saveTask(String title, String description);
            void populateTask();
            boolean isDataMissing();
        }
    }
    

    可以看到,它把相关的 view 和persenter 都放在 contract 这个类中,这样做的好处在于,方便后期的维护,类的实现方法和接口都在这,方便查找与调试,所以,我们封装的时候,也参考这种模式。但思考一个问题,我们的 basepresenter 是用抽象类好呢?好像接口呢?

    答案是抽象,为什么?
    我们知道,接口是一个特殊的抽象类,接口里面的方法都是抽象方法,如果定义成接口函数,则里面的方法必须实现,则在 baseactivity 或者 basefragment 继承时重写了方法,而在 具体子类的 presenter 的时候也重写了方法,这样就多余了,所以,书上看到这接口和抽象类的总结,觉得还不错,如下:

    • 优先使用接口
    • 在既要定义子类的行为,又要为子类提供公共的功能时应选择抽象类

    所以,这里的 basepresenter 就用 抽象类会好一点。

    /**
     * T 泛型,指的是baseview,当然也可以是其他的view 类型
     * Created by zhengshaorui on 2017/6/22.
     */
    
    public abstract class BasePresenter<T> {
        // 获取 view 的view 实例
        T view;
         void onAttach(T view){
             this.view = view;
         };
    
        // 解绑 view 层
         void onDetch(){
             this.view = null;
         };
        // view 数据的开始,一般再 oncreate 或者 onresume 中
       // void start();
    }
    

    可能你会疑问,这样的话,我们的契约类怎么写?别急,往下看。

    3、契约类 contract

    回顾一下自己以前写的 mvp 小demo,我们是不是 v 一个接口,p一个接口,除了结构上给人类很多之外,而且还大部分是重复的,涉及到 v 层数据的更新,p在获取了v的实例之后,通过观察者模式,获取model的数据更新到v,那么这里接口就存在重复的,我们可以用基类继续封装;

    而且mvp中的p,我们说过,它就是个纽带,那么就不要添加什么逻辑性的东西在这里,保证 p 的轻薄,v 就是更新 UI 的。

    ok,首先,先看一下我的 contract 类的封装:

    public interface ContractUtils {
    
        /*=============一键减速契约类===============*/
        // 一键加速contract
        interface ISpeedUpView  extends SpeedUpBaseInterface {
            void getMemory(long availsize,String total);
        }
        //presenter 的接口类
        interface ISpeedUpPresenter extends SpeedUpBaseInterface{
            void startSpeedup();
        }
    
        /* ==============轻度清理契约类==================*/
        interface IClearView extends BaseView {
            void scanComplete(List<CacheInfo> mInfoList);
        }
    
        interface  IClearPresenter extends BaseView{
            void scanComplete(List<CacheInfo> mInfoList);
            void startclear();
        }
        .....
    }
    
    

    ok,在 contractUtils 类中,我把v与p的接口都放在这里,这样方便查阅,然后关于他们之间更新数据重复的接口,再封装一次,其中 SppedUpBaseInterface 如下,继承自baseview:

    public interface SpeedUpBaseInterface extends BaseView {
        void scanComplete(List<RunAppInfo> datas);
        void updateMemory(String msg);
    }
    

    以后在调试的时候,只要过来查看这个共有契约类就方便多了。

    4、封装baseactiviy

    这个大家在平时都有做的,我们可以在这里封装activity之间的启动动画,权限管理,还有共有方法等等,而 MVP 这个设计模式,当然也是所有的 activity 都支持的,所有,封装如下,这里只展示 mvp 部分:

    public abstract class BaseActivity<V extends BaseView<T>,T extends BasePresenter<V>> extends Activity  {
        public T mPresenter;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            // TODO:OnCreate Method has been created, run FindViewById again to generate code
            ..... 
           //这里初始化 mvp
            mPresenter = getPresenter();
        }
        @Override
        protected void onResume() {
            super.onResume();
            if (mPresenter != null) {
                mPresenter.onAttach((V) this);
            }
        }
        @Override
        protected void onDestroy() {
            super.onDestroy();
            if (mPresenter != null) {
                mPresenter.onDetch();
            }
        }
        /**
         * 不同activity的布局接口
         * @return
         */
        public abstract int getLayoutId();
        public abstract T getPresenter();
       
    }
    

    在所有的 onresume 中,将 view 实例给 presenter ,在 ondestroy 中,销毁其中的view,而 baseactivity 后面的泛型,就是根据不同的子类来的。最后用一个抽象方法,getPresenter 把子类的 presenter 拿出来,都在 oncreate 中初始化;

    5、子类中的调用

    子类中的调用,这里展示 mvp 部分,比如一键加速这里:
    View:

    public class SpeedUpActivity extends BaseActivity<ContractUtils.ISpeedUpView,SpeedUpPresenter>
            implements ContractUtils.ISpeedUpView {
        ....
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            initdata();
        }
    
        @Override
        public int getLayoutId() {
            return R.layout.activity_speedup;
        }
    
        @Override
        public SpeedUpPresenter getPresenter() {
            mSpeedUpPresenter = new SpeedUpPresenter(this,this);
            return mSpeedUpPresenter;
        }
    
        @Override
        protected void onResume() {
            super.onResume();
        }
    
        private List<RunAppInfo> mlist = new ArrayList<>();
    
        public void speedup(View view) {
        
    
            mSpeedUpPresenter.startSpeedup();
          
    
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
    
        }
    
    
    
        @Override
        public void getMemory(long availsize, String total) {
            mCircleRunView.setMemeryMsg(availsize," / "+total);
        }
    
        @Override
        public void updateMemory(String msg) {
            if (msg.equals("0")){
                Toast.makeText(this, "您的电视非常流畅", Toast.LENGTH_SHORT).show();
            }else{
                Toast.makeText(this, "共为您节省了: "+msg, Toast.LENGTH_SHORT).show();
            }
        }
    
    
    
        @Override
        public void fail(String errormsg) {
            lg.d("speedupviewerror: "+errormsg);
        }
    
        @Override
        public void scanStatusShow(int current, int maxsize, long size,String path) {
            mLoadingText.setText(getString(R.string.loading,current,maxsize));
        }
    
    
       
    
    
    }
    

    注意子类集成 baseactivity 的 view 和 presenter,还有抽象方法 getPresenter 即可,基本没啥区别

    至于 presenter 的实现,则大家修改一下自己的就行了,比如我的:

    public class SpeedUpPresenter extends BasePresenter<ContractUtils.ISpeedUpView> implements
                ContractUtils.ISpeedUpPresenter{
    
        private ISpeedUpModel mSpeedUpModel;
        private ContractUtils.ISpeedUpView mISpeedUpView;
        private Context mContext;
        public SpeedUpPresenter(ContractUtils.ISpeedUpView iSpeedUpView, Context context) {
            mContext = context;
            mISpeedUpView = iSpeedUpView;
            mSpeedUpModel = new SpeedUpModel(context,this);
            mSpeedUpModel.startScanRunInfo();
        }
        .....
     }
    

    关于model的思考

    上面,我们对 view 和 presenter 都进行了封装,那model是否也该封装呢?这个不好说,根据自己的项目来,不过一般很多封装,毕竟这个数据逻辑处理的,都是单独出来的;
    不过,model 我一般把用单例的,而且比如项目中的深度清理,model 的扫描和数据的获取是在presenter 中去初始化后再去扫描吗?

    No No No,我们说过 p 只是一个观察者,它只负责你model的数据更新后实时更新给v,那么你 model 的怎么做,我就不管了,我只要你的数据就行,想深度扫描这种耗时的,我是在 application里面初始化的;但记得的一点是,不要在application 里做过多的操作,不然会拖慢app的启动的。

    封装后与以前的区别

    • v 与 p 的接口,我们都可以用契约类来实现了,除了方便查阅调试之外,还减少了很多类
    • 封装了 v 与 p 公有的方法,减少累赘代码
    • 关于model的思考

    好,以上就是这是我对 MVP 封装的初步理解,如有错误,也欢迎大家留言指正,谢谢。

    相关文章

      网友评论

      本文标题: MVP 在项目中的最佳实战(封装篇)

      本文链接:https://www.haomeiwen.com/subject/chedcxtx.html