通过一个小的登录功能模块案例,帮助大家了解MVP。最终实现一个结合Rxjava2,Retrofit 的MVP通用框架。代码放到github上。(如有错误之处,请在评论区指出,谢谢。如果感觉写的不错,请点赞,关注,谢谢。)
Android MVP-编程思想1(什么是MVC-MVP-MVVM模式?)
Android MVP-编程思想2(代码实现初级版)
Android MVP-编程思想3(内存泄露问题处理,基类封装,有没有必要再使用软引用?)
Android MVP-编程思想4(AOP思想-动态代理运用,反射创建M层实例对象)
Android MVP-编程思想5(如何处理多个P层的问题?)
Android MVP-编程思想6(依赖注入多个P层优化—注解,反射)
Android MVP-编程思想7(为什么使用代理类抽取通用方法而不是工具类?,基类BaseMvpFragment)
未完待续--------
Android MVP-编程思想8(集成Rxjava2,Retrofit)
本篇是接着上一篇写的。但是并不需要按照顺序阅读,对MVP已经有一定理解和实践的可以直接阅读。如果没有接触过MVP请先阅读第一小节
Activity又持有P层的引用,P持有V层(Activity)的强引用,循环引用了(所以即使在onDestory中设置mPresenter=null 也不会被回收,因为P还持有了V的引用)。当用户点击返回按钮,关闭Activity时,由于Activity 被强引用了,所以就不会去回收它,导致内存泄露。当你再次打开 Activity 又关闭时,Activity 又申请了一段新的内存空间,GC 又没去回收它,久而久之,势必会内存溢出。
这里我们就在Activity需要回收的时候,断开循环强引用不就行了。看代码如下,注入view的方法attachView,释放view的方法detachView。然后再Activity的onDestory() 方法中调用P层的detachView,释放mView,来断开循环引用链,在释放mPresenter=null。继续看就知道了
每个P都有V,M的引用,可以抽取到父类,由于没有实现类的V,M不同,所以使用泛型。P持有的V的引用使用attachView 注入,P持有M引用,通过抽象方法createModel注入。detachView解决循环引用,内存无法释放问题。
public abstract class BasePresenter<V, M> {
protected V mView;
protected M mModel;
/**
* 注入view
*
* @param view
*/
protected void attachView(final V view) {
mView = view;
mModel = createModel();
}
/**
* 释放View
*/
protected void detachView() {
mView = null;
mModel = null;
}
protected abstract M createModel();
}
有一些人说没必要了,觉得我们已经使用detachView 的方式处理了内存泄露问题了。其实你有没有考虑过某些情况下Activity的onDestroy方法不会执行?所以这才是使用软引用的原因。
public abstract class BasePresenter<V, M> {
private SoftReference<V> mViewReference;
protected V mView;
protected M mModel;
/**
* 注入view
*
* @param view
*/
protected void attachView(final V view) {
mViewReference = new SoftReference<>(view);
mView = mViewReference.get();
mModel = createModel();
}
/**
* 释放View
*/
protected void detachView() {
mViewReference.clear();
mViewReference = null;
mView = null;
mModel = null;
}
protected abstract M createModel();
}
为什么叫BaseMvpActivity? 为了和BaseMvcActivity区别,BaseMvcActivity的抽取后面几节会贴上。大家也看到了Mvp有个缺点就是需要写很多接口。所以一些简单的功能模块我们还是使用Mvc模式开发更快。在一个应用中根据模块的复杂度同时使用Mvc和Mvp模式才是正解。
每个V层都持有P的引用,(多个P层的情况到后面讲,我们一步一步来),抽取到基类,P的实现类不同,使用泛型。提供createPresenter()抽象方法注入P。解决内存泄露:onDestroy方法先调用 mPresenter.detachView();释放V,然后mPresenter=null释放P。
public abstract class BaseMvpActivity<P extends BasePresenter> extends AppCompatActivity {
protected P mPresenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View rootView;
final Object layout = getLayout();
if (layout instanceof Integer) {
rootView = View.inflate(this, (int) getLayout(), null);
} else if (layout instanceof View) {
rootView = (View) getLayout();
} else {
throw new ClassCastException("type of setLayout() must be int or View!");
}
setContentView(rootView);
mPresenter = createPresenter();
mPresenter.attachView(this);
afterCreate();
}
@Override
protected void onDestroy() {
super.onDestroy();
mPresenter.detachView();
mPresenter = null;
}
@SuppressWarnings("ConstantConditions")
protected <T extends View> T $(@IdRes int viewId) {
return findViewById(viewId);
}
public void showProgress(boolean isShow) {
if (isShow) {
Log.i("mvp_", "显示等待框");
} else {
Log.i("mvp_", "隐藏等待框");
}
}
public void showToast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
}
protected abstract Object getLayout();
protected abstract P createPresenter();
protected abstract void afterCreate();
}
每个V层都有下面两个行为,所以这里做一个基类
public interface IBaseView {
//请求网络显示等待框
void showProgress(boolean isShow);
//显示提示框-登录失败,或其他提示信息
void showToast(String string);
}
由于抽取类IBaseView,这里变化了LoginContract类,不多解释,懂得都懂
interface IView extends IBaseView {
//登录成功跳转到主界面
void goToMainActivity();
}
由于抽取了BasePresenter,所以LoginPresenter变化如下
public class LoginPresenter extends BasePresenter<LoginContract.IView, LoginContract.IModel> implements LoginContract.IPresenter {
@Override
protected LoginContract.IModel createModel() {
return new LoginModel();
}
@Override
public void onClickLogin(String username, String pwd) {
mView.showProgress(true);
mModel.getUserInfo(username, pwd, new ICallBack<String>() {
@Override
public void success(String data) {
if (mView != null) {
mView.showToast("登录成功");
mView.showProgress(false);
mView.goToMainActivity();
}
}
@Override
public void error(String error) {
if (mView != null) {
mView.showProgress(false);
mView.showToast("登录失败");
}
}
});
}
}
由于抽取了BaseMvpActivity,所以LoginActivity变化如下
public class LoginActivity extends BaseMvpActivity<LoginPresenter> implements LoginContract.IView {
@Override
protected Object getLayout() {
return R.layout.activity_main;
}
@Override
protected LoginPresenter createPresenter() {
return new LoginPresenter();
}
@Override
protected void afterCreate() {
$(R.id.btn_login).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mPresenter.onClickLogin("xxx", "xxx");
}
});
}
@Override
public void goToMainActivity() {
runOnUiThread(new Runnable() {
@Override
public void run() {
//todo 登录成功跳转到主界面
Log.i("mvp_", "登录成功跳转到主界面");
}
});
}
@Override
public void showProgress(boolean isShow) {
if (isShow) {
Log.i("mvp_", "显示等待框");
} else {
Log.i("mvp_", "隐藏等待框");
}
}
@Override
public void showToast(String str) {
Toast.makeText(this, str, Toast.LENGTH_LONG).show();
}
}
这一节讲解了内存泄露的原因以及解决方案,还有基类的封装抽取,复用。但是还是不够完美,比如,每次调用mView之前需要进行非空判断。这个重复的逻辑能不能做统一封装?这个问题留到下一节处理。