问题背景
前几天写了篇 MVP 入门级的文章,文章结尾有个疑问没有解决,如何避免 Presenter 内存泄漏情况的发生?我查看了一些资料后,发现这个问题可以转成另外一个问题,如何保证 Presenter 与 Activity/Fragment 的生命周期一致?
注:为了方便起见,文中 View 层直接用 Activity/Fragment 代替,有时只说 Activity 。
我们先来看一个小案例,看看具体问题是什么样。
案例本身很简单,就是模拟耗时加载数据,一点技术含量都没有,只不过是标准的 MVP 模式。
源码暂时就不贴了,现象为主,什么现象呢?首先我在加载数据时,也就是在 Presenter 那里故意延时了3秒钟,在这3秒钟内,我们对 Activity 做一些邪恶的事情。
1、跳转到另一个 Activity,但并不 Finish 自身:
2、跳转到另一个 Activity,同时 Finish 自身:
仔细对比可以看出几个问题:
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 操作都用一个抽象类来实现,但我仍然不觉得这是一个最优的解决方案(又批判了大神,再逃……)。
Android MVP框架MVPro的使用和源码分析 - 亓斌
而对于 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 接口来间接完成的。
这就很显而易见了,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 机制需要三个东西,三者关系如下图所示:
下面依次来看:
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 已经完全被销毁重用了。
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<PersonBean> loadPersonInfo(); }
Model
实现 Model Interface ,这里是模拟数据:
public class PersonModel implements IPersonModel { //存一下Person的信息 private ArrayList<PersonBean> personList = new ArrayList<>(); /** * 加载Person信息 * * @return 返回信息集合 */ @Override public ArrayList<PersonBean> 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<PersonBean> personList); }
Base Presenter
这里因为我们需要跟 Activity 生命周期挂钩,所以抽取一个 Presenter 的基类:
public interface BasePresenter<V> { 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<PersonBean> 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<T extends BasePresenter> extends Loader<T> { 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 extends Presenter> { T create(); }
View
好了,那么最后就是 View 层的实现类 Activity 了,仍然先看代码:
public class MainActivity extends AppCompatActivity implements IPersonView, LoaderManager.LoaderCallbacks<PersonPresenter>, 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<PersonBean> personList) { PersonBean personBean = personList.get(0); tvMainName.setText("姓名:" + personBean.getName()); //...省略 showToast("第 " + i + " 次加载"); i++; } /*========== Loader 的回调方法 ==========*/ @Override public Loader<PersonPresenter> onCreateLoader(int id, Bundle args) { //创建 return new PresenterLoader<>(this, new PresenterFactory(this)); } @Override public void onLoadFinished(Loader<PersonPresenter> loader, PersonPresenter presenter) { //完成加载 this.mPersonPresenter = presenter; } @Override public void onLoaderReset(Loader<PersonPresenter> 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 自身:
2、跳转到另一个 Activity,同时 finish() 自身:
从图中我们可以看到两个现象:
当 Presenter 所在 Activity 生命周期发生变化时,Presenter 也会发生对应的变化,图中的变化就是停止循环加载;
当不 finish 掉 Activity 并再次返回时,Presenter 仍然可以继续之前停止的位置开始加载,这就是 Loader 的作用。
上面两点说明的问题一句话概括就是:
Presenter 的生命周期已经和 Activity 保持一致了,而且比 Activity 的生命周期还要长,因为它被 Loader 绑定了。
到此,文章开头的疑问也就基本解决了。
总结
回头看了一下,实在没想到写了这么多,有的地方还是啰嗦了,而且案例选择的不太好,不过在原理上已经没啥大问题了。
不过值得注意的是,用 Loader 方案去解决这种问题,并不是完美的,真正实践起来还是有坑的地方,比如 Fragment 里面比较难控制 Loader ,只是思路比较有趣,解决起来也相对比较容易,至少不影响 MVP 的架构,个人来说比较喜欢。
之前也跟 GithubApp 开发者 交流过一次,他的思路是使用一个管理者去管理 Rx 中所有的 Subscription 。要是生命周期变化,对应的 Presenter 就会变化,根据这些变化一些 Subscription 就会被退订,也就防止了内存泄漏的情况。
不管怎么说,还是实践出真知。
共同学习,写下你的评论
评论加载中...
作者其他优质文章