最近在进行项目的重构,也没怎么去看flutter,至今还处于入门阶段。
就又东一榔头西一榔头的写一些关于mvp的东西了。不过总会继续写完的(只要flutter不凉,其实我甚至考虑要边看rxjava源码边开一篇文章记录学习(hhh
闲话少说进入正题。
1:关于mvp模式
目前市面上使用的模式主流的有:MVC,MVP,MVVM三种。
mcv模式是模型,视图,控制器三种不同功能的组合,你中有我我中有你。也不失为一种好的模型,毕竟剩下两种其实本质上来说也是基于mvc演变而来的。硬要说剩下两种就比mvc好也不见得。
图片来源于谷歌的mvp项目示例。单看右侧来说,这就是控制器与视图的交互,但是左边的model和视图的交互却由presenter来代理了,这就降低了耦合度。但是有一个不好的地方就是:会多出不少的代码需要你进行撰写。
MVVM呢,目前是前端方面用的比较多,其实安卓也是有使用的,经典的使用就是MVVM+Databinding框架。谷歌现在开放的库有:
lifecycles 处理UI界面的生命周期,在26版本以后的Support库中,AppCompatActivity和SupportActivity中都实现了LifecycleOwner,内部已经对UI界面的生命周期做了处理了。
而LiveData是一个抽象类,我们可以存放UI页面需要的数据,就是把数据包装在LiveData中了,我们可以观测LiveData中的数据变化,但是LiveData是跟UI的生命周期关联的,当UI页面销毁了,LiveData的数据变化回调是不会执行的。
Room 就是一个sqlite数据持久化库,我们也可以使用别的ORM库。
MVVM的响应式编程还是不错的,但是本文并不使用这个。说起来RN和flutter其实也是响应式编程。(大胆猜测,这可能是未来的主流
2.关于mvp的使用
先不看其他的示例,我们第一个实现的想法很自然:
public class MainActivity extends AppCompatActivity{
...
private Presenter mPresenter;
public void setPresenter(Presenter presenter){
mPresenter = presenter;
}
...
}
public class Presenter{
private Model mModel;
private MainActivity mView;
public Presenter(Model model,aActivity view){
mModel = model;
mView = view;
mView.setPresenter(this);
}
}
这样就行了吗?开始你会觉得很自然,但是琢磨琢磨,是不是发现这三者的耦合度太高了,相比于mvc实际上并没有降低多少。那么怎么改进呢?持有实例耦合度太高,那咱们使用接口吧。
接下来怎么做呢?咱们让Activity实现一个IView的接口,让Presenter实现IPresenter的接口。
public class MainActivity extends AppCompatActivity implements IView{
....
private IPresenter mPresenter;
public void setPresenter(IPresenter presenter){
mPresenter = presenter;
}
....
}
public class Presenter implements IPresenter{
private IModel mModel;
private IView mView;
public Presenter(IModel model,IView view){
mModel = model;
mView = view;
mView.setPresenter(this);
}
}
再把接口集中管理:
public class MainActivityContract {
public interface IPresenter{
}
public interface IModel {
}
public interface IView{
}
}
其实这里以及和谷歌的示例差不多了。但是我们这里做了model的接口注入,而谷歌官方是没有的(这个可选可不选,谷歌之所以不用是为了保证全局唯一的数据层单例,就不能通过接口强转,避免数据污染。)
那么还有没有可以优化的点呢?还是有的:实现更基础的V,P接口。
public interface BaseView<T> {
void setPresenter(T presenter);
}
public interface BasePresenter {
void start();
}
这是谷歌官方的例子,先上网站:谷歌示例
这里和谷歌不一样的是,谷歌选用了fragment作为View,而我们使用了Activity。
为什么选用fragment呢?原因有以下两点:
- 我们把 Activity 作为一个全局控制类来创建对象,把 Fragment 作为 view,这样两者就能各司其职。
- 因为 Fragment 比较灵活,能够方便的处理界面适配的问题。
而我们选用activity也没有问题,只要是作为view而不处理其他。
3.再优化
上面的代码是不是就完美了呢?其实并不是的,Presenter是持有view和model两方的,这里看起来没啥问题。但是你有没有想过,model是进行耗时操作呢?
比如说一个网络请求,正常的retrofit来说,一个call<>方法是作为model,然后再presenter中实现,再将数据更新给view。看起来似乎是没什么问题,对吧?
但是View是必须在主线程中运行的,而网络操作呢?是耗时的,为了避免ANR,必须要在异步线程中运行,那么这时候View关闭会发生什么呢?
内存泄漏。这里View被持有,是强引用。
实线为强引用,w是windowmanager。仅做个例子,这里presenter对view持有,会导致无法被回收,GC是并不会回收强引用的,即使这时候内存不够了。
那么我们的解决方法就呼之欲出了,使用软引用或者弱引用去取代强引用,为了能让activity被回收,这里我们使用哪种都行。
我个人觉得弱引用是更好的(网上大部分也是弱引用),毕竟最先被回收的就是弱引用,当强引用断掉时立马回收。而软引用仅仅只是有可能回收。
show my code.
public abstract class BasePresenter<V>{
private Reference<V> mViewReference;
/**
* 建立关联(弱引用)
* @param view 界面
*/
public void attachView(V view){
mViewReference=new WeakReference<V>(view);
}
/**
* 获取view
* @return 持有界面
*/
protected V getView(){
if (isViewAttached()){
return mViewReference.get();
}
return null;
}
/**
* 判断view是否添加
*/
public boolean isViewAttached(){
return mViewReference != null && mViewReference.get()!=null;
}
/**
* 取消关联
*/
public void detachView(){
if(mViewReference != null){
mViewReference.clear();
mViewReference = null;
}
}
/**
* 初始化
*/
public abstract void start();
}
这样就完了吗?并不,我们还需要在View的生命周期中进行调用
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = createPresenter();
if (mPresenter != null) {
mPresenter.attachView((V) this);
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.detachView();
}
}
最终,我们的结构是这样的:
Presenter:BasePresenter(基础行为,弱引用和初始化)+IPresenter(接口)
以及我们的xxxxPresenter为具体实现。
View:BaseView(生命周期调用以及创建presenter)+IView(接口) 以及具体实现类
Model:使用全局单例(防止数据污染)或者如以上两个,使用接口注入。
4.另一种实现
其实还有另一种写法,因为MVP里有大量的依赖以及注入行为,代码会显得臃肿复杂,那么这时候就可以使用框架了:Dagger2时安卓的注入框架。
我们可以使用Dagger2实现mvp模式。
但是不好意思我还没学Dagger2,哈哈哈哈。
dbq,等我学完了我再来补充这一部分。