去年我寫過一篇文章《Android 中不該該使用 Enum 嗎?》,若是你沒有看過這篇文章,我能夠簡單爲你介紹一下,在這篇文章中,我向你們說明了,「Android 中不該該使用 Enum 」 這句話的歷史緣由,以及在現階段,咱們到底可不可使用 Enum,以及在 Kotlin 中的替代方案。java
今天咱們繼續來聊聊 Enum,而且說說當 Enum 和 When 一塊兒使用時,爲何會有隱藏開銷,以及如何避免這種隱藏開銷。數組
這是一段很是簡單和常見的代碼,給你三秒中思考一下,並如實回答如下問題:你以爲這段代碼存在隱藏開銷嗎? 這是反編譯後的代碼,咱們能夠看到當咱們使用 when 來作判斷的時候,編譯器爲咱們生成了一個類 MainActivity WhenMappings 中聲明瞭和 Enum 長度相等的 Int 數組 $EnumSwitchMapping EnumSwitchMapping$0 數組中以 Enum 的 ordinal 爲索引,按順序存放這整數。那麼 Enum 中的 ordinal 表示什麼?安全
根據文檔的定義,ordinal 表示每個值在 Enum 中定義的位置,且 ordinal 從 0 開始。看到這裏咱們應該就明白了編譯器是如何處理這段代碼的了,它將 Enum 中的值與生成的數組 $EnumSwitchMapping$0 作了一個映射,以此來實現 when 邏輯的判斷。那麼開銷在哪裏? markdown
當我在另外一個類 MainFragment 中,須要作 Enum 邏輯判斷的代碼,與第一段 MainActivity 中的代碼徹底一致,可是在反編譯這個文件後,發現了不同的地方。 編譯器爲咱們生成了新的 MainFragment$WhenMappings 以此來支持在 MainFragment 中的 when 邏輯判斷。也就是說,只要咱們在某一個地方使用 Enum 和 when 的時候,編譯器都會爲咱們生成一個新的類 XXX$WhenMappings 來輔助實現 when 邏輯的處理。app
若是你有看過《深刻理解 Java 虛擬機》這本書的話,在第七章虛擬機類加載機制中有介紹:oop
在 Java 語言裏面,類型的加載、連接和初始化過程都是在程序運行期間完成的。優化
同時還有spa
若是類沒有進行過初始化,則須要先觸發其初始化。code
至此咱們終於發現來 Enum 和 When 的隱藏開銷在哪裏:orm
若是你在許多地方都有 Enum 和 When 配合使用,那麼編譯器會爲咱們生成無數的 XXXEnumSwitchMapping$0 數組,在運行時執行這些代碼的時候,會由於每個類的加載和實例化增長時間開銷,固然因爲增長了新的類,一樣也會增長最終的到的二進制文件的大小。
那麼形成這樣的緣由是爲何?
咱們先來看看 XXXEnumSwitchMappingWhenMappings 的初始化方法裏,這裏就不貼了),而後作了比較操做。
因此能夠看出 XXXWhenMappings 來作比較。
在查資料時,我找到到 jakewharton 的一篇文章中寫着這樣一句話
The switch map indirection created by javac is useful when the enum may be recompiled separately from the callers.
即當 Enum 和其被調用方能夠分別編譯時,javac 建立的這個臨時變量是很是有用的。也就是說,當咱們用 javac 編譯 Enum 和 MainActivity 這兩個文件時,編譯器會將此時 Enum 的值存入一個臨時變量中,並保存在 MainActivity 的調用堆棧中,當你若是單獨修改了 Enum 類,並只編譯了 Enum 時,MainActivity 的堆棧中仍然保持的是先前 Enum 的值。
這在我看來是一個出於安全性的考慮。
這裏我就要把 jakewharton 說的另外一句話分享給你們:
Android applications are packaged as a single unit, so the indirection is nothing but wasted binary size and runtime overhead.
意思是 Android 是總體編譯的,根本不會存在上面說的分別編譯的問題,因此編譯器引入的這個臨時變量徹底是畫蛇添足,只會形成二進制文件的增大和運行時的開銷。
至此咱們就知道了形成這個開銷的緣由,原來是編譯器的鍋,那麼如何避免這個開銷呢?
那就是使用 minifyEnabled true 開啓混淆
當咱們開啓混淆的時候 R8 編譯器會爲咱們移除這段沒必要要的臨時變量,下圖是開啓混淆後的字節碼堆棧。
能夠看到 XXX$WhenMappings 這個臨時變量不見了,這樣就消除了這個由編譯器產生的隱藏開銷。可是須要注意的是,若是你是用的是 Android Studio 3.6 如下,使用 kotlin 編寫 Enum 和 when 時即便開啓了混淆,可能仍會有這個隱藏開銷存在,由於 java 與 Kotlin 生成的這個臨時變量的命名規則不一樣,3.6 以前版本的 R8,並無針對此作優化,因此只有 3.6 以後的版本纔會消除這個隱藏開銷。
今天這期推送就到這裏,記得關注【Android|Kotlin】!