一步步构建MVP框架
MVP模式已经是老生常谈了,很多程序员用过这个架构进行开发,但是从无到有搭建MVP架构的过程,或许没有体验过,在此我们来一步步搭建MVP环境。
先说说概念,MVP: Model View Presenter
image.pngModel:获取数据业务逻辑
View: UI展示
Presenter: 实现者 Model与View相互独立,Presenter负责将Model与View绑定,是一个中间者的角色。
如图:MVC模式中,Model与View相互持有对方的引用,视图与数据逻辑交互。
在实际开发中,业务逻辑基本都是在Activity / ViewController中实现的,导致Controller的臃肿和难以维护。
而在MVP模式中:Model与View完全被Presenter隔离,而Presenter作为一个中间者,负责将Model与View绑定。
假设我们的有一组数据模型叫StudentBean
public class StudentBean {
private String name;
private String sex;
private String ege;
private String phone;
//各种set get属性的方法...
首先我们创建出Model,获取StudentBean的数据;同时我们也希望在数据获取成功时有所反馈,于是我们在数据获取结束时,来个回调,实现如下:
/**
* Created by Shmily on 2018/1/18.
* Model接口
*/
public interface IStudentModel {
/**
* 加载数据
* @param listener
*/
void fetchData(OnLoadListener listener);
/**
* 加载完成的回调
*/
interface OnLoadListener {
void onLoadSuccess(List<StudentBean> list);
void onLoadFailed();
}
}
由代码我们可以看出Model提供了获取数据的方式,并在数据获取结束时给予回调方法。
现在我们实现这个Model接口
public class StudentModelImpl implements IStudentModel{
/**
* 获取数据,并在结束时实现回调
* @param listener
*/
@Override
public void fetchData(OnLoadListener listener) {
if (listener == null) {
return;
}
ArrayList<StudentBean> list = loadDataFromLocal();
if (list == null || list.isEmpty()){
listener.onLoadFailed();
}
else {
listener.onLoadSuccess(list);
}
}
/**
* 模拟加载本地数据
* @return
*/
private ArrayList<StudentBean> loadDataFromLocal(){
ArrayList<StudentBean> list = new ArrayList<>();
StudentBean student1 = new StudentBean();
student1.setName("jason");
student1.setSex("女");
student1.setEge("10");
student1.setPhone("010-1234567");
list.add(student1);
...
return list;
}
}
在StudentModelImp中,模拟实现了加载数据的方法,并在加载结束时,根据结果进行对应成功失败的回调。
有了数据获取,我们来实现数据展示View模块
View更简单,拿到数据就展示,没有数据,就展示其他。
public interface IStudentView {
/**
* 显示进度条
*/
void showLoading();
/**
* 展示失败结果
*/
void showFailed();
/**
* 显示获取数据的结果,即数据
* @param list
*/
void showStudentList(List<StudentBean> list);
}
在View模块中,实现加载进度条的方法和展示数据。
View模块与Model模块都有了,就差Presenter了,看代码之前,我们来分析下Presenter:
Presenter虽然隔离了Model与View,但是也将二者关联起来,也就是说要将数据获取与数据展示关联起来,所以,聪明的你,应该想到了吧?
Presenter持有Model与View的引用,通过对象组合,借助Model获取数据,借助View展示数据,利用回调的方式,将这个过程串起来。
public class StudentPresenter {
private IStudentView mStudentView;
private IStudentModel mModel = new StudentModelImpl();
public StudentPresenter(IStudentView studentView) {
this.mStudentView = studentView;
}
public void fetch(){
//借助view show loading
mStudentView.showLoading();
// 借助model 获取数据
mModel.fetchData(new IStudentModel.OnLoadListener() {
@Override
public void onLoadSuccess(List<StudentBean> list) {
// model获取数据成功后,由view来展示
mStudentView.showStudentList(list);
}
@Override
public void onLoadFailed() {
mStudentView.showFailed();
}
});
}
}
还差最后一步,View的实现,与MVC一样,我们用Activity作为View,实现IStudentView接口,
并借助Presenter调用数据获取的方法。
Presenter本身不能获取数据,但是它持有View与Model的引用,通过Model获取数据,再通过回调的方式,让View展示数据
private void fetchData(){
// 调用presenter加载数据
new StudentPresenter(this).fetch();
}
/**
* 展示进度条
*/
@Override
public void showLoading() {
Toast.makeText(this,getString(R.string.loading),Toast.LENGTH_SHORT).show();
}
/**
* 展示数据
*/
@Override
public void showStudentList(List<StudentBean> list) {
myAdapter = new MyAdapter(this,list);
listview.setAdapter(myAdapter);
}
/**
* 展示加载失败
*/
@Override
public void showFailed() {
Toast.makeText(this,getString(R.string.loadfailed),Toast.LENGTH_SHORT).show();
}
在Activity中,完全看不到Model,但却借助Presenter调用Model间接获取了数据,再通过View本身展示。
Model与View二者完全隔离,却被Presenter利用对象组合回调的方式相关联。
代码的耦合度降低,在业务需求发生变化时,eg:需要从网络获取数据。 基于目前的代码,我们不需要修改什么,只要再实现一个StudentImlInternet即可。可见实现了开闭原则Open Closed Principle
忘了什么是开闭原则?贴下概念:
定义: Softeware entities like classes,modules and functions should be open for extension but closed for modifications。
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
即通过扩展来实现变化,而不是通过修改已有代码来实现变化。
以上就是MVP模式。有没有一种so easy! 的感觉。那么上面的写法会不会有问题呢?
某一个Activity(View)在内存不足时,已经销毁, 但是Model获取数据(子线程耗时操作)
那么Presenter还持有View的引用。但Model完成后,Presenter去通知View更新,则出现内存泄漏(生命周期不#####同步,上下文丢失等问题)
image.png举例:开启一个DownActivity 点击页面下载功能 那么model就开启子线程获取数据
然后点击DownActivity的某按钮去其他页面 AnimationActivity 播放动画或视频,DownActivity是如果此时内存不足>DownActivity被销毁,那么当model完成数据获取,由于P还持有V的引用,则Presenter就会通过DownActivity去更>新UI,此时会发生内存泄漏。
解决方案:View被销毁时,解除与Presenter的关联。
So 我们需要两个方法:attachView(view); detachView(); (像Fragment的生命周期的用法)
在View创建onCreate与销毁onDestory的时候调用。
但是如果每个实现View的Activity都在onCreate与onDestory的时候调用绑定与解绑的方法,这未免太傻了吧。于是你想到了在BaseActivity中实现,于是我们要把代码优化为扩展的方式: 配合泛型提取父类
/**
* Created by Shmily on 2017/6/2.
*/
public abstract class BaseActivity<V,T extends BasePresenter<V>> extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 创建Presenter 将在view中创建presenter提取到Base类
mPresenter = createPresenter();
// p与v的关联
mPresenter.attachView((V)this);
}
public T mPresenter;
/**
* create presenter
* @return 模版方法,具体实现到子类
*/
public abstract T createPresenter();
@Override
protected void onDestroy() {
super.onDestroy();
// p与v解除关联 解决内存泄漏的问题
mPresenter.detachView();
}
}
解释下泛型这里:
由于业务未来的扩展,Presenter实现可能有多种类型,可以提取到父类BaseActivity中创建:采用模版
方法public T mPresenter; public abstract T createPresenter();
而T要在BaseActivity中声明,So BaseActivity<T>
Presenter的父类 BasePresenter(持有View的引用)
使用泛型V 在BasePresenter声明 BasePresenter<V>
public abstract class BasePresenter<T>
而BaseActivity持有BasePresenter的子类的引用(要在这里创建) So BaseActivity中的T是继承
BasePresenter的一个子类。那么 abstract class BaseActivity<V,T extends BasePresenter<V>>
基于上文内存泄漏的可能性,我们对Presenter的扩展时,让presenter对view的持有为弱引用
public abstract class BasePresenter<T> {
//View接口类型弱引用
public WeakReference<T> mViewRefer;
/**
* bind view with presenter
* @param view
*/
protected void attachView(T view) {
//建立关联
mViewRefer = new WeakReference<T>(view);
}
//可以通过此方法,判断是否与View建立了关联
protected boolean isViewAttached() {
return mViewRefer != null && mViewRefer.get() != null;
}
/**
* unbind view with presenter
*/
protected void detachView(){
if (mViewRefer != null) {
mViewRefer.clear();
mViewRefer = null;
}
}
/**
* base 类提供view的引用
* @return
*/
public T getView(){
return mViewRefer.get();
}
}
对于弱引用不了解的童鞋,一定要自己学习下。
最后在Activity调用
private void fetchData(){
mPresenter.requestData();
}
/**
* 子类实现父类的方法
* @return
*/
@Override
public StudentPresenterIml createPresenter() {
return new StudentPresenterIml();
}
在子类中创建自己想要的presenter对象,通过引用父类的mPresenter直接获取数据。
MVP是安卓研发编码时常用的代码框架,本篇先是以最精简的方式,搭建MVP框架。了解了它的核心与本质后,再用扩展的方式,再将Presenter的实现提到抽象层,具体实现延迟到子类,并解决可能存在的内存泄漏的问题。
代码中有充分的注释,在此献上代码
[精简版] https://github.com/Shmily701/SimpleMVP
[扩展版] https://github.com/Shmily701/ExtendedMVP
喜欢学习,乐于分享,不麻烦的话,给个❤鼓励下吧!