分屏模式是 Android 7.0 新增的功能,在手机上表现为两个 App 上下分开,同时显示在前台。但同一时间只能有一个 App 响应用户交互,用户能够点击切换可交互的 App。java
进入和离开分屏模式时,Activity 会触发重建流程,保证在当前窗口尺寸中正确显示,简要的生命周期输出以下:git
(找了两个以前的demo项目作测试,新建的空Activity,仅输出一些生命周期回调的日志)github
进入分屏模式时:web
分屏模式下的焦点切换: post
离开分屏模式(将其中一个App拖动成全屏):测试
进入和离开分屏模式的时候都触发了Activity的重建流程,调试
EditText 在进出分屏模式的时候须要保存已输入内容并从新 setText,若是内容发生了变化,那应该是「存」或「取」的过程当中发生的问题,能够从 onSaveInstanceState 和 onCreate 开始查起。日志
整个 Activity 虽然看起来不太复杂,但涉及多处数据修改,业务逻辑并不简单,代码量也不算小。因为 bug 只涉及两个文本输入框,应该不须要阅读所有代码,NoteEditor 中重写了 onSaveInstanceState,也有处理 onCreate 的参数,第一步就是输出保存的和读取到的数据。cdn
(logcat 的截图丢了,调试代码已经删除,不想再写一遍了,onSaveInstanceState 存的值和 onCreate 中取到的值都是正确的)xml
有点疑惑,出入的数据竟然没有问题,但页面上确实出现了误差。搜索一下 FieldEditText 对象,发现使用的位置有点多,不适合逐个排查。(实际上仍是作了一部分排查,但效果很差)
因而把思路转移到 FieldEditText 内部,若是经过调用 setText 的方式修改了文案,那能够重写 setText,经过在其中抛出异常的方式获取方法调用栈,从而快速定位问题。EditText 的 setText 方法有两个,只有一个参数的 setText 是 final 的,但内部仍是调用到了两个参数的 setText。重写 setText 并加入异常代码:
由于setText会被屡次调用,此处作了一个判断,我在 Front 的输入框输入 "front",Back 中输入 "back",按照 bug 描述,当 Front 输入框被赋值成 "back" 时就是产生 bug 的那次调用。运行起来:
竟然不在 NoteEditor 里…怪不得以前的排查数据都没出现错误。看来问题出在 EditText 内部。那么问题变复杂了,若是只写一个 EditText,整个 Activity 重建时自动恢复的数据不会错误,错误的数据是哪里来的呢?这就须要研究 View 的保存和恢复机制了。
先不看源码,View 对象在 Activity 重建先后必然经历的回收和新建,在两个不直接相关的对象中传递数据,一定须要一个能链接两者的标识。NoteEditor 的代码中两个 EditText 是经过同一个 xml 文件建立的,结合 bug 的现象,初步猜想是经过 View 的 id 保存和恢复数据,因此保存时第二个 EditText 的 text 覆盖了第一个 EditText 的 text。
上源码验证猜测:
EditText 并未重写 onSaveInstanceState,直接跳到 TextView:
在须要保存的状况下,会保存 text 相关的内容,没发现能做为标识的值。再向上看一个 super。
保存的值中出现了 mAutofillViewId。这个 id 是为了统一用户手动设置(好比xml中指定)的 id 和自动生成的 id,是一个 int 变量。
而后是取值恢复,onRestoreInstanceState 的参数是以前保存的数据,那么调用 onRestoreInstanceState 的地方须要获取一个 state,根据以前的经验(保存是在 View.java 中作的),应该看 View 类中调用 onRestoreInstanceState 的地方。
再找 mID 的赋值处,发现其中之一是获取 xml 中的 id 属性:
能够确认,该 bug 是因为复用 xml 文件致使的 EditText 的 id 相同,在 onSaveInstanceState 时发生了数据的覆盖,恢复数据时两个 EditText 就变成了一样的内容。
解决方式应该须要自定义数据的保存和恢复,但这些内容 NoteEditor 已经处理过了,因此只须要把 FieldEditText 中的 onSaveInstanceState 禁用掉就没问题了。(禁用 onRestoreInstanceState 也行)
而后自测一下就能够提交PR了~
春节快乐