Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 744|回复: 0

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

[复制链接]
  • TA的每日心情
    奋斗
    3 天前
  • 签到天数: 789 天

    [LV.10]以坛为家III

    2049

    主题

    2107

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    722638
    发表于 2021-5-4 17:30:06 | 显示全部楼层 |阅读模式

    一、概述

    最近对 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 中引发的空指针异常

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-9-9 04:13 , Processed in 2.063370 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表