Fragment 中调用 requireContext() 报错 not attached to a context
今天遇到个线上 Crash,报错信息是:java.lang.IllegalStateException: Fragment ... not attached to a context.。从堆栈初步分析,是调用 requireContext 导致的。考虑到之前也遇到过,但总是容易重新犯错,这次从源码角度分析下,加深理解。
前言
requireContext 是 AndroidX 中的类 Fragment 提供的 API,不属于 Android SDK。
Android SDK 中的类 Fragment 与之对应的方法是 getContext,这个方法 Android X 的类 Fragment 也有,并且作用一样。
细心的你会发现,在使用getContext 时,AS 会建议我们使用 requireContext 替代。
个人认为它这么做的原因是:getContext 返回的值可能为 null,但 requireContext 返回的值一定不为 null,使用后者的好处是避免了判空检查。
但如果你认为可以在 Fragment 内肆意地使用 requireContext,那就大错特错了。
在开始下一步分析前,先建立起一些共同的认知:
Fragment不能独立存在,必须依附Activity。
源码分析
直接上源码:
1 | package androidx.fragment.app; |
可以看到,使用 requireContext 不需要进行空指针检查,是因为在它内部实现了判空逻辑。但这是以抛出异常为代价实现的,这告诫我们要慎用 requireContext。
可能有人会说,那我用 requireContext 之前进行次判空,或者直接用 getContext 不就行了。个人认为这并不是解决问题的根本方案。根本问题在于:我们调用 requireContext 的时候,是否“应该”出现 context 为 null 的情况,如果为 null,是不是认为此时程序执行的不合理了?是不是就应该 Crash?如果不应该为 null,是不是意味着我们的代码逻辑考虑不周,存在问题?从经验和事实来看,两种原因都有:前者多半是系统兼容性问题导致的,后者出现的次数会更多一些。
回到这次的问题,经排查发现,是由于在一个回调事件中,调用 requireContext 导致的异常。通过还原现场发现,用户其实已经退出了 Fragment(跟宿主 activity,或者说 context detach 掉),但是有一个回调在它之后触发,故而导致异常。
解决这个 Crash 的办法有很多,其中之一是在 Fragment 的 onDetach 中取消掉回调时间的注册,这是符合我们场景的预期的。
requireContext 使用建议
- 在回调中调用
requireContext必须慎重!需要分析清楚当回调事件发生时,Fragment 可能存在的状态- 如果 Fragment 被 detach 掉后,访问 requireContext 的行为不符合预期,可以在 onDetach 事件中取消掉该回调的注册。
- 如果确实需要,可以考虑使用其他 context 进行替代,比如 Application 的 context。