基于Activity、Fragment的生命周期避免MVP模式内存泄露的问题

前言

回顾

之前对MVP做过相关研究《Android-architecture之MVC、MVP、MVVM、Data-Binding》,也做过简单的分享《Android Architecture(Is Activity God?)》,在《列表全家桶之刷新、加载更多、悬浮、左滑删除》中也提到过希望对MVP相关做进一步的升级,抽取出了BasePresenter、BaseView、MVPBaseActivity、MVPBaseFragment,并通过使用弱引用预防可能发生的内存泄露问题 ,遂抽空着手整理一下

框架模式、设计模式、架构的关系

  • 框架模式通常是对(面向相同行为代码的重用)代码的重用,是大智慧,用来对软件设计进行分工
  • 设计模式通常是对(面向相同结构代码的重用)设计的重用,是小技巧,对具体问题提出解决方案,以提高代码复用率,降低耦合度
  • 架构则介于框架和设计之间
  • 我们所说的MVC、MVP、MVVM是一种框架模式而非设计模式
  • Android图片加载库的封装实战即是通过设计模式完成对图片加载框架Glide进行封装的案例

    问题发现

    MVP有很多优点,例如:
  • 易于维护
  • 易于测试
  • 松耦合
  • 复用性高
  • 健壮稳定
  • 易于扩展

但是,由于Presenter经常性地需要执行一些耗时操作,例如请求网络数据,而Presenter持有了Activity或者Fragment的强引用,如果在请求结束之前Activity或者Fragment被销毁了,那么由于网络请求还没有返回,导致Presenter一直持有Activity或者Fragment的对象,使得Activity或者Fragment对象无法回收,此时就发生了内存泄露

MVP模式内存泄露问题的解决

答案就是,通过弱引用和Activity、Fragment的生命周期来解决这个问题,首先建立一个Presenter对象,我们命名为BasePresenter,它是一个泛型类,泛型类型为View角色要实现的接口类型,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* Author: SuS
* Version V1.0
* Date: 17/02/23
* Description:通过弱引用和Activity以及Fragment的生命周期预防内存泄露的问题
* Modification History:
* Date Author Version Description
* -----------------------------------------------------------------------------------
* 17/02/23 SuS 1.0 1.0
* Why & What is modified:
*/
public abstract class BasePresenter<V> {
protected Reference<V> mViewRef;//View 接口类型的弱引用
public void attachView(V view) {
mViewRef = new WeakReference<V>(view);
}
protected V getView() {
return mViewRef.get();
}
public boolean isViewAttached() {
return mViewRef != null && mViewRef.get() != null;
}
public void detachView() {
if (mViewRef != null) {
mViewRef.clear();
mViewRef = null;
}
}
//每个Presenter都会有初始化的工作,可以在这里统一处理
// 当然也可以不处理,这里只是一个公用的示范方法
public abstract void start();
//这里也可以理解为一个公用的示范方法
public abstract void update();
}

除了start和update两个公用的示范方法,BasePresenter还有4个方法,分别与View建立关联、解除关联、判断是否与View建立了关联、获取View。View类型通过BasePresenter的泛型类型传递进来,Presenter对这个View持有弱引用。通常情况下这个View类型应该是实现了某个特定接口的Activity或者Fragment等类型。
创建一个MVPBaseActivity基类,通过这个基类的生命周期函数来控制它与Presenter的关系,相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* Author: SuS
* Version V1.0
* Date: 17/02/23
* Description:MVP Activity基类
* Modification History:
* Date Author Version Description
* -----------------------------------------------------------------------------------
* 17/02/23 SuS 1.0 1.0
* Why & What is modified:
*/
public abstract class MVPBaseActivity<V,P extends BasePresenter<V>> extends BaseActivity {
private static final String TAG = "MVPBaseActivity";
protected P mPresenter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = createPresenter();//创建Presenter
mPresenter.attachView((V)this);
}
@Override
public void onDestroy() {
super.onDestroy();
mPresenter.detachView();
}
protected abstract P createPresenter();
}

MVPBaseActivity含有两个泛型参数,第一个是View接口类型,第二个是Presenter的具体类型,通过泛型参数,使得一些通用的逻辑可以抽象到MVPBaseActivity类中。例如,在MVPBaseActivity的onCreate函数中,会通过createPresenter函数创建一个具体的Presenter,这个Presenter的类型就是BasePresenter类型。构建Presenter之后调用attachView函数与Activity建立关联,而在onDestroy函数中,则会与Activity解除关联,从而避免内存泄露。
疑问:如果在onDestroy中解除了对Activity的引用,那么就没有必要再用弱引用了
解惑:并不是在任何情况下Activity的onDestroy都会被调用(其它原因导致Activity对象还在被引用,就不会回调onDestroy方法),一旦这种情况发生,弱引用也能够保证不会造成内存泄露。而通过MVPBaseActivity的封装维护Presenter与View关联关系的代码,使得子类可以避免重复的代码。
当然我们也可以把同样的思想用到更广阔的范围,例如可以为Fragment或者FragmentActivity建立类似这样的基类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* Author: SuS
* Version V1.0
* Date: 17/02/23
* Description:MVP Fragment基类
* Modification History:
* Date Author Version Description
* -----------------------------------------------------------------------------------
* 17/02/23 SuS 1.0 1.0
* Why & What is modified:
*/
public abstract class MVPBaseFragment<V,P extends BasePresenter<V>> extends BaseFragment {
private static final String TAG = "MVPBaseFragment";
protected P mPresenter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = createPresenter();//创建Presenter
mPresenter.attachView((V)this);
}
@Override
public void onDestroy() {
super.onDestroy();
mPresenter.detachView();
}
protected abstract P createPresenter();
}

补充:其实我们在Activity中嵌套Fragment的情况下也可以通过如下方式将Presenter从Activity注入到Fragment

1
2
3
4
5
6
public interface BaseView<P> {
//这个可以在Activity中包裹Fragment的时候应用,这时候继承MVPBaseActivity
//Activity中初始化Presenter的实例 ,然后通过view调用该方法将Presenter塞给Fragment
void setPresenter(P presenter);
}

定义自己的MVP框架

提供一个MVPContract ,封装MVP需要的所有基础接口,View和InteractionListener中使用的泛型为加载的数据类型,假设为MVPItem类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MVPContract {
public interface Model {
//请求数据
void loadContent(boolean isLoadMore, String lastKey);
}
public interface View<T> {
//销毁加载页面
void dismissLoadingViews();
//展示加载页面
void showLoadingViews();
//展示异常页面
void showErrorViews(int errorCode, String msg);
//刷新块数据的内容
void refreshContentView(ArrayList<T> contentList);
//加载更多块数据的内容
void loadMoreContentView(ArrayList<T> contentList);
}
public interface Presenter {
//下拉刷新请求
void requestRefresh();
//加载更多数据
void requestLoadMore();
}
public interface InteractionListener<T> {
//请求成功
void onInteractionSuccess(T t);
//请求失败
void onInteractionFail(int errorCode, String errorMsg);
}
}

实现自己的Presenter,命名为MVPPresenter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class MVPPresenter extends BasePresenter<MVPContract .View<MVPItem>> implements MVPContract .InteractionListener<ArrayList<MVPItem>>,MVPContract .Presenter{
private MVPContract .View<MVPItem> mView;
private MVPContract .Model mModel;
private String param1;
private ArrayList<MVPItem> mList;
private boolean isLoading = false;
private boolean isLoadMore = false;
public MVPPresenter (String param, MVPContract .View<MVPItem> view){
this.param= param;
this.mView = view;
mModel = new MVPModel(param,this);
}
@Override
public void onInteractionSuccess(ArrayList<MVPItem> list) {
isLoading = false;
if(isLoadMore){
this.mList.addAll(list);
mView.loadMoreContentView(list);
} else {
this.mList = list;
mView.refreshContentView(list);
}
mView.dismissLoadingViews();
}
@Override
public void onInteractionFail(int errorCode, String errorMsg) {
isLoading = false;
mView.dismissLoadingViews();
mView.showErrorViews(errorCode, errorMsg);
}
@Override
public synchronized void requestRefresh() {
if (isLoading) {
return;
}
isLoading = true;
isLoadMore = false;
mModel.loadContent(false,null);
}
@Override
public synchronized void requestLoadMore() {
if (isLoading) {
return;
}
if (mList == null || mList.size() == 0) {
return;
}
isLoading = true;
isLoadMore = true;
mModel.loadContent(true,mList.get(mList.size() - 1).getKey());
}
@Override
public void start() {
if (isLoading) {
return;
}
isLoading = true;
isLoadMore = false;
mView.showLoadingViews();
mModel.loadContent(false,null);
}
@Override
public void update() {
}
}

实现自己的Model,命名为MVPModel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MVPModel implements MVPContract.Model {
private MVPContract.InteractionListener<ArrayList<MVPItem>> mListener;
private String param;
public MVPModel(String param, MVPContract.InteractionListener<ArrayList<MVPItem>> listener) {
this.param = param;
this.mListener = listener;
}
@Override
public void loadContent(boolean isLoadMore, String lastKey) {
//网络请求
//数据处理
//成功或者失败的回调
//伪代码
if(success){
mListener.onInteractionSuccess("结果数据");
}else{
mListener.onInteractionFail("错误码","错误信息");
}
}
}

例如MVPFragment 继承自MVPBaseFragment的实现如下:
此时,Presenter的创建以及与View建立关联等操作都被封装到MVPBaseFragment中,消除了子类重复代码的同时又避免了内存泄露的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public class MVPFragment extends MVPBaseFragment<MVPContract.View<MVPItem>, MVPPresenter> implements MVPContract.View<MVPItem>, MVPListView.IListener{
private static final String TAG = MVPFragment.class.getSimpleName();
private String param;
public MVPFragment() {
// Required empty public constructor
}
public static MVPFragment newInstance(String param) {
MVPFragment fragment = new MVPFragment();
Bundle args = new Bundle();
args.putString("param", param);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
if (getArguments() != null) {
param = getArguments().getString("param");
}
super.onCreate(savedInstanceState);
}
@Override
protected MVPPresenter createPresenter() {
return new MVPPresenter(param, this);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_mvp, container, false);
initView(v);
return v;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
initData();
}
private void initView(View v) {
}
private void initData() {
mPresenter.start();
}
@Override
public void dismissLoadingViews() {
}
@Override
public void showLoadingViews() {
}
@Override
public void showErrorViews(int errorCode, String msg) {
}
@Override
public void refreshContentView(ArrayList<MVPItem> contentList) {
}
@Override
public void loadMoreContentView(ArrayList<MVPItem> contentList) {
}
@Override
public void onRefresh() {
mPresenter.requestRefresh();
}
@Override
public void onLoadMore() {
mPresenter.requestLoadMore();
}
}

这里的MVPListView.IListener如下:(仅做参考)

1
2
3
4
5
6
7
8
9
/**
* Implements this interface to get refresh/load more event.
*/
public interface IListener{
public void onRefresh();
public void onLoadMore();
}

小结

  • 从整体效果来说,MVP是开发过程中非常值得推荐的架构模式,它能够将各组件进行解耦,并且带来良好的可扩展性、可测试性、稳定性、可维护性,同时使得每个类型的职责相对单一、简单,避免了大量的“胖”的程序存在,例如数千行的Activity类。它有效的将业务逻辑、数据处理等工作从Activity等View元素中抽离出来,使得每个类尽可能简单,同时每个模块能够独立进行演化。它的思想也非常好地体现了面向对象的设计原则,即抽象、单一指责、最小化、低耦合。
  • 当然,需要说明的是,你的项目并不一定非要用MVP/MVVM或者别的什么模式,模式都有它们自身应用的范围,利弊并存,但了解是必需的,不然你很难扩展自己的技能范围,高级的开发应该学会判断某个项目是否合适一些现成的模式,合理的使用模式会让你的应用框架更加清晰并且易于维护和扩展。

参考

Android源码设计模式解析与实战

SuS wechat
欢迎您扫一扫上面的微信公众号,订阅我的最新信息!
坚持原创技术分享,您的支持将鼓励我继续创作!