手记

Android:聊聊 MVP 中 Presenter 的生命周期

问题背景

前几天写了篇 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 来管理。不得不说,这个思路我还是比较能接受的,也是我能想出来的最简单的方法。

Android应用中MVP最佳实践 - Jude95

但是回头看一看 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 就会被退订,也就防止了内存泄漏的情况。

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

原文链接:http://www.apkbus.com/blog-854106-61852.html

0人推荐
随时随地看视频
慕课网APP