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 { *; }