kotlin_module文件的移除方法

背景是这边要求避免露出某些的标识。上周测试同学发现多了一个kotlin_module的文件,大约是这样的:

META-INF/xxxx_release.kotlin_module。

这个文件比较奇怪,demo中没有,但是我生成出来的正式包有。

检查原始代码

发现没有这个文件,但是反编译的代码中可以找到对应的代码

复制代码
@Metadata(d1 = {"}, d2 = {"Lorg/yeshen/model/Code$Close;", "", "", "(Ljava/lang/String;I)V", "AA", "BB", "CC", "xxxx_release"}, k = 1, mv = {2, 1, 0}, xi = ConstraintLayout.LayoutParams.Table.LAYOUT_CONSTRAINT_VERTICAL_CHAINSTYLE)

-> Metadata,d2中有对应的字符信息"xxxx_release"

-> META-INF/xxxx_release.kotlin_module 文件中的几个字符串反编译后,都可以在代码中找到对应信息

尝试excludes文件失败

尝试了在 android.packagingOptions.exclude 、android.packaging.resources.excludes 中配置了这个文件,发现还是有。

-> exclude 配置是对的,配置了之后,META-INF下其他文件都会消失,但是这个文件不会消失

-> 猜测是动态生成的,所以excludes不了。

定位demo项目不会有这个文件,但是正式包有这个文件的原因

-> 尝试对齐版本,定位是什么什么版本引入的问题。

-> 发现测试包不会有这个文件,但是正式包有,怀疑是混淆问题

-> 问了下ai,ai给了几个可疑的混淆规则,一一尝试之后,定位到是 -keep class kotlin.Metadata { *; } 之后,就会生成这个文件

-> 使用whyareyoukeeping方法,定位到是af-android-sdk-6.17.3引入的规则,逆向查看确实如此

总结

  1. appsflyer指定了-keep class kotlin.Metadata,这个规则下+混淆, -keep class kotlin.Metadata { *; } 告诉 R8 保留所有带 @Metadata 注解的类。 R8 解析这些类时,发现注解引用了 kotlin_module 文件,R8 将 kotlin_module 标记为强制保留,AGP 资源合并时,R8 的标记优先级高于 exclude 配置,最终 kotlin_module 被打包到 APK 中。
  2. 这个信息在运行时反射调用的时候会使用到,所以保留其实是合理的行为。

解决方法

  1. 修改 appsflyer 的包,把-keep class kotlin.Metadata的混淆规则去掉;或者直接去掉这个依赖。
  2. 避免写文件级别的常量(即把@Metadata 这些东西都去掉就好)也可以解决问题。
  3. 是修改模块名,避免模块名直接暴露特殊的信息。

AI 总结


Kotlin Module 文件详解

文件来源

META-INF/xxx_release.kotlin_module 文件是由 Kotlin 编译器自动生成的元数据文件,主要在以下情况产生:

1. 生成时机

kotlin 复制代码
// 当你的项目使用 Kotlin 编写代码时
// Kotlin 编译器会在编译过程中自动生成
  • 编译阶段 :Kotlin 编译器(kotlinc)在将 .kt 文件编译成 .class 文件时生成
  • 打包阶段 :最终会被打包到 AAR/JAR 文件的 META-INF 目录下
  • 命名规则 :通常格式为 <模块名>_<构建类型>.kotlin_module

2. 生成位置

复制代码
build/intermediates/compile_library_classes_jar/
    └── release/
        └── classes.jar
            └── META-INF/
                └── your_module_release.kotlin_module

文件作用

核心功能

  1. 存储 Kotlin 元数据
kotlin 复制代码
// 记录模块中的 Kotlin 特性信息:
- 顶层函数/属性
- 类型别名
- 扩展函数
- 内联函数
- 模块名称
  1. 反射支持
kotlin 复制代码
// 支持 Kotlin 反射获取模块信息
import kotlin.reflect.full.*

fun getModuleInfo() {
    // 可以通过反射获取模块中的顶层声明
    val kClass = MyClass::class
    kClass.memberFunctions
}
  1. 编译器优化
  • 帮助 Kotlin 编译器识别和优化跨模块调用
  • 内联函数的跨模块内联
  • 编译时类型检查

移除会有的问题

1. 反射功能受限

kotlin 复制代码
// ❌ 可能失败:无法正确识别顶层声明
val functions = MyClass::class.memberFunctions

// ❌ 可能失败:获取不到模块级别的元数据
kotlin.reflect.jvm.kotlinClass

2. 跨模块调用问题

kotlin 复制代码
// Module A (library)
// Utils.kt
inline fun debugLog(message: String) {
    if (BuildConfig.DEBUG) {
        Log.d("TAG", message)
    }
}

// Module B (app) - 依赖 Module A
// ⚠️ 如果 kotlin_module 被移除,内联优化可能失效
debugLog("test") // 可能无法正确内联

3. IDE 支持问题

  • 代码提示:IDE 可能无法正确提示顶层函数
  • 导航功能:跳转到声明可能失效
  • 重构支持:重命名等操作可能不准确

4. 多模块项目冲突

groovy 复制代码
// 如果多个模块有同名的 kotlin_module 文件
// 打包时可能会出现冲突

// build.gradle (可能需要的解决方案)
android {
    packagingOptions {
        // ⚠️ 不推荐移除
        exclude 'META-INF/*.kotlin_module'
    }
}

常见场景和建议

1. 文件冲突处理

groovy 复制代码
// build.gradle
android {
    packagingOptions {
        // 方案1:保留第一个(不推荐)
        pickFirst 'META-INF/*.kotlin_module'
        
        // 方案2:合并(推荐)
        merge 'META-INF/*.kotlin_module'
    }
}

2. ProGuard/R8 配置

proguard 复制代码
# proguard-rules.pro
# 保留 kotlin_module 文件
-keep class kotlin.Metadata { *; }
-keepattributes RuntimeVisibleAnnotations

3. 最佳实践

kotlin 复制代码
// ✅ 推荐:保留 kotlin_module 文件
// 它体积很小(通常几 KB),但提供重要功能

// ❌ 不推荐:移除 kotlin_module
// 除非你确定:
// 1. 不使用 Kotlin 反射
// 2. 没有跨模块的内联函数
// 3. 不需要 IDE 的完整支持

总结

方面 说明
生成方式 Kotlin 编译器自动生成
文件大小 通常几 KB,对 APK 大小影响很小
建议 保留,除非有特殊需求
移除风险 反射失效、内联优化受影响、IDE 支持受限

建议:不要移除 这个文件,它对应用运行时影响很小,但对 Kotlin 功能的完整性很重要。如果遇到冲突,使用 merge 策略而不是 exclude


Kotlin Module 与混淆的关系

1. 混淆确实是关键因素

典型配置差异

groovy 复制代码
// app/build.gradle
android {
    buildTypes {
        debug {
            minifyEnabled false              // ❌ 不混淆
            shrinkResources false            // ❌ 不压缩资源
        }
        
        release {
            minifyEnabled true               // ✅ 启用混淆
            shrinkResources true             // ✅ 启用资源压缩
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
        }
    }
}

2. 为什么会有这种差异

R8/ProGuard 的处理逻辑

bash 复制代码
Debug 构建流程:
Kotlin编译 → 生成kotlin_module → 直接打包 → APK
                                    ↓
                                可能被优化掉(空文件)

Release 构建流程:
Kotlin编译 → 生成kotlin_module → R8混淆 → 重新打包 → APK
                                    ↓
                                强制保留并合并

具体原因分析

groovy 复制代码
// Debug 模式
- 编译器生成 kotlin_module
- 但因为是空模块或无特殊声明
- Gradle 打包时可能跳过空文件
- 最终 APK 中没有该文件 ✅

// Release 模式(启用混淆)
- R8/ProGuard 会扫描所有 META-INF 文件
- kotlin_module 被视为元数据必须保留
- 即使是空文件也会被强制打包 ⚠️

3. 验证混淆是否是原因

实验方法

groovy 复制代码
// app/build.gradle
android {
    buildTypes {
        release {
            // 临时禁用混淆测试
            minifyEnabled false  // ← 改为 false
            shrinkResources false
        }
    }
}
bash 复制代码
# 重新构建 Release 包
./gradlew clean
./gradlew assembleRelease

# 检查 APK 内容
unzip -l app/build/outputs/apk/release/app-release.apk | grep kotlin_module

如果禁用混淆后 kotlin_module 消失了 → 确认是混淆导致的


4. ProGuard 规则的影响

默认规则会保留 kotlin_module

proguard 复制代码
# Android 默认的 proguard-android-optimize.txt 包含:

# 保留 Kotlin 元数据
-keep class kotlin.Metadata { *; }

# 保留注解
-keepattributes *Annotation*

# 这些规则会导致 R8 保留 kotlin_module 文件

自定义规则尝试排除

proguard 复制代码
# proguard-rules.pro

# ❌ 这个无效(R8 会忽略)
-dontwarn META-INF/*.kotlin_module

# ❌ 这个也无效
-dontnote META-INF/*.kotlin_module

# ✅ 需要在 Gradle 中配置

5. R8 的资源压缩行为

shrinkResources 的影响

groovy 复制代码
android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true  // ← 这个配置很关键
        }
    }
}
工作原理
xml 复制代码
<!-- R8 资源压缩流程 -->
1. 扫描代码引用的资源
2. 标记未使用的资源
3. 删除未引用的资源文件

<!-- kotlin_module 的特殊性 -->
- 不是标准资源文件
- 被视为"代码元数据"
- 默认保留(认为被代码引用)

6. 解决方案

方案 1: 在 R8 阶段排除(AGP 7.0+)

groovy 复制代码
// app/build.gradle
android {
    buildTypes {
        release {
            minifyEnabled true
            
            // AGP 7.0+ 新语法
            packaging {
                resources {
                    excludes += ['META-INF/*.kotlin_module']
                }
            }
        }
    }
}

方案 2: 后处理删除

groovy 复制代码
// app/build.gradle
android {
    // ...
}

// 在打包后删除文件
tasks.whenTaskAdded { task ->
    if (task.name == 'packageRelease') {
        task.doLast {
            def apkFile = file("$buildDir/outputs/apk/release/app-release.apk")
            if (apkFile.exists()) {
                // 使用 zip 命令删除
                exec {
                    commandLine 'zip', '-d', apkFile.absolutePath, 
                               'META-INF/*.kotlin_module'
                }
            }
        }
    }
}

方案 3: 自定义 R8 配置

groovy 复制代码
// app/build.gradle
android {
    buildTypes {
        release {
            minifyEnabled true
            
            // 自定义 R8 配置文件
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
                         'proguard-rules.pro',
                         'r8-custom.txt'  // 额外配置
        }
    }
}
proguard 复制代码
# r8-custom.txt
# R8 特定配置(实验性,不保证有效)
-assumenosideeffects class kotlin.Metadata {
    public *;
}

7. 为什么 Debug 没有此问题

Debug 模式的特殊性

groovy 复制代码
// Debug 配置通常是这样:
debug {
    minifyEnabled false
    shrinkResources false
    debuggable true
    
    // 因为没有混淆,Gradle 的打包策略不同:
    // - 只打包非空的有效文件
    // - 空 kotlin_module 被自动跳过
}

实际生成差异

bash 复制代码
# Debug 构建
build/intermediates/compile_library_classes_jar/debug/
    └── classes.jar
        └── META-INF/
            └── xxxx_debug.kotlin_module  # 可能存在但为空

# 打包到 APK 时
- Gradle 检测到文件为空或无有效内容
- 自动跳过该文件 ✅

# Release 构建
build/intermediates/compile_library_classes_jar/release/
    └── classes.jar
        └── META-INF/
            └── xxxx_release.kotlin_module

# R8 混淆后
- R8 扫描到 META-INF 文件
- 作为元数据强制保留
- 最终打包到 APK ⚠️

8. 验证结论的完整测试

bash 复制代码
# 1. 检查中间产物
./gradlew :app:compileReleaseKotlin
ls build/intermediates/compile_library_classes_jar/release/classes.jar
# 解压查看
unzip -l build/intermediates/.../classes.jar | grep kotlin_module

# 2. 对比 Debug 和 Release
./gradlew assembleDebug assembleRelease

# 3. 检查 APK 内容
unzip -l app/build/outputs/apk/debug/app-debug.apk | grep kotlin_module
unzip -l app/build/outputs/apk/release/app-release.apk | grep kotlin_module

# 4. 测试禁用混淆的 Release
# 修改 build.gradle: minifyEnabled false
./gradlew clean assembleRelease
unzip -l app-release.apk | grep kotlin_module

9. 总结

条件 Debug Release 原因
minifyEnabled = false 无文件 无文件 Gradle 跳过空文件
minifyEnabled = true - 有文件 R8 强制保留元数据 ⚠️

核心结论

  • 混淆是主要原因
  • R8 将 kotlin_module 视为必要的代码元数据
  • 即使文件为空,混淆后也会被保留
  • Debug 模式因为没有混淆,空文件被自动优化掉

建议

groovy 复制代码
// 如果确实要在 Release 中排除
android {
    packaging {
        resources {
            excludes += ['META-INF/*.kotlin_module']
        }
    }
}

R8 强制保留 kotlin_module 的源码分析

1. 核心原理概览

复制代码
kotlin_module 文件保留链路:

ProGuard规则 → R8解析 → 元数据扫描 → 资源合并 → 强制打包
     ↓            ↓          ↓            ↓          ↓
-keep Metadata  识别注解    关联文件    aapt2合并   APK输出

2. R8 源码分析

2.1 ProGuard 规则解析

java 复制代码
// R8 源码位置: 
// com/android/tools/r8/shaking/ProguardConfiguration.java

public class ProguardConfiguration {
    // 解析 -keep 规则
    public void parseKeepRule(String rule) {
        if (rule.contains("kotlin.Metadata")) {
            // 标记 Kotlin 元数据需要保留
            this.keepKotlinMetadata = true;
        }
    }
}

2.2 Kotlin Metadata 注解检测

java 复制代码
// R8 源码:
// com/android/tools/r8/kotlin/KotlinMetadataUtils.java

public class KotlinMetadataUtils {
    
    // Kotlin.Metadata 注解的签名
    private static final String KOTLIN_METADATA_DESC = 
        "Lkotlin/Metadata;";
    
    /**
     * 检查类是否包含 Kotlin 元数据注解
     */
    public static boolean hasKotlinMetadata(DexProgramClass clazz) {
        for (DexAnnotation annotation : clazz.annotations().annotations) {
            if (annotation.getAnnotationType()
                .toDescriptorString()
                .equals(KOTLIN_METADATA_DESC)) {
                return true;  // ← 发现 @Metadata 注解
            }
        }
        return false;
    }
    
    /**
     * 解析 Kotlin 元数据内容
     */
    public static KotlinMetadata parseMetadata(
        DexAnnotation annotation
    ) {
        // 读取 @Metadata 注解的字段
        // k: kind (1=class, 2=file facade, 3=synthetic class)
        // d1: 元数据二进制数据
        // d2: 字符串数据
        
        KotlinMetadataHeader header = 
            new KotlinMetadataHeader(annotation);
        
        if (header.getKind() == 2) {  // File facade
            // 这种类型需要 kotlin_module 文件支持
            return parseFileFacade(header);
        }
        
        return null;
    }
}

2.3 关联 kotlin_module 文件的逻辑

java 复制代码
// R8 源码:
// com/android/tools/r8/kotlin/KotlinModuleUtils.java

public class KotlinModuleUtils {
    
    /**
     * 扫描 META-INF 目录下的 kotlin_module 文件
     */
    public static void processKotlinModules(
        AppView<?> appView,
        ExecutorService executorService
    ) {
        // 1. 查找所有 kotlin_module 文件
        List<Resource> kotlinModules = 
            findKotlinModuleResources(appView);
        
        for (Resource resource : kotlinModules) {
            // 2. 解析 kotlin_module 内容
            KotlinModuleMetadata metadata = 
                parseKotlinModule(resource);
            
            // 3. 检查是否有对应的类被保留
            for (String className : metadata.getFileClasses()) {
                DexType type = 
                    appView.dexItemFactory().createType(className);
                
                if (appView.appInfo().definitionFor(type) != null) {
                    // 4. 如果类被保留,标记 kotlin_module 也要保留
                    markKotlinModuleAsLive(resource);
                    break;
                }
            }
        }
    }
    
    /**
     * 标记 kotlin_module 为必须保留
     */
    private static void markKotlinModuleAsLive(Resource resource) {
        // 将文件添加到最终的资源列表中
        resource.setKeep(true);
        
        // 记录日志
        Log.info("Keeping Kotlin module: " + resource.getName());
    }
}

3. AGP 的资源合并逻辑

3.1 Gradle 任务流程

kotlin 复制代码
// AGP 源码位置:
// com/android/build/gradle/internal/tasks/MergeJavaResourcesTask.kt

abstract class MergeJavaResourcesTask : NewIncrementalTask() {
    
    override fun doTaskAction() {
        // 合并所有 Java 资源(包括 META-INF)
        mergeJavaResources(
            inputs = inputs.get(),
            output = outputFile.get(),
            packagingOptions = packagingOptions.get()
        )
    }
    
    private fun mergeJavaResources(...) {
        val excludePatterns = packagingOptions.excludes
        
        for (input in inputs) {
            // 遍历所有资源文件
            input.forEach { entry ->
                val path = entry.name
                
                // 检查是否匹配排除规则
                if (shouldExclude(path, excludePatterns)) {
                    // ⚠️ 这里是关键:即使配置了 exclude
                    // 如果文件被 R8 标记为 keep,仍会保留
                    if (!entry.isMarkedAsKeep()) {
                        return@forEach  // 跳过
                    }
                }
                
                // 添加到输出
                output.addEntry(entry)
            }
        }
    }
}

3.2 R8 标记传递机制

java 复制代码
// AGP 源码:
// com/android/build/gradle/internal/dependency/R8ResourceShrinker.java

public class R8ResourceShrinker {
    
    /**
     * R8 shrinking 后的资源标记
     */
    public void markResourcesFromR8(
        File r8MappingFile,
        Set<String> usedResources
    ) {
        // 1. 读取 R8 的输出信息
        R8Output output = parseR8Output(r8MappingFile);
        
        // 2. 获取被保留的元数据文件
        Set<String> keptMetadata = output.getKeptMetadataFiles();
        
        // 3. 强制标记这些文件
        for (String metadataFile : keptMetadata) {
            if (metadataFile.endsWith(".kotlin_module")) {
                // ← 关键:即使配置了 exclude,这里会覆盖
                usedResources.add(metadataFile);
                
                Log.info("R8 marked as keep: " + metadataFile);
            }
        }
    }
}

4. 完整的执行流程

有 -keep kotlin.Metadata

有 exclude
已标记 keep
Kotlin 编译
生成 @Metadata 注解
生成 kotlin_module
R8 处理
检查 ProGuard 规则
扫描所有类
找到带 @Metadata 的类
解析注解内容
是否引用 kotlin_module?
标记 kotlin_module 为 keep
资源合并阶段
检查 exclude 规则
检查 R8 标记
忽略 exclude,强制保留
最终 APK 包含 kotlin_module


5. Kotlin Metadata 注解示例

5.1 生成的字节码

kotlin 复制代码
// 源代码: Utils.kt
package com.example

fun topLevelFunction() {
    println("test")
}
java 复制代码
// 反编译后的 UtilsKt.class

@Metadata(
    k = 2,  // ← FileFacade 类型
    d1 = {
        "\u0000\b\n\u0000\n\u0002\u0010\u0002\n\u0000\u001a\u0006\u0010\u0000\u001a\u00020\u0001"
    },
    d2 = {
        "topLevelFunction", "", "app_release"
    }  // ← 关联到 app_release.kotlin_module
)
public final class UtilsKt {
    public static final void topLevelFunction() {
        System.out.println("test");
    }
}

5.2 kotlin_module 文件内容

protobuf 复制代码
// META-INF/app_release.kotlin_module (二进制格式)

// 解析后的内容:
module {
  package_parts {
    package_fqname: "com.example"
    file_facades {
      name: "UtilsKt"  // ← 对应上面的类
    }
  }
}

6. R8 的判断逻辑源码

java 复制代码
// R8 核心判断逻辑
// com/android/tools/r8/shaking/Enqueuer.java

public class Enqueuer {
    
    private void processKotlinMetadataAnnotation(
        DexProgramClass clazz,
        DexAnnotation annotation
    ) {
        // 1. 解析 @Metadata 注解
        KotlinMetadata metadata = 
            KotlinMetadataUtils.parseMetadata(annotation);
        
        if (metadata == null) return;
        
        // 2. 检查 ProGuard 配置
        if (!proguardConfiguration.keepKotlinMetadata) {
            return;  // ← 如果没有 -keep kotlin.Metadata,直接返回
        }
        
        // 3. 如果是 FileFacade 类型
        if (metadata.isFileFacade()) {
            String moduleName = metadata.getModuleName();
            String moduleFile = "META-INF/" + moduleName + ".kotlin_module";
            
            // 4. 标记 kotlin_module 文件必须保留
            registerResourceToKeep(moduleFile);
            
            // 5. 记录原因(用于调试)
            keepReasons.put(
                moduleFile,
                "Referenced by Kotlin Metadata in " + clazz.type
            );
        }
    }
    
    /**
     * 注册需要保留的资源文件
     */
    private void registerResourceToKeep(String resourcePath) {
        // 添加到全局的 keep 集合
        this.resourcesToKeep.add(resourcePath);
        
        // 这个集合会传递给 AGP 的资源合并任务
        // 覆盖用户的 exclude 配置
    }
}

7. 为什么 exclude 配置无效

7.1 优先级机制

java 复制代码
// AGP 资源处理优先级
// com/android/build/gradle/internal/tasks/PackageAndroidArtifact.kt

class PackageAndroidArtifact {
    
    fun mergeResources() {
        val priority = listOf(
            ResourceSource.R8_KEEP,        // ← 最高优先级
            ResourceSource.AAPT2_COMPILED,
            ResourceSource.USER_CONFIG     // ← exclude 配置在这里
        )
        
        for (resource in allResources) {
            // 按优先级处理
            val action = determineAction(resource, priority)
            
            when (action) {
                Action.KEEP_BY_R8 -> {
                    // 即使配置了 exclude,也强制保留
                    output.add(resource)
                    Log.warn(
                        "Resource ${resource.name} kept by R8, " +
                        "ignoring exclude configuration"
                    )
                }
                Action.EXCLUDE -> {
                    // 仅当没有更高优先级标记时才排除
                    continue
                }
            }
        }
    }
}

7.2 调试方法

bash 复制代码
# 开启 R8 详细日志
./gradlew assembleRelease \
  -Pandroid.enableR8.fullMode=true \
  -Pandroid.r8.verbose=true \
  --info

# 查看 R8 保留的文件
grep "Keeping.*kotlin_module" build/outputs/logs/r8-*.log

示例输出

复制代码
[R8] Keeping resource: META-INF/app_release.kotlin_module
[R8] Reason: Referenced by @Metadata in com.example.UtilsKt
[R8] Keep rule: -keep class kotlin.Metadata { *; }

8. 解决方案

方案1:移除 Metadata 保留规则(推荐)

proguard 复制代码
# proguard-rules.pro

# ❌ 注释掉这行
# -keep class kotlin.Metadata { *; }

# ✅ 只保留必要的 Kotlin 内容
-keep class kotlin.** { *; }
-keep class kotlinx.** { *; }

# 如果需要反射,只保留特定包
-keep @kotlin.Metadata class com.example.** { *; }

方案2:在 R8 之后手动删除

groovy 复制代码
// app/build.gradle

android {
    applicationVariants.all { variant ->
        variant.packageApplicationProvider.get().doLast {
            // 在打包完成后删除 kotlin_module
            def apkFile = variant.outputs[0].outputFile
            
            exec {
                commandLine 'zip', '-d', apkFile.path, 
                    'META-INF/*_release.kotlin_module'
            }
        }
    }
}

方案3:修改 R8 配置

gradle 复制代码
// gradle.properties
android.enableR8.kotlinMetadataRemoval=true  # AGP 8.0+

9. 源码位置参考

复制代码
R8 源码仓库:
https://r8.googlesource.com/r8

关键文件:
- src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
- src/main/java/com/android/tools/r8/shaking/Enqueuer.java
- src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java

AGP 源码仓库:
https://android.googlesource.com/platform/tools/base/

关键文件:
- build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/MergeJavaResourcesTask.kt
- build-system/gradle-core/src/main/java/com/android/build/gradle/internal/dependency/R8ResourceShrinker.java

总结

核心原理

  1. -keep class kotlin.Metadata { *; } 告诉 R8 保留所有带 @Metadata 注解的类
  2. R8 解析这些类时,发现注解引用了 kotlin_module 文件
  3. R8 将 kotlin_module 标记为强制保留
  4. AGP 资源合并时,R8 的标记优先级高于 exclude 配置
  5. 最终 kotlin_module 被打包到 APK 中

关键点 :不是混淆"生成"了文件,而是混淆过程中 R8 强制保留了原本可能被优化掉的文件。

相关推荐
PoppyBu9 小时前
Ubuntu20.04版本上安装最新版本的scrcpy工具
android·ubuntu
执念、坚持9 小时前
Property Service源码分析
android
用户41659673693559 小时前
在 ViewPager2 + Fragment 架构中玩转 Jetpack Compose
android
GoldenPlayer9 小时前
Gradle脚本执行
android
用户74589002079549 小时前
Android进程模型基础
android
we1less10 小时前
[audio] Audio debug
android
Jomurphys10 小时前
AndroidStudio - TOML
android
有位神秘人10 小时前
Android最新动态权限申请工具
android
lxysbly10 小时前
psp模拟器安卓版下载汉化版2026
android
2501_9418227510 小时前
面向灰度发布与风险隔离的互联网系统演进策略与多语言工程实践分享方法论记录思考汇总稿件
android·java·人工智能