android mvp beam框架,Android:聊聊 MVP 中 Presenter 的生命周期

探讨了MVP模式中Presenter生命周期与Activity/Fragment不一致导致的问题,提出了使用Loader方案来确保两者生命周期的一致性,有效避免内存泄漏。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题背景

前几天写了篇 MVP 入门级的文章,文章结尾有个疑问没有解决,如何避免 Presenter 内存泄漏情况的发生?我查看了一些资料后,发现这个问题可以转成另外一个问题,如何保证 Presenter 与 Activity/Fragment 的生命周期一致?

注:为了方便起见,文中 View 层直接用 Activity/Fragment 代替,有时只说 Activity 。

我们先来看一个小案例,看看具体问题是什么样。

案例本身很简单,就是模拟耗时加载数据,一点技术含量都没有,只不过是标准的 MVP 模式。

[图片上传失败...(image-4c30c2-1530535722013)]

源码暂时就不贴了,现象为主,什么现象呢?首先我在加载数据时,也就是在 Presenter 那里故意延时了3秒钟,在这3秒钟内,我们对 Activity 做一些邪恶的事情。

1、跳转到另一个 Activity,但并不 Finish 自身:

[图片上传失败...(image-16c13c-1530535722013)]

2、跳转到另一个 Activity,同时 Finish 自身:

[图片上传失败...(image-f42772-1530535722013)]

仔细对比可以看出几个问题:

Presenter 所在 Activity 已经不可见了(onStop),Presenter 仍然在作用;

Presenter 所在 Activity 已经被杀死了(onDestroy),Presenter 仍然在作用;

页面不可见的时候,竟然没有报空指针,且返回时仍然能看见,显示正常。

这些现象都是因为 Activity 的生命周期变化后 Presenter 没有做对应的处理所造成的,那该怎么办呢?

一些思考

上文说了,问题本质就是如何保证 Presenter 与 Activity/Fragment 的生命周期一致?其实方法有很多,比较成熟的有 MVPro 框架、Beam 框架,还有今天要说的比较有趣且巧妙的方法 Loader 方案。

对于 MVPro 框架,它的思路是:

既然要保证 Presenter 与 Activity 生命周期一致,那不如就把 Activity 作为 Presenter 层,而不是作为 View 层。

这就上升到更深层次的问题:Activity/Fragment 到底是 V 层还是 P 层?

网上各种说法都有,我个人觉得 P 层是 M 层和 V 层的沟通桥梁,而按照我们惯性的想法就是 Activity 是直观呈现给用户的,是与 View 有直接关联的,包括一些展示、输入、更新等等操作都是在 Activity 上完成(至少给我们的直观感觉是这样),那么显然 Activity 更合适在 V 层上。

虽然 MVPro 框架将 Activity 上所有的 View 操作都用一个抽象类来实现,但我仍然不觉得这是一个最优的解决方案(又批判了大神,再逃……)。

而对于 Beam 框架,它的思路是这样的:

Presenter 与 Activity 的绑定关系应由静态类管理,而不是由 Activity 管理。当 Activity 意外重启时 Presenter 不应重启,只需在 Activity 重启时,让 Presenter 与 Activity 重新绑定,并根据数据恢复 Activity 状态即可。而当 Activity 真正销毁时,对应 Presenter 才应该跟随销毁。

这跟设置一个单例的 Application 有点类似,不管 Activity 怎么变化,Application 都只有一个,所以可以通过这个 Application 来管理。不得不说,这个思路我还是比较能接受的,也是我能想出来的最简单的方法。

但是回头看一看 MVP 架构的核心思想(原文及图来源):

将 Activity/Fragment 变成一个单纯的 View ,负责展示数据并将各种事件分发给中间人,也就是 Presenter 。Presenter 会处理每一个事件,从 Model 层获取或上传数据,并将获得的数据进行处理并让 View 层展示。Presenter 与 Activity/Fragment 的通信,是通过 Activity/Fragment 所继承的 View 接口来间接完成的。

24d7d01ab1da

MVP

这就很显而易见了,Activity/Fragment 就是 View 层,所以我们仍然需要一个更好的方法来解决这个问题,让 Presenter 不由 Activity/Fragment 的生命周期来管理。

由于 Presenter 是一个中间的主持者,所以生命周期一定长于或者说至少不短于 Activity/Fragment ,所以这就有两点要求:

Presenter 生命周期独立;

Presenter 生命周期长于 Activity/Fragment 。

但我们知道,Presenter 只是我们自己定义的一层中间主持者对象,如果要实现需求还是要绑定一个已有的东西,那 Android 里有什么东西的生命周期是独立且长于 Activity/Fragment 呢?

我首先想到的是 Application ,它的生命周期是独立且大于等于 Activity 的,完全满足需求,而且一般我们做项目的时候都会有一个 Application 类,充当一个全局管理者的角色。但这跟上面的 Beam 框架有点类似,所以暂时不说这个。

那有没有更好的呢?我没想到,但是别人想到了,也就是今天说的 Loader 。

初识 Loader

关于 Loader 类,其实之前我不太了解,只是在《 Andrid 开发艺术探索》里面见过这个类,当时说的是异步。因为异步这东西现在很多框架都能很好的实现,所以并没在意,但通过几天的学习,觉得这个类还是很 NB 的。

因为文章重点不是这个类,我就简单说一下它的作用及特点,不深入讨论具体的使用方式,有兴趣可以点击文末相关链接学习。

Loader 是啥

一句话概括:

Android 提供的一种支持 Activity/Fragment 的、异步的、带有监听功能的、能自动重连接的加载器。

哦哟,真的很 NB 的样子,来一个一个看。

1、支持 Activity/Fragment

这个意思是不管在 Activity 中还是 Fragment 中,它都能通过 getLoaderManager().initLoader(0, null, this) 来创建自身的管理类。而我们的 View 层的实现刚好是 Activity/Fragment 。

2、异步

这个词在 Android 中不要太熟悉,在 Loader 的实现中还有一个抽象子类 AsyncTaskLoader 。它的内部提供一个 AsyncTask 进行异步的一些耗时操作。这就很厉害了,因为这个问题的源头就是 Presenter 进行了耗时操作。

3、带有监听功能

这个意思其实就是能够及时响应一个数据的变化并实时更新 UI ,有点类似 ContentObserver ,充当一个观察者的角色。

4、能自动重新连接

我觉得这才是重磅功能,它能够在 Activity/Fragment 发生 Configuration Change 的时候,自动重新连接。比如 Activity 突然横屏了,生命周期发生了巨大变化,这个时候它能够自己处理这些变化,并自动重新连接自身。

说到这里,Loader 的强大之处我们已经能够窥见一丢丢了。

Loader 的生命周期

前面就一直强调生命周期的问题,既然 Loader 满足需求,那就来看看它的生命周期。一般来说,一个完成的 Loader 机制需要三个东西,三者关系如下图所示:

[图片上传失败...(image-1c4b5a-1530535722013)]

下面依次来看:

1、LoaderManager

顾名思义,它是 Loader 的管理者:

initLoader():第一个参数是 Loader 的 Id,第二个参数可选,第三个参数为回调的实现类,一般都为当前的 Activity/Fragment。

restartLoader():其实一般通过 initLoader 都会监测是否存在指定 Id 的 Loader ,如果有就重启一下,但是如果你不想要之前的数据了,就彻底重新一个新的 Loader 。

2、LoaderManagerCallbacks

从名称就可以看出,这是 LoaderManager 的回调类,里面有三个方法:

onCreateLoader():实例化和返回新建给定 Id 的 Loader ;

onLoadFinished():当一个创建好的 Loader 完成了 Load 过程,调用此函数;

onLoaderReset():当一个创建好的 Loader 要被 Reset ,调用此函数,此时数据无效。

3、Loader

a、生命周期

active:活动状态:

started:启动状态;

stopped:停止状态,有可能再次启动。

inactive:非活动状态:

abandoned:废弃状态,废弃后过段时间也会重置;

reseted:重置状态,表示该 Loader 已经完全被销毁重用了。

24d7d01ab1da

Loader 生命周期图

b、onStartLoading

如果 Activity 执行 onStart 方法,Loader 会执行此方法,此时 Loader 处于 started 状态下,Loader 应该监听数据源的变化,并将变化的新数据发送给客户端。 这个时候有两种情况:

已经存在 Loader 所要加载对象实例,应该调用 deliverResult() 方法,触发 onLoadFinished() 回调的执行,从而客户端可以从该回调中轻松获取数据。

如果没有的话,则先触发 onCreateLoader() 回调创建 Loader ,再调用 forceLoad() 方法,去促使它去加载,加载后再调用 deliverResult() 方法,回调 onLoadFinished() 。

c、onStopLoading

当 Activity/Fragment 执行 onStop() 方法时,Loader 会调用此方法,此时 Loader 处于 stopped 状态下。而且当 Activity/Fragment 处于 stopped 状态时,所有的 Loader 也会被置于 stopped 状态。

此时应该继续监听数据的变化,但是如果数据有变化应该先存起来,等重新 start 的时候再发送给客户端更新 UI 。

d、onReset

abandoned 状态暂时略过,来看 onReset 这个方法。它会在 Activity/Fragment 销毁时调用(主动调用 destroyLoader() 也可)。触发 onLoaderReset() 回调,并重新创建 Loader ,后续步骤类似 onStartLoading() 。

回头看看上文会发现它的第四个特点跟它的生命周期密切相关。也就是说,它不管 Activity/Fragment 怎么变化,它自己过它自己的。

为什么选 Loader

通过上文对 Loader 的相关了解,现在来总结一下,为什么 Loader 能够满足这样的需求呢?

Loader 是 Android 框架中内部提供的;

每一个 Activity/Fragment 都可以持有自己的 Loader 对象的引用。

Loader 在 Activity/Fragment 状态改变时是不会被销毁的,因为它可以自动重建;

Loader 的生命周期是是由系统控制的;

Loader 会在 Activity/Fragment 不再被使用后由系统自动回收;

Loader 与 Activity/Fragment 的生命周期绑定,事件自身就能分发;

项目改造

先分析

好了,花了很长的篇幅去简单介绍一下 Loader 。现在回到本质问题上,如何利用 Loader 的相关特性去解决 Presenter 的生命周期问题呢?一句话概括:

在 Loader 的生命周期内,绑定其所在的 Activity/Fragment 所对应的 Presenter 。

这就能让 Presenter 独立于 Activity/Fragment 的生命周期之外,这样就不用担心它们生命周期变化所带来的一系列问题。

再动手

现在我们就要对本文开头的那个小案例进行修改了,不过有一点说明:

之前的例子不太好,因为现在 Android 中子线程不能手动 stop ,所以没法演示 Activity 销毁,Presenter 就同步销毁的 案例,所以我将耗时部分加了循环,根据标志位,判断是否循环加载。

好,现在一项一项来:

Bean

数据 Bean 类,太简单,不说:

public class PersonBean {

private String name;

private String age;

// ...省略

}

Model Interface

仍然是为了演示架构强行抽取的,没有实质性意义的方法:

public interface IPersonModel {

//加载Person信息

ArrayList loadPersonInfo();

}

Model

实现 Model Interface ,这里是模拟数据:

public class PersonModel implements IPersonModel {

//存一下Person的信息

private ArrayList personList = new ArrayList<>();

/**

* 加载Person信息

*

* @return 返回信息集合

*/

@Override

public ArrayList loadPersonInfo() {

personList.add(initPerson());

return personList;

}

private PersonBean initPerson() {

PersonBean personBean = new PersonBean();

personBean.setName("张三");

//...省略

return personBean;

}

}

View Interface

View 层必须的 Interface ,这里也就一个方法:

public interface IPersonView {

//更新UI

void updateUI(ArrayList personList);

}

Base Presenter

这里因为我们需要跟 Activity 生命周期挂钩,所以抽取一个 Presenter 的基类:

public interface BasePresenter {

void onViewAttached(V view);

void onViewDetached();

void onDestroyed();

}

Presenter

这里就是重要的 Presenter 的实现了,有几点要注意的:

仍然需要 Model 和 View 层的接口;

线程是循环的,是否循环通过标记判断;

在 onViewAttached、onViewDetached、onDestroyed 中改变循环标记。

public class PersonPresenter implements BasePresenter {

private IPersonModel mPersonModel; //Model接口

private IPersonView mPersonView; //View接口

private Handler mHandler = new Handler(); //模拟耗时用的 没实质性作用

private boolean isLoad = true; //循环加载标志

public PersonPresenter(IPersonView mPersonView) {

mPersonModel = new PersonModel();

this.mPersonView = mPersonView;

}

public void updateUIByLocal() {

//Model层处理

final ArrayList personList = mPersonModel.loadPersonInfo();

new Thread(new Runnable() {

@Override

public void run() {

while (isLoad) {

//模拟1s耗时

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

//运行在 Main 线程

mHandler.post(new Runnable() {

@Override

public void run() {

//View层更新

mPersonView.updateUI(personList);

}

});

}

}

}).start();

}

public void onViewAttached(Object view) {

this.isLoad = true;

updateUIByLocal();

}

public void onViewDetached() {

this.isLoad = false;

}

public void onDestroyed() {

this.isLoad = false;

}

}

Loader

现在来看 Loader 怎么写,先看源码:

public class PresenterLoader extends Loader {

private PersonPresenter presenter;

private PresenterFactory factory;

public PresenterLoader(Context context,PresenterFactory factory) {

super(context);

this.factory = factory;

}

@Override

protected void onStartLoading() {

// 如果已经有Presenter实例那就直接返回

if (presenter != null) {

deliverResult((T) presenter);

return;

}

// 如果没有 就促使加载

forceLoad();

}

@Override

protected void onForceLoad() {

// 实例化 Presenter

presenter = factory.create();

// 返回 Presenter

deliverResult((T) presenter);

}

@Override

protected void onReset() {

presenter.onDestroyed();

presenter = null;

}

}

这里只继承了最简单的 Loader 类,T 就是各种继承 BasePresenter 的 Presenter。

记着 Loader 的生命周期与 Presenter 生命周期一致,所以 Loader 开启,那就要加载 Presenter。如果 Loader 重启,那么 Presenter 就要销毁。

这里有个 PresenterFactory 类,就是为了创建各种 Presenter,具体看下面。

PresenterFactory

这是 Presenter 的工厂类,为了创建各种 Presenter,先看代码怎么写的:

public class PresenterFactory{

private IPersonView mPersonView;

public PresenterFactory(IPersonView mPersonView) {

this.mPersonView = mPersonView;

}

public PersonPresenter create() {

return new PersonPresenter(mPersonView);

}

}

可以看到很简单,就是 new 一个对应的 Presenter 出来,这里我偷懒了,最好抽取一个接口出来:

public interface PresenterFactory {

T create();

}

View

好了,那么最后就是 View 层的实现类 Activity 了,仍然先看代码:

public class MainActivity extends AppCompatActivity implements IPersonView, LoaderManager.LoaderCallbacks, View.OnClickListener {

/*===== 控制相关 =====*/

private int i = 0;

private Toast mToast;

private PersonPresenter mPersonPresenter;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

initView();

initListener();

initData();

}

//...省略

private void initData() {

//得到一个Loader管理者,并创建一个Loader

getLoaderManager().initLoader(0, null, this);

}

public void onClick(View view) {

switch (view.getId()) {

case R.id.bt_main_load:

mPersonPresenter.updateUIByLocal();

break;

case R.id.bt_main_goto:

gotoOther(fsvMainFinish.ismIsOpen());

break;

}

}

private void gotoOther(boolean isFinish) {

startActivity(new Intent(this, OtherActivity.class));

if (isFinish) {

finish();

}

}

@Override

protected void onStart() {

super.onStart();

mPersonPresenter.onViewAttached(this);

}

@Override

protected void onStop() {

super.onStop();

mPersonPresenter.onViewDetached();

}

@Override

protected void onDestroy() {

super.onDestroy();

mPersonPresenter.onDestroyed();

}

/**

* View 接口方法 更新UI

*

* @param personList 用户集合

*/

@Override

public void updateUI(ArrayList personList) {

PersonBean personBean = personList.get(0);

tvMainName.setText("姓名:" + personBean.getName());

//...省略

showToast("第 " + i + " 次加载");

i++;

}

/*========== Loader 的回调方法 ==========*/

@Override

public Loader onCreateLoader(int id, Bundle args) {

//创建

return new PresenterLoader<>(this, new PresenterFactory(this));

}

@Override

public void onLoadFinished(Loader loader, PersonPresenter presenter) {

//完成加载

this.mPersonPresenter = presenter;

}

@Override

public void onLoaderReset(Loader loader) {

//销毁

this.mPersonPresenter = null;

}

}

代码中的核心有这几个部分:

分别实现了 View 层接口和 LoaderManager.LoaderCallbacks 接口,因为前文已经说过 Loader 需要回调;

通过 getLoaderManager().initLoader(0, null, this) 得到一个管理者并新建一个 Id 为 0 的 Loader ,这里我没有用 V4 包,所以没有 support ;

在 Activity 的每个生命周期方法中,调用 Presenter 的相关方法;

实现 LoaderCallbacks 接口的相关回调方法。

看到这里,大家可能有疑问,从代码中看 Presenter 的相关方法确实已经跟 Activity 的生命周期连在一起了,但为什么说是绑定了 Loader 呢?原因如下:

Presenter 确实和 Loader 绑定了,因为 Presenter 随着 Loader 的创建/销毁而创建/销毁,并非是 Activity;

Activity 生命周期中 Presenter 对应的方法并不意味着它和 Presenter 绑定,只是生命周期发生改变需要 Presenter 做出一些变化而已;

这种做法其实也可以说是:利用 Loader 延长了 Presenter 的生命周期。

看结果

现在我们看一下结果:

1、跳转到另一个 Activity,但并不 Finish 自身:

[图片上传失败...(image-9274bc-1530535722013)]

2、跳转到另一个 Activity,同时 finish() 自身:

[图片上传失败...(image-92f4bf-1530535722013)]

从图中我们可以看到两个现象:

当 Presenter 所在 Activity 生命周期发生变化时,Presenter 也会发生对应的变化,图中的变化就是停止循环加载;

当不 finish 掉 Activity 并再次返回时,Presenter 仍然可以继续之前停止的位置开始加载,这就是 Loader 的作用。

上面两点说明的问题一句话概括就是:

Presenter 的生命周期已经和 Activity 保持一致了,而且比 Activity 的生命周期还要长,因为它被 Loader 绑定了。

到此,文章开头的疑问也就基本解决了。

总结

回头看了一下,实在没想到写了这么多,有的地方还是啰嗦了,而且案例选择的不太好,不过在原理上已经没啥大问题了。

不过值得注意的是,用 Loader 方案去解决这种问题,并不是完美的,真正实践起来还是有坑的地方,比如 Fragment 里面比较难控制 Loader ,只是思路比较有趣,解决起来也相对比较容易,至少不影响 MVP 的架构,个人来说比较喜欢。

之前也跟 GithubApp 开发者 交流过一次,他的思路是使用一个管理者去管理 Rx 中所有的 Subscription 。要是生命周期变化,对应的 Presenter 就会变化,根据这些变化一些 Subscription 就会被退订,也就防止了内存泄漏的情况。

不管怎么说,还是实践出真知。

参考资料

项目源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值