起因
最近有同事和用户反馈, 说应用首页的 4 个 tab 在使用了一段时间之后 reselect 重复点击选中时没有触发刷新, 并且是小概率事件本地复现无果后, 先进行代码排查
排查过程
反馈的这个问题有 2 个信息:
点击 tab 没有反应(说明执行过程遇到错误)
点击 tab 没有崩溃(说明这个错误没有引起闪退)
看了代码后发现, 逻辑上并没有条件判断的 return 逻辑, 并且在监听 tab 的事件上使用了 RxJava , 那么结论显而易见了, 是 RxJava 吃掉了本该引起崩溃的异常
再往下跟, 发现事情可能没有这么简单, 这个异常是一个空指针异常, 表示某个 ui 控件的成员变量没有初始化
ui 控件的成员变量初始化是交给 butterknife 的
butterknife 的逻辑是在 Fragment 的 onCreateView 方法中执行
说明这个 onCreateView 的生命周期方法没有被执行到, 那么为什么没有被执行到呢? 联想到用户反馈的一个条件是使用了一段时间, 那么这个问题很可能和 savedInstanceState 有关
一般和 tab 组合使用的 Fragment 会用到
FragmentPagerAdapter
, 而我们在对 Fragment 做重用 (即利用 savedInstanceState 进行初始化) 的时候, 不仅仅需要考虑 Fragment 自身的恢复, 还要考虑 Fragment 所包含的成员变量的恢复对于初始化逻辑嵌套好几层的来说, 就需要传递好几层的 savedInstanceState , 但代码中明显是漏掉不少的
提到 savedInstanceState , 就不得不说开发者模式下的不保留活动了开启不保留活动后, 这个问题就变成必现的了
这让我重新思考到底什么时候需要做恢复
首页的这 4 个 tab 对应的 fragment 是不需要做恢复的
在有用户输入的场景(如果没有备份的草稿逻辑, 需要做恢复)
在数据量不大的关键 ui 上需要做恢复
去掉恢复逻辑后, 这个问题解决了, 但处理过程中发现了其他的问题 Fragment 这个东西坑特别多, 因为这个东西可以 new 出来, 但又有 Activity 类似的生命周期那么在写代码的时候就要特别注意
尽量不要增加不必要的 public 方法
所有的非生命周期 public 方法都要进行 isAdded 的判断和 final 关键字防止 Override
特别注意下 setUserVisibleHint 这个方法是不保证在生命周期内的
到这里就结束了么? 我也差点以为结束了, 结果又发现一个问题, 也是不保留活动引起的(或者说使用了很长一段时间, 中间只用 home 键切出)
问题描述: 打开若干个页面后, 按 home 键切回桌面, 再进入后, 右滑返回动画异常
这个问题的原因是切回桌面的时候 MainActivity 被销毁了, 再次进入后又重新创建, 但其他几个 activity 并没有重新创建 (推测是由于 MainActivity 的 singleTask 属性), 而右滑返回的动画是依赖于 ActivityStack (手动维护) 的, 很明显 MainActivity 从 Stack 的栈底被加到栈顶了, 造成了动画的异常
解决方法也比较简单, 新增一个叫做
ActivityStackBottom
的接口让 MainActivity 去实现, 然后代码如下:
- @Override
- public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
- if (activity instanceof ActivityStackBottom) {
- mActivityStack.add(0, activity);
- } else {
- mActivityStack.add(activity);
- }
- }
这样就确保 MainActivity 永远在栈底了
后话
到这里, 目前发现的问题也就解决了, 但恐怕其他地方还有不少问题, 总结下:
Android 自己的规则是很重要的东西, 对生命周期函数的理解不仅仅在顺序和交互上
一个问题可能会牵涉到另一个问题, 解决问题的过程很像唐人街探案的案中案, 不要轻易地认为事情到这里就结束了
在无法复现的时候多想想代码本身的问题, 但也不要过分纠结在代码中, 从代码问题追溯到具体场景(比如解决恢复问题并不是完善恢复逻辑, 而是先思考需要恢复的场景)
好了不说了, 要去解决另一个棘手的问题了, 今天的重构任务要完不成了, 但 Never 哭哭~~
来源: https://juejin.im/post/5a8fc3a5f265da4e983f25a5