Stark Wong 的個人開發網站
 


 此頁面:更新於 2019 年 5 月 2 日 12 時 04 分 09 秒,頁面處理需時 0.0011 秒
 網站內容版權所有(C)Stark Wong。頁面(不包括檔案)可自由連結。網站系統版本 1.90-AngularJSBase (2015/9/27)
 
網站地圖

Notice: Undefined variable: row in /var/www/html/blog.inc.php on line 22
請小心為你的 Android Layout 改名,否則可能會出現奇怪的問題

在我們所開發的程式中,其中一個是以 aar 方式封裝播放器的介面,再加上第三方的 SDK (同樣以 aar 方式) 亦有播放介面,然後一併交由另一個團隊進行整合,一直都沒有問題。然而就在一個比較大的播放器介面更新時,我們改動過一些 Layout XML 並將重用的部份分離出一些新的 Layout XML,結果出現一個非常奇怪的問題:

  • 我們有一個自己寫的程式可以測試介面 aar 以及第三方播放 SDK 的功能,用那個程式測試時沒有問題
  • 對方團隊那邊執行我們的測試程式時也沒有發現問題
  • 對方團隊在將所有 aar 整合後,發現第三方播放器 SDK 開啟時發生 NullPointerException

初步分析

當時我們覺得很奇怪,在 Android Studio 的反編譯功能協助下,我們發現 NullPointerException 的原因是前面的 findViewById() 傳回 Null 值,但既然該播放器 SDK 單獨使用並無問題,而 findViewById() 指向的 ID 又確實存在 (否則應該無法編譯),所以肯定可以排除是第三方播放器 SDK 的問題。那樣的話我們只能在該次介面更新的範圍進行測試,最後發現把重用的 Layout XML 刪除就沒有問題,然後就一直到現在都再沒問題。

進階測試

我一直對這個解決方法都有疑問,直至最近想起,Android 上無論是 Java 還是 Kotlin 都有 package 的概念 (也就是只要大家的 package 不同,同樣名稱的源碼檔案其實是不同的類,只要匯入時使用正確的 package 名就能使用正確的類),至於 XML 呢?於是我就寫了一個小程式來測試這個問題。

測試程式可以在 https://github.com/starkwong/AndroidLayoutCollison 裡找到。

測試程式的原理很簡單,首先有一個程式庫 library1 內含 MainActivity,而它會使用 R.layout.activity_main (內含 ID 為 R.id.textview1 的 TextView),然後主程式也有 MainActivity 和 R.layout.activity_main (內含 ID 為 R.id.textview2 的 TextView)。library1 的 MainActivity 裡另加了修改 R.id.textview2 文字的語句,正常來說是完全沒問題的。然而當執行這個程式時,嘗試開啟 library1.MainActivity 的就會發生 NullPointerException:

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.mergetest/com.example.library1.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference

這正正就是當時我們所遇到的問題!很明顯地當 Android SDK 進行資源合併時,可同時存在的 XML 內容可以合併 (例如是 strings.xml,colors.xml 等,而且由於裡面的 ID 會轉化成數字 ID 並對應到有區分 package 的 R 類,所以並不會有同名問題,除非是同一個 package 裡有兩個資源目錄結構有相同的 xml 時則會在編譯時報錯),但同名的 Layout XML 由於不能合併,Android SDK 現在的處理似乎只會使用其中一個,而我們遇到的問題估計是 build.gradle 中兩個 aar 的匯入次序不同所致。

衍生行為

再確認這個行為後,我突然又想到…既然我可以取代別人程式裡的 Layout XML,那麼我可以在不修改或 Extend 別人 Activity 的情況下修改顯示的內容或邏輯嗎?

於是我在上面的測試程式上再加入了 library2,裡面的 MainActivity 會使用 R.layout.activity_library (內含 ID 為 R.id.textview3 的 TextView),然後在整個 Layout XML 複製到主程式,並將 Root View 的 ConstraintLayout 改成我們自己的繼承版本 HijackConstraintLayout,並在裡面修改 R.id.textview3 的文字。

當執行測試程式時,我發現我的想法是完全正確,可以經由取代 Layout XML 來修改其他程式庫中 Activity 的行為,就算是把裡面按鈕的 OnClickListener 換掉也很簡單。

結論

從以上兩個測試,可以歸納出以下的結論:

  1. 不同套件間的同名 Layout XML,Android SDK 只會使用其中一個
  2. 但沒有使用的 Layout XML 裡面的 ID 仍然會被加到 R 類,所以不會編譯失敗
  3. 當第三方套件使用被替換的 Layout XML 後,引用該 Layout XML 不存在的 View 就會導致程式錯誤
  4. 第三方套件為免出現 Layout XML 被意外替換的問題,應該使用非常用的名稱
  5. 不同套件間的同名 ID 具有相同的資源編號,所以可以對第三方 Activity 進行 Layout XML 替換與邏輯修改

撰寫於:2022/1/23 17:08:53 / 回應已關閉
正在讀取回響內容...
其他內容請回到主頁