一般我们做项目都会选择使用MVP进行解耦,关于MVP大家早都耳熟能详了,由于之前接手的项目中使用的MVP模式写法相当随意,同时也存在很多问题,所以下面我们就一步步去实现自己的MVP,顺便分析一下可能会导致出问题的一些写法。当然了,100个人就有一百种MVP写法,你开心就好….
先预留一张gif图(录制gif的工具找不到了,回头补上)
场景
请求干货集中营的妹纸图片,并将数据显示在RecyclerView上,在这里很感谢干货集中营开放的接口,接口地址:http://gank.io/api
1.一般的写法
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private ImagesAdapter imagesAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化控件
iniViews();
//加载数据
getDatas();
}
private void iniViews() {
recyclerView = findViewById(R.id.main_recyclerView);
imagesAdapter = new ImagesAdapter(this, null);
recyclerView.setLayoutManager(new GridLayoutManager(this, 2));
recyclerView.setAdapter(imagesAdapter);
}
private void getDatas() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://gank.io/api/data/")
.addConverterFactory(GsonConverterFactory.create())
.build();
NetApiService service = retrofit.create(NetApiService.class);
Call<ImagesBean> call = service.getImages("福利", "10", "1");
call.enqueue(new Callback<ImagesBean>() {
@Override
public void onResponse(Call<ImagesBean> call, Response<ImagesBean> response) {
ImagesBean bean = response.body();
List<ImagesBean.ResultsBean> imagesLists = bean.getResults();
if(null != imagesLists && imagesLists.size() > 0){
imagesAdapter.setDatas(imagesLists);
}
}
@Override
public void onFailure(Call<ImagesBean> call, Throwable t) {
}
});
}
}
可以看到我们把网络请求以及更新View控件都写在了一起,如果业务很复杂的话,那么一个MainActivity就会出现成百上千行代码,将会把你看得眼花缭乱。那么我们就开始使用MVP模式来进行优化。
2.MVP初级使用
2.1 View
public interface MvpView<T> {
void showProgress();
void hideProgress();
void loadSuccess(T t);
void loadFailure(String msg);
}
2.2 Model
public class MvpModel {
public void request(String category, String count, String pageSize, Callback<ImagesBean> callback){
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://gank.io/api/data/")
.addConverterFactory(GsonConverterFactory.create())
.build();
NetApiService service = retrofit.create(NetApiService.class);
Call<ImagesBean> call = service.getImages(category, count, pageSize);
//发起异步请求
call.enqueue(callback);
}
}
2.3 Presenter
public class MvpPresenter {
private MvpView mvpView;
private MvpModel mvpModel;
public MvpPresenter(MvpView mvpView){
this.mvpView = mvpView;
mvpModel = new MvpModel();
}
public void requstNetDatas(String category, String count, String pageSize){
//显示加载进度
mvpView.showProgress();
mvpModel.request(category, count, pageSize, new Callback<ImagesBean>() {
@Override
public void onResponse(Call<ImagesBean> call, Response<ImagesBean> response) {
mvpView.loadSuccess(response.body());
mvpView.hideProgress();
}
@Override
public void onFailure(Call<ImagesBean> call, Throwable t) {
mvpView.loadFailure("数据加载失败!");
mvpView.hideProgress();
}
});
}
}
2.4 Activity中如何调用
public class MainActivity extends AppCompatActivity implements MvpView<ImagesBean>{
private ProgressDialog progressDialog;
private RecyclerView recyclerView;
private ImagesAdapter imagesAdapter;
private MvpPresenter mvpPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化控件
iniViews();
//初始化Presenter
mvpPresenter = new MvpPresenter(this);
//加载数据
getDatas();
}
private void getDatas() {
mvpPresenter.requstNetDatas("福利", "10", "1");
}
private void iniViews() {
recyclerView = findViewById(R.id.main_recyclerView);
imagesAdapter = new ImagesAdapter(this, null);
recyclerView.setLayoutManager(new GridLayoutManager(this, 2));
recyclerView.setAdapter(imagesAdapter);
progressDialog = new ProgressDialog(this);
progressDialog.setCancelable(false);
progressDialog.setMessage("正在拼命加载中...");
}
@Override
public void showProgress() {
if(!progressDialog.isShowing()){
progressDialog.show();
}
}
@Override
public void hideProgress() {
if(progressDialog.isShowing()){
progressDialog.dismiss();
}
}
@Override
public void loadSuccess(ImagesBean imagesBean) {
imagesAdapter.setDatas(imagesBean.getResults());
}
@Override
public void loadFailure(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
这样我们就实现了一个初级版本的MVP模式了,代码感觉清爽多了,Model只负责加载数据,Presenter作为View与Model之间交互的桥梁,Presenter将Model中请求到的结果告诉View,Activity通过实现View接口中的方法去更新数据等等。为什么说这只是一个初级版本呢,试想一下,假设我们在请求网络的过程中由于内存不足,当前的Activity被回收,但是我们的Presenter还持有V层的引用(即对Activity的引用),导致Activity无法被回收,这样就造成了内存泄漏。
3.MVP进一步优化
上面说到了当页面关闭了,但Presenter还持有View的引用,导致内存泄漏。解决办法很简单,我们可以进一步优化我们的Presenter
3.1 Presenter
public class MvpPresenter {
private MvpView mvpView;
private MvpModel mvpModel;
public MvpPresenter(){
mvpModel = new MvpModel();
}
/**
* 绑定View
* @param mvpView
*/
public void attachView(MvpView mvpView){
this.mvpView = mvpView;
}
/**
* 解绑View
*/
public void detachView(){
mvpView = null;
}
/**
* 使用前先检查当前View是否可用
* @return
*/
public boolean checkViewAvailable(){
return mvpView != null;
}
public void requstNetDatas(String category, String count, String pageSize){
if(checkViewAvailable()){
//显示加载进度
mvpView.showProgress();
mvpModel.request(category, count, pageSize, new Callback<ImagesBean>() {
@Override
public void onResponse(Call<ImagesBean> call, Response<ImagesBean> response) {
if (checkViewAvailable()) {
mvpView.loadSuccess(response.body());
mvpView.hideProgress();
}
}
@Override
public void onFailure(Call<ImagesBean> call, Throwable t) {
if (checkViewAvailable()) {
mvpView.loadFailure("数据加载失败!");
mvpView.hideProgress();
}
}
});
}
}
}
主要修改了Presenter的构造器以及添加了绑定View、解绑View/检查当前View是否还可用等几个方法
3.2 Activity
public class MainActivity extends AppCompatActivity implements MvpView<ImagesBean>{
private ProgressDialog progressDialog;
private RecyclerView recyclerView;
private ImagesAdapter imagesAdapter;
private MvpPresenter mvpPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化控件
iniViews();
//初始化Presenter
mvpPresenter = new MvpPresenter();
//绑定View
mvpPresenter.attachView(this);
//加载数据
getDatas();
}
@Override
protected void onDestroy() {
super.onDestroy();
//页面销毁的时候解绑View
mvpPresenter.detachView();
}
private void getDatas() {
mvpPresenter.requstNetDatas("福利", "10", "1");
}
private void iniViews() {
recyclerView = findViewById(R.id.main_recyclerView);
imagesAdapter = new ImagesAdapter(this, null);
recyclerView.setLayoutManager(new GridLayoutManager(this, 2));
recyclerView.setAdapter(imagesAdapter);
progressDialog = new ProgressDialog(this);
progressDialog.setCancelable(false);
progressDialog.setMessage("正在拼命加载中...");
}
@Override
public void showProgress() {
if(!progressDialog.isShowing()){
progressDialog.show();
}
}
@Override
public void hideProgress() {
if(progressDialog.isShowing()){
progressDialog.dismiss();
}
}
@Override
public void loadSuccess(ImagesBean imagesBean) {
imagesAdapter.setDatas(imagesBean.getResults());
}
@Override
public void loadFailure(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
代码中写了详细的注释,很简单大家都能看懂,这样的话就解决了内存泄漏的问题了,谷歌官方MVP示例demo中是用Fragment来实现View接口,因为Fragment是有生命周期的,所以可以借助Fragment的生命周期,在Activity层关联一个Fragment,在创建Presenter实例的时候将这个Fragment传过去
4.MVP继续优化
上面的方式虽然解决了内存泄漏的问题,但是依旧不够优雅,如果每一个Activity都对应一个Model和Presenter的话,试想一下,如果有几百个Activity的话那么是不是也要创建很多个Model和Presenter,关键是很多地方都是重复的可以通用的。所以,创建基类就显得很有必要了
4.1 View的基类
这里我们只是简单的定义几个通用的接口方法,比如显示加载进度框、关闭加载框等等
BaseMvpView
public interface BaseMvpView {
void showProgressDialog();
void hideProgressDialog();
}
4.2 Presenter的基类
BaseMvpPresenter
public abstract class BaseMvpPresenter<V extends BaseMvpView> {
private V mvpView;
/**
* 绑定View
* @param mvpView
*/
public void attachView(V mvpView){
this.mvpView = mvpView;
}
/**
* 解绑View
*/
public void detachView(){
mvpView = null;
}
/**
* 获取View
* @return
*/
public V getMvpView(){
return mvpView;
}
}
4.3 Activity的基类
BaseActivity
public abstract class BaseActivity<V extends BaseMvpView, P extends BaseMvpPresenter<V>> extends AppCompatActivity {
private P presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutId());
//实例化Presenter
presenter = createPresenter();
//绑定
if (presenter != null){
presenter.attachView((V) this);
}
//初始化控件
iniViews();
//获取数据
getRequestDatas();
}
@Override
protected void onDestroy() {
super.onDestroy();
//解绑
if (presenter != null){
presenter.detachView();
}
}
protected abstract int getLayoutId();
protected abstract P createPresenter();
protected abstract void iniViews();
protected void getRequestDatas(){}
protected P getPresenter(){
return presenter;
}
}
基类有了,那么针对上面获取妹纸图片数据场景,我们就来写具体的实现类。
MvpPresenterImpl
public class MvpPresenterImpl extends BaseMvpPresenter<MvpViewImpl<ImagesBean>> {
private MvpModel mvpModel;
public MvpPresenterImpl(){
this.mvpModel = new MvpModel();
}
public void requestNetDatas(String category, String count, String pagesize){
if (null != getMvpView()) {
getMvpView().showProgressDialog();
mvpModel.request(category, count, pagesize, new Callback<ImagesBean>() {
@Override
public void onResponse(Call<ImagesBean> call, Response<ImagesBean> response) {
if (null != getMvpView()) {
getMvpView().loadSuccess(response.body());
getMvpView().hideProgressDialog();
}
}
@Override
public void onFailure(Call<ImagesBean> call, Throwable t) {
if (null != getMvpView()) {
getMvpView().loadFailure("请求失败!");
getMvpView().hideProgressDialog();
}
}
});
}
}
}
然后就是根据具体的业务场景定义相应的接口了
MvpViewImpl
public interface MvpViewImpl<T> extends BaseMvpView {
/**
* 请求成功
* @param data 接口返回的数据
*/
void loadSuccess(T data);
/**
* 请求失败
* @param msg 失败原因
*/
void loadFailure(String msg);
}
最后,我们再来看看Activity类
public class MainActivity extends BaseActivity<MvpViewImpl<ImagesBean>, MvpPresenterImpl> implements MvpViewImpl<ImagesBean>{
private ProgressDialog progressDialog;
private RecyclerView recyclerView;
private ImagesAdapter imagesAdapter;
@Override
protected int getLayoutId() {
return R.layout.activity_main;
}
@Override
protected void iniViews() {
recyclerView = findViewById(R.id.main_recyclerView);
imagesAdapter = new ImagesAdapter(this, null);
recyclerView.setLayoutManager(new GridLayoutManager(this, 2));
recyclerView.setAdapter(imagesAdapter);
progressDialog = new ProgressDialog(this);
progressDialog.setCancelable(false);
progressDialog.setMessage("正在拼命加载中...");
}
@Override
protected void getRequestDatas() {
//加载数据
getDatas();
}
@Override
protected MvpPresenterImpl createPresenter() {
return new MvpPresenterImpl();
}
private void getDatas() {
getPresenter().requestNetDatas("福利", "10", "1");
}
@Override
public void showProgressDialog() {
if(!progressDialog.isShowing()){
progressDialog.show();
}
}
@Override
public void hideProgressDialog() {
if(progressDialog.isShowing()){
progressDialog.dismiss();
}
}
@Override
public void loadSuccess(ImagesBean data) {
imagesAdapter.setDatas(data.getResults());
}
@Override
public void loadFailure(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
当然了,这里只是加载图片,业务比较单一,在activity中直接实现了MvpViewImpl接口,直接明确了泛型类型为ImagesBean实体,业务比较复杂的时候,可能一个页面需要不同的接口得到不同的数据类型,其实我们可以直接这样
public interface MvpViewImpl extends BaseMvpView {
//获取到广告位数据
void loadBannerSuccess(BannerBean bean);
//获取到推荐数据
void loadRecommendSuccess(RecommendBean recommendBean);
}
到此为止简单的封装就完成了,本示例业务比较简单,对于真正项目的话可以视情况扩展。这段时间无意间看到了一个封装的非常不错的一个库,打算利用业余时间好好研究下这个库。下面提供下这个库的地址,大家有兴趣的话也可以去研究研究https://github.com/JessYanCoding/MVPArms