Android设计模式之MVP
引言
在Android的架构中Activity,fragment,布局的xml相当于View。然而在实际的开发过程中,Android的View层任务繁重,将V和C都糅杂在Activity、Fragment中,这就导致了在实际开发中View层太过累赘。MVP把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model类还是原来的Model
mvc1.png
mvp1.png
MPV的介绍
- 优点
- 分离了视图逻辑和业务逻辑,降低了耦合
- Activity只处理生命周期的任务,代码变得更加简洁
使用MVP之后,Activity就能瘦身许多了,基本上只有FindView、SetListener以及Init的代码。其他的就是对Presenter的调用,还有对View接口的实现。这种情形下阅读代码就容易多了,而且你只要看Presenter的接口,就能明白这个模块都有哪些业务,很快就能定位到具体代码。Activity变得容易看懂,容易维护,以后要调整业务、删减功能也就变得简单许多
- 视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中去,提高代码的可阅读性
- Presenter被抽象成接口,可以有多种具体的实现,所以方便进行单元测试
MVP中,由于业务逻辑都在Presenter里,我们完全可以写一个PresenterTest的实现类继承Presenter的接口,现在只要在Activity里把Presenter的创建换成PresenterTest,就能进行单元测试了,测试完再换回来即可
- 把业务逻辑抽到Presenter中去,避免后台线程引用着Activity导致Activity的资源无法被系统回收从而引起内存泄露和OOM
Android APP 发生OOM的最大原因就是出现内存泄露造成APP的内存不够用,而造成内存泄露的两大原因之一就是Activity泄露(Activity Leak)(另一个原因是Bitmap泄露(Bitmap Leak))Activity是有生命周期的,用户随时可能切换Activity,当APP的内存不够用的时候,系统会回收处于后台的Activity的资源以避免OOM。采用传统的MV模式,一大堆异步任务和对UI的操作都放在Activity里面,比如你可能从网络下载一张图片,在下载成功的回调里把图片加载到 Activity 的 ImageView 里面,所以异步任务保留着对Activity的引用。这样一来,即使Activity已经被切换到后台(onDestroy已经执行),这些异步任务仍然保留着对Activity实例的引用,所以系统就无法回收这个Activity实例了,结果就是Activity Leak。Android的组件中,Activity对象往往是在堆(Java Heap)里占最多内存的,所以系统会优先回收Activity对象,如果有Activity Leak,APP很容易因为内存不够而OOM。采用MVP模式,只要在当前的Activity的onDestroy里,分离异步任务对Activity的引用,就能避免 Activity Leak
- 缺点
相对而言,MVP模式的代码量就多了,类文件也多了,简单的一个业务逻辑操作就要各方来配合协作(即是需要presenter 和 view的接口)。但是这个问题完全在可以接收的范围。完全符合Java的抽象封装设计原则(接口隔离,开闭原则)
通过实例介绍MPV的使用
mvp展示.gif-
一个简单的订单查询 点击查询显示 点击清除取消显示
项目结构.png - 首先来看看MainActivity
package com.rrcc.mvp.mvpdemo.view;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import com.rrcc.mvp.mvpdemo.R;
import com.rrcc.mvp.mvpdemo.adapter.DailyPagerAdapter;
import com.rrcc.mvp.mvpdemo.model.OrderListBean;
import com.rrcc.mvp.mvpdemo.model.OrdersModelImpl;
import com.rrcc.mvp.mvpdemo.presenter.IPresenterImpl;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity implements ISearchView, View.OnClickListener{
private RecyclerView mRecyclerView;
private DailyPagerAdapter mDailyPagerAdapter = null;
private List<OrderListBean.DataBean> mList = new ArrayList<OrderListBean.DataBean>();
private Button mSearchBtn = null;
private Button mCancelBtn = null;
private IPresenterImpl mIPresenterImpl = null;
private OrdersModelImpl mOrdersModelImpl = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mOrdersModelImpl = new OrdersModelImpl();
mIPresenterImpl = new IPresenterImpl(mOrdersModelImpl,this);
initView();
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_search:
mIPresenterImpl.doSearch();
break;
case R.id.btn_search_cancel:
mIPresenterImpl.cancelSearch();
break;
}
}
@Override
public void showResult(List<OrderListBean.DataBean> datas) {
if(null!=datas)
setView(datas);
}
@Override
public void cancelShow(List<OrderListBean.DataBean> datas) {
if(null!=datas)
setView(datas);
}
private void initView(){
mRecyclerView = findViewById(R.id.pager_shopping_recycler);
mSearchBtn = findViewById(R.id.btn_search);
mCancelBtn = findViewById(R.id.btn_search_cancel);
mSearchBtn.setOnClickListener(this);
mCancelBtn.setOnClickListener(this);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(layoutManager);
}
private void setView(List<OrderListBean.DataBean> mList){
mDailyPagerAdapter = new DailyPagerAdapter(this,mList);
mRecyclerView.setAdapter(mDailyPagerAdapter);
mDailyPagerAdapter.notifyDataSetChanged();
}
@Override
protected void onDestroy() {
super.onDestroy();
mIPresenterImpl = null;
}
}
- 再来看看IPresenter
public interface IPresenter {
void doSearch();
void cancelSearch();
}
package com.rrcc.mvp.mvpdemo.presenter;
import com.rrcc.mvp.mvpdemo.model.OrderListBean;
import com.rrcc.mvp.mvpdemo.model.OrdersModelImpl;
import com.rrcc.mvp.mvpdemo.view.ISearchView;
import java.util.ArrayList;
import java.util.List;
public class IPresenterImpl implements IPresenter {
private OrdersModelImpl mOrdersModelImpl = null;
private ISearchView mSearchView = null;
private List<OrderListBean.DataBean> mList = null;
public IPresenterImpl(final OrdersModelImpl ordersModelImpl,final ISearchView searchView) {
this.mOrdersModelImpl = ordersModelImpl;
this.mSearchView = searchView;
mList = new ArrayList<OrderListBean.DataBean>();
}
@Override
public void doSearch() {
mList = mOrdersModelImpl.getOrderList();
mSearchView.showResult(mList);
}
@Override
public void cancelSearch() {
if(mList!=null)
mList.clear();
mSearchView.cancelShow(mList);
}
}
从代码可以看出,IPresenterImpl保留了ISearchView的引用,因此在IPresenterImpl里就可以直接进行UI操作了,而不用在Activity里完成。这里使用了ISearchView引用,而不是直接使用Activity,这样一来,如果在别的Activity里也需要用到相同的业务逻辑,就可以直接复用IPresenterImpl类了。
- 再看看ISearchView 其实现类为 MainActivity
package com.rrcc.mvp.mvpdemo.view;
import com.rrcc.mvp.mvpdemo.model.OrderListBean;
import java.util.List;
public interface ISearchView {
void showResult(List<OrderListBean.DataBean> data);
void cancelShow(List<OrderListBean.DataBean> data);
}