Android 枚举:生成带有 R8 的 AAR 二进制文件并在客户端应用程序中抛出 "Function invocation 'name()'" 异常
Android Enum: generate AAR binary with R8 on and throws "Function invocation 'name()'" exception in client app
这是一个非常罕见的案例,我已经尝试用谷歌搜索,但不幸的是没有结果。
基本上我想创建一个库模块并从中构建一个 aar 二进制文件以供不同的客户端应用程序共享。但是当我在库模块中创建一个枚举 class 时发生了一件奇怪的事情:
当 R8 minifyEnabled 关闭时,生成的 aar 文件可以导入到客户端应用程序并正确编译。但是当 minifyEnabled 打开时,它开始抛出调用枚举“.name”的错误 属性:
Function invocation 'name()' expected
更多详情如下:
在库模块中
ScaleTyp.kt:
enum class ScaleType constructor(private val text: String) {
LARGE("large"),
SMALL("small");
override fun toString(): String {
return text
}
}
build.gradle:
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
minSdkVersion 19
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
debug {
testCoverageEnabled = false
minifyEnabled true
proguardFiles 'proguard-rules.pro'
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
...
在应用模块中:
place generated aar in libs folder
MainActivity.kt,这里是调用.name时报错的地方,在kotlin中应该是完全合法的。
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.mylibrary.ScaleType
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
ScaleType.LARGE.name //compile failed
}
}
build.gradle 在应用程序模块中
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.example.myapplication"
minSdkVersion 19
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
testCoverageEnabled = false
minifyEnabled false
shrinkResources false
proguardFiles 'proguard-rules.pro'
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
...
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.0'
implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: [])
...
}
深入研究 AAR 二进制文件后,我观察到当 lib 模块中的 minifyEnabled 关闭时,ScaleType class 被转换为
public final enum class ScaleType private constructor(text: kotlin.String) : kotlin.Enum<com.example.mylibrary.ScaleType> {
因此在客户端应用程序中这绝对没问题,因为这只是调用名称的 kotlin 方式。
ScaleType.LARGE.name //compile pass
但是当 minifyEnabled 为真时,它被转换为
public final enum com/example/mylibrary/ScaleType extends java/lang/Enum
然后在客户端应用程序中,因为它将 ScaleType 视为 java 枚举,它会期望开发人员调用
ScaleType.LARGE.name()
instead of
ScaleType.LARGE.name // compile failed
我也可以确认将库模块作为源导入不会有这个问题,因为编译器已将所有 kotlin classes 统一翻译为 java class。它们以这种方式保持一致,所以它没有这个 kotlin 到 java 冲突。
这下麻烦大了。对于直接导入库模块的应用,调用“.name”即可。但是对于使用aar binary的应用程序,他们必须调用“.name()”,更痛苦的是你在调试lib模块时仍然必须使用“.name”
如有任何帮助,我们将不胜感激。
如果你想尝试,你可以找到示例代码here
Kotlin 编译器使用 class 文件中的 Kotlin 元数据来确定什么是 Kotlin 属性。因此,如果不维护 Kotlin 元数据,Kotlin 编译器将无法判断 name
是 属性 并将其视为 Java 代码,因此会坚持要求您调用 name()
方法。
R8 在 AGP 4.1 beta3 中添加了对维护和重写 Kotlin 元数据的支持。因此,如果您更新到该版本并将以下内容放入您的库收缩器配置文件中,希望这应该可以解决您的问题。
# Keep kotlin.Metadata annotations to maintain metadata on kept items.
-keepattributes RuntimeVisibleAnnotations
-keep class kotlin.Metadata { *; }
这是一个非常罕见的案例,我已经尝试用谷歌搜索,但不幸的是没有结果。
基本上我想创建一个库模块并从中构建一个 aar 二进制文件以供不同的客户端应用程序共享。但是当我在库模块中创建一个枚举 class 时发生了一件奇怪的事情:
当 R8 minifyEnabled 关闭时,生成的 aar 文件可以导入到客户端应用程序并正确编译。但是当 minifyEnabled 打开时,它开始抛出调用枚举“.name”的错误 属性:
Function invocation 'name()' expected
更多详情如下:
在库模块中
ScaleTyp.kt:
enum class ScaleType constructor(private val text: String) {
LARGE("large"),
SMALL("small");
override fun toString(): String {
return text
}
}
build.gradle:
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
minSdkVersion 19
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
debug {
testCoverageEnabled = false
minifyEnabled true
proguardFiles 'proguard-rules.pro'
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
...
在应用模块中:
place generated aar in libs folder
MainActivity.kt,这里是调用.name时报错的地方,在kotlin中应该是完全合法的。
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.mylibrary.ScaleType
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
ScaleType.LARGE.name //compile failed
}
}
build.gradle 在应用程序模块中
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.example.myapplication"
minSdkVersion 19
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
testCoverageEnabled = false
minifyEnabled false
shrinkResources false
proguardFiles 'proguard-rules.pro'
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
...
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.0'
implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: [])
...
}
深入研究 AAR 二进制文件后,我观察到当 lib 模块中的 minifyEnabled 关闭时,ScaleType class 被转换为
public final enum class ScaleType private constructor(text: kotlin.String) : kotlin.Enum<com.example.mylibrary.ScaleType> {
因此在客户端应用程序中这绝对没问题,因为这只是调用名称的 kotlin 方式。
ScaleType.LARGE.name //compile pass
但是当 minifyEnabled 为真时,它被转换为
public final enum com/example/mylibrary/ScaleType extends java/lang/Enum
然后在客户端应用程序中,因为它将 ScaleType 视为 java 枚举,它会期望开发人员调用
ScaleType.LARGE.name()
instead of
ScaleType.LARGE.name // compile failed
我也可以确认将库模块作为源导入不会有这个问题,因为编译器已将所有 kotlin classes 统一翻译为 java class。它们以这种方式保持一致,所以它没有这个 kotlin 到 java 冲突。
这下麻烦大了。对于直接导入库模块的应用,调用“.name”即可。但是对于使用aar binary的应用程序,他们必须调用“.name()”,更痛苦的是你在调试lib模块时仍然必须使用“.name”
如有任何帮助,我们将不胜感激。
如果你想尝试,你可以找到示例代码here
Kotlin 编译器使用 class 文件中的 Kotlin 元数据来确定什么是 Kotlin 属性。因此,如果不维护 Kotlin 元数据,Kotlin 编译器将无法判断 name
是 属性 并将其视为 Java 代码,因此会坚持要求您调用 name()
方法。
R8 在 AGP 4.1 beta3 中添加了对维护和重写 Kotlin 元数据的支持。因此,如果您更新到该版本并将以下内容放入您的库收缩器配置文件中,希望这应该可以解决您的问题。
# Keep kotlin.Metadata annotations to maintain metadata on kept items.
-keepattributes RuntimeVisibleAnnotations
-keep class kotlin.Metadata { *; }