去年我写过一篇文章《Android 中不该该使用 Enum 吗?》,若是你没有看过这篇文章,我能够简单为你介绍一下,在这篇文章中,我向你们说明了,“Android 中不该该使用 Enum ” 这句话的历史缘由,以及在现阶段,咱们到底可不可使用 Enum,以及在 Kotlin 中的替代方案。java
今天咱们继续来聊聊 Enum,而且说说当 Enum 和 When 一块儿使用时,为何会有隐藏开销,以及如何避免这种隐藏开销。数组
那么 Enum 中的 ordinal 表示什么?安全
那么开销在哪里? app
也就是说,只要咱们在某一个地方使用 Enum 和 when 的时候,编译器都会为咱们生成一个新的类 XXX$WhenMappings 来辅助实现 when 逻辑的处理。优化
若是你有看过《深刻理解 Java 虚拟机》这本书的话,在第七章虚拟机类加载机制中有介绍:cdn
在 Java 语言里面,类型的加载、连接和初始化过程都是在程序运行期间完成的。blog
同时还有索引
若是类没有进行过初始化,则须要先触发其初始化。图片
至此咱们终于发现来 Enum 和 When 的隐藏开销在哪里:文档
若是你在许多地方都有 Enum 和 When 配合使用,那么编译器会为咱们生成无数的 XXXEnumSwitchMapping$0 数组,在运行时执行这些代码的时候,会由于每个类的加载和实例化增长时间开销,固然因为增长了新的类,一样也会增长最终的到的二进制文件的大小。
那么形成这样的缘由是为何?
咱们先来看看 XXXEnumSwitchMapping
WhenMappings 的初始化方法里,这里就不贴了),而后作了比较操做。
因此能够看出 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 编译器会为咱们移除这段没必要要的临时变量,下图是开启混淆后的字节码堆栈。
可是须要注意的是,若是你是用的是 Android Studio 3.6 如下,使用 kotlin 编写 Enum 和 when 时即便开启了混淆,可能仍会有这个隐藏开销存在,由于 java 与 Kotlin 生成的这个临时变量的命名规则不一样,3.6 以前版本的 R8,并无针对此作优化,因此只有 3.6 以后的版本才会消除这个隐藏开销。
今天这期推送就到这里,记得关注【Android|Kotlin】!