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

2018-07-02  本文已影响0人  iamxiarui

问题背景

前几天写了篇 MVP 入门级的文章,文章结尾有个疑问没有解决,如何避免 Presenter 内存泄漏情况的发生?我查看了一些资料后,发现这个问题可以转成另外一个问题,如何保证 PresenterActivity/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)]

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

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

一些思考

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

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

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

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

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

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

Android MVP框架MVPro的使用和源码分析 - 亓斌

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

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

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

Android应用中MVP最佳实践 - Jude95

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

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

MVP

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

由于 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 的管理者:

2、LoaderManagerCallbacks

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

3、Loader

a、生命周期

Loader 生命周期图

b、onStartLoading

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

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 。现在回到本质问题上,如何利用 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 的实现了,有几点要注意的:

    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;
    }
}

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;
    }
}

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

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

看结果

现在我们看一下结果:

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

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

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

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

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

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

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

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

总结

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

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

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

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

参考资料

通过Loader延长Presenter生命周期

深入源码解析Android中Loader、AsyncTaskLoader、CursorLoader、LoaderManager

Android应用Loaders全面详解及源码浅析 - 工匠若水

项目源码

LoaderMVPDemo - IamXiaRui


个人博客:www.iamxiarui.com
原文链接:http://www.iamxiarui.com/?p=903

上一篇 下一篇

猜你喜欢

热点阅读