Android MVP Presenter 中引发的空指针异常

一、概述

最近对 googlesamples/android-architecture 中的 MVP-dagger 进行了学习。对照项目的 MVP-dagger 分支,对 MVP-dagger 进行了实践,不日将会在另一篇文章中进行介绍。

MVP 架构,顾名思义,Model-View-Presenter。其作用是解决 Android 的 MVC 架构中,Activity 的职责不清,过于庞杂,难以维护的缺点。

在众多对 MVP 的实践中,Presenter 常有 attachView 和 unattachView 两个方法,用以建立起 Presenter 同 View 的联系,便于在 Presenter 中对 View 的接口进行调用。

然而,Presenter 中常常有一些耗时的操作,在某些情况下(诸如用户退出对应的 View),unAttachView 被调用,此时 Presenter 才完成耗时操作,需要完成对 View 的更新。但此时由于 View 已经被解绑,Presenter 中获取到的 View 为空,若不进行判空操作,则会引起空指针异常。

在 Presenter 中如何优雅地判空?通过进一步了解 Presenter 的生命周期,能不能找到更好的解决方案?这两个问题是本文要讨论的重点!

二、Presenter 中对 View 判空

2.1 最简单直接的暴力方式

https://github.com/cnneillee/DailyZHIHU/blob/master/app/src/main/java/com/neil/dailyzhihu/presenter/TopicDetailPresenter.java

public class TopicDetailPresenter extends RxPresenter<TopicDetailContract.View> implements TopicDetailContract.Presenter {
    private RetrofitHelper mRetrofitHelper;

    @Inject
    TopicDetailPresenter(RetrofitHelper retrofitHelper) {
        this.mRetrofitHelper = retrofitHelper;
    }

    @Override
    public void getTopicDetailData(int topicId) {
        mRetrofitHelper.fetchTopicNewsList(topicId).enqueue(new Callback<TopicStoryListBean>() {
            @Override
            public void onResponse(Call<TopicStoryListBean> call, Response<TopicStoryListBean> response) {
                if (response.isSuccessful()) {
                    mView.showContent(response.body());
                }
            }

            @Override
            public void onFailure(Call<TopicStoryListBean> call, Throwable t) {
                mView.showError(t.getMessage());
            }
        });
    }
}

在上面这个例子中,并没有对 mView 进行判空,当网络状态不好,用户退出当前 Presenter 关联的 View,就极容易引起空指针异常。

为了避免此问题的出现,应当对 mView 进行判空操作。

if(mView != null) mView.showContent(response.body());
...
if(mView != null) mView.showError(t.getMessage());

这是最直接了当的做法。倘若对整个项目进行如是改造,且不说编码规范和设计原则的问题,单是修改整个项目的 Presenter 就得费老鼻子劲儿,修改过程也极容易出现遗漏等问题。

暴力××不可取呀!!!

2.2 整合抽象的方式

当然了,上面的代码在代码规范和设计模式上也有一定的问题。一种更佳的方式是,不直接让子 Presenter 对 mView 进行操作,而是使用 getView 方法对 mView 进行暴露,用户使用 getView 获取绑定的 view

if(getView() != null) getView().showContent(response.body());
...
if(getView() != null) getView().showError(t.getMessage());

2.3 优雅的判空方式

来自知乎专栏的一片文章 『极光日报 - 不要再在你的 Presenter 中检查 view != null 啦』 中介绍了一种优雅的方式,抛异常/使用第三方库。

有另一种我认为更好的方式,就是将 2.2 与 抛异常结合起来。毕竟,谁都不想为了一些小的细节,而引入一个第三方库。

public class TopicDetailPresenter extends RxPresenter<TopicDetailContract.View> implements TopicDetailContract.Presenter {
    private RetrofitHelper mRetrofitHelper;

    @Inject
    TopicDetailPresenter(RetrofitHelper retrofitHelper) {
        this.mRetrofitHelper = retrofitHelper;
    }

    @Override
    public void getTopicDetailData(int topicId) {
        mRetrofitHelper.fetchTopicNewsList(topicId).enqueue(new Callback<TopicStoryListBean>() {
            @Override
            public void onResponse(Call<TopicStoryListBean> call, Response<TopicStoryListBean> response) {
                if (response.isSuccessful()) {
                    getView().showContent(response.body());
                }
            }

            @Override
            public void onFailure(Call<TopicStoryListBean> call, Throwable t) {
                getView().showError(t.getMessage());
            }
        });
    }
}

在父 Presenter 中

protected View getView(){
	if(mView == null) throw new IllegaStateException("view not attached");
	else return mView;
}

三、Presenter 的生命周期

这个话题源自一篇文章 『Android:聊聊 MVP 中 Presenter 的生命周期』

当然,这篇文章涵盖了处理 Presenter 的生命周期 与 Activity/Fragment 生命周期同步的问题的几个框架。同步 Presenter 和 Activity/Fragment 生命周期,从而保证在 View 层(这里姑且 Activity/Fragment 归类到 View 层吧)生命结束后,Presenter 也被终止生命,故而避免了空指针异常的问题!

这里只引用文中的几个框架,详细分析内容,可参见原文!

此文在我的 Github Pages 上同步发布,地址为:Android MVP Presenter 中引发的空指针异常

posted @ 2017-05-03 10:56  NeilLee  阅读(4566)  评论(2编辑  收藏  举报