MVP 在项目中的最佳实战(封装篇)
说到MVP,大家应该都不陌生了,由于其高度解等等优点,越来越多的项目使用这个设计模式;然而,优点虽在,缺点也不少,其中一个就是类多了很多,而且V与P直接要项目通信,那么P就得持有V得实例,但如果活动挂掉了,如果没有对V进行释放,还有导致内存溢出得问题,而且,那么多的接口函数,看得到人眼花缭乱,也使得很多人在使用这个模式的时候望而尚步。
如果对MVP不了解,可以看我以前的这两篇文章:
MVP设计模式理解,实战理解MVP
MVP +多线程+断电续传实现应用在线升级库(手把手教你打造自己的lib)
回归原题,最近把一个项目改成MVP的模式,用以前的方式,写完之后,那么多接口类,除了很多重复了之外,和别人共同开发也是个问题;那么,封装一些基础
类似显显尤为重要了。项目类型:类似一个雷豹清理大师的清理工具,先上传个简单的效果图吧,这样会比较好理解一下:
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是持有视图的实例的,假如活动崩溃,而persent继续持有,那么势必会导致内存泄漏的问题的;所以,我们希望在活动崩溃时,也把视图去掉;
那么,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();
}
}
可以看到,它把相关的观点和persent都放在合同这个类中,这样做的好处在于,方便后期的维护,类的实现方法和接口都在这里,方便查找与调试,所以,我们封装的时代,也参考这种模式。但思考一个问题,我们的basepresenter是用抽象类好呢?好像接口呢?
答案是抽象,为什么?因为如果定义成接口函数,则里面的方法必须实现,则在baseactivity或者basefragment继承时重写了方法,而在具体子类的演示者的时候也重写了方法,这样就多余了,而且在抽象函数中,除了定了方法之外,还可以相应地做些修改所以,最后的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,契约类合同
回顾一下自己以前写的mvp小演示,我们是不是v一个接口,p一个接口,除了结构上给人类很多之外,而且还大部分是重复的,涉及到v层数据的更新,p在获取了v的实例之后,通过观察者模式,获取模型的数据更新到v,那么这里接口就存在重复的,我们可以用基类继续封装;
而且mvp中的p,我们说过,它就是个纽带,那么就不要添加什么逻辑性的东西在这里,保证p的轻薄,v就是更新UI的。
好的,首先,先看一下我的契约类的封装:
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();
}
.....
}
好的,在contractUtils类中,我把v与p的接口都放在这里,这样方便查阅,然后关于他们之间更新数据重复的接口,再封装一次,其中SppedUpBaseInterface如下,继承自baseview:
public interface SpeedUpBaseInterface extends BaseView {
void scanComplete(List<RunAppInfo> datas);
void updateMemory(String msg);
}
以后在调试的时候,只要过来查看这个共有契约类就方便多了。
4,封装baseactiviy
这个大家在平时都有做的,我们可以在这里封装活动之间的启动动画,权限管理,还有共有方法等等,而MVP这个设计模式,当然也是所有的活动都支持的,所有,封装如下,这里只展示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中,将查看实例给Presenter,在ondestroy中,销毁其中的视图,而baseactivity后面的泛型,就是根据不同的子类来的最后用一个抽象方法,getPresenter把子类的主持人拿出来,都在oncreate中初始化
5,子类中的调用
子类中的调用,这里展示mvp部分,比如一键加速这里:
查看:
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的观点和Presenter,还有抽象方法getPresenter即可,基本没啥区别
至于演示者的实现,则大家修改一下自己的就行了,比如我的
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();
}
.....
}
关于模式的思考
上面,我们对视图和演示者都进行了封装,那个模型是否也是封装呢?这个不好说,根据自己的项目来,不过一般很多封装,毕竟这个数据逻辑处理的,都是
单独出来的; 不过,模型我一般把用例,而且比如项目中的深度清理,模型的扫描和数据的获取是在演示者中去初始化后再去扫描吗?
没有不,我们说过只是一个观察者,它只负责你模型的数据更新后实时更新给v,那么你模型的怎么做,我就不管了,我只要你的数据就行,想深度扫描这种耗时的,我是在应用程序里面初始化的;但记得的一点是,不要在应用程序里做过多的操作,不然会拖慢应用程序的启动的。
封装后与以前的区别
- v与p的接口,我们都可以用契约类来实现了,除了方便查阅调试之外,还减少了很多类
- 封装了v与p公有的方法,减少累重代码
- 关于模式的思考
好,以上就是这是我对MVP封装的初步理解,如有错误,也欢迎大家留言指正,谢谢。