[bugfix實錄]分屏模式的生命週期和 EditText 的自動保存功能

bug 描述

bug 分析

分屏模式是 Android 7.0 新增的功能,在手機上表現爲兩個 App 上下分開,同時顯示在前臺。但同一時間只能有一個 App 響應用戶交互,用戶能夠點擊切換可交互的 App。java

進入和離開分屏模式時,Activity 會觸發重建流程,保證在當前窗口尺寸中正確顯示,簡要的生命週期輸出以下:git

(找了兩個以前的demo項目作測試,新建的空Activity,僅輸出一些生命週期回調的日誌)github

進入分屏模式時web

  • 一、全屏打開App1(webrtc)
  • 二、使用 RECENT 進入多任務切換頁面
  • 三、對App1啓用分屏模式,等待選擇分屏顯示的另外一個App
  • 四、點擊圖標啓動App2(post)

分屏模式下的焦點切換post

離開分屏模式(將其中一個App拖動成全屏):測試

進入和離開分屏模式的時候都觸發了Activity的重建流程,調試

EditText 在進出分屏模式的時候須要保存已輸入內容並從新 setText,若是內容發生了變化,那應該是「存」或「取」的過程當中發生的問題,能夠從 onSaveInstanceState 和 onCreate 開始查起。日誌

排查過程

  • 復現問題,找到對應代碼:NoteEditor(是一個Activity)

整個 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了~

技術總結

  1. 調查一個方法的調用時機,除了Ctrl+左鍵和全局搜索,還有經過拋出異常直接查看調用棧這種更直接的方法。(搜索代碼的方式有範圍限制,不夠全面)
  2. View 自身保存和恢復狀態時是依據 id (應該還有所屬 Activity)確認是否爲同一 View 的。複用 xml 時須要注意本身實現數據的保存和恢復。

最後

春節快樂

相關文章
相關標籤/搜索