PAYATHON 2026

Flutter release build 启动即崩溃,如何配置 R8?

排障赵师傅

结论

先把 isMinifyEnabled 设为 false 做一次对照测试。如果 release 包不再崩溃,问题大概率出在 R8 压缩或混淆,而非 Impeller。

关闭 R8 可以暂时避免崩溃。长期处理时,建议按以下步骤操作:

  1. 通过 adb logcat 找到被删除或改名的类。
  2. 为受影响的插件添加精确的 ProGuard/R8 keep 规则。
  3. 重新启用 isMinifyEnabled = true。
  4. 使用与正式发布配置完全一致的 release 包验证。

ffmpeg_kit_flutter_new 和 just_audio 的不同版本或分支可能使用不同的 Android 包名,因此不存在适用于所有版本的固定规则。下面以常见命名空间为例,实际配置要以项目依赖中的类名和崩溃日志为准。

确认问题是否由 R8 引起

先在 android/app/build.gradle.kts 中暂时关闭代码压缩:

android {
    buildTypes {
        release {
            isMinifyEnabled = false
        }
    }
}

重新构建并安装 release 包:

flutter clean
flutter build apk --release
adb install -r build/app/outputs/flutter-apk/app-release.apk

如果关闭压缩后应用可以正常启动,R8 很可能删除或改名了由反射、JNI 或 Flutter 插件注册机制访问的类。

如果应用仍然崩溃,可以同时关闭资源压缩,排查资源裁剪问题:

android {
    buildTypes {
        release {
            isMinifyEnabled = false
            isShrinkResources = false
        }
    }
}

isShrinkResources = false 只控制资源裁剪。对于桥接类、JNI 类或反射类,主要需要检查的仍是 isMinifyEnabled。

排查 R8 时,不要把正式包改成 debug 签名。下面的配置只能用于本地临时测试,不应写入生产发布配置:

signingConfig = signingConfigs.getByName("debug")

获取实际的崩溃原因

release 包通常不会在 Flutter 调试控制台中直接显示异常,但 Android 系统日志一般会记录具体原因:

adb logcat -c
adb logcat AndroidRuntime:E flutter:E *:S

启动应用后,重点搜索以下内容:

  • ClassNotFoundException
  • NoClassDefFoundError
  • NoSuchMethodError
  • UnsatisfiedLinkError
  • MissingPluginException
  • GeneratedPluginRegistrant
  • JNI DETECTED ERROR
  • FATAL EXCEPTION

如果日志中出现了具体类名,先为这个类及其必要成员添加规则,不要直接对整个应用使用范围过大的 -keep。

启用 ProGuard 配置文件

确认 release 构建已经引用自定义规则文件:

android {
    buildTypes {
        release {
            isMinifyEnabled = true
            isShrinkResources = true

            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

Groovy DSL 项目可以使用:

android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true

            proguardFiles getDefaultProguardFile(
                'proguard-android-optimize.txt'
            ), 'proguard-rules.pro'
        }
    }
}

规则文件通常位于:

android/app/proguard-rules.pro

用于初步排查的 keep 规则

以下配置比较保守,适合先验证问题。应用稳定后,再根据 mapping.txt、usage.txt 和实际调用方式逐步缩小保留范围。

# 保留运行时注解、泛型签名和内部类信息。
-keepattributes RuntimeVisibleAnnotations,RuntimeInvisibleAnnotations
-keepattributes AnnotationDefault,Signature,InnerClasses,EnclosingMethod

# 保留 Flutter 自动生成的插件注册入口。
-keep class io.flutter.plugins.GeneratedPluginRegistrant { *; }

# 保留 FlutterPlugin 实现及其成员。
-keep class * implements io.flutter.embedding.engine.plugins.FlutterPlugin { *; }

# 保留包含 native 方法的类名和 native 方法签名。
# 适用于通过 JNI 按名称查找 Java/Kotlin 类或方法的原生库。
-keepclasseswithmembernames,includedescriptorclasses class * {
    native <methods>;
}

# ffmpeg-kit 常见 Android 命名空间。
# 如果当前 fork 使用其他包名,需要按实际 AAR 内容修改。
-keep class com.arthenica.ffmpegkit.** { *; }
-keep interface com.arthenica.ffmpegkit.** { *; }
-dontwarn com.arthenica.ffmpegkit.**

# just_audio 常见 Android 插件命名空间。
# 请先通过日志或依赖内容确认当前版本仍使用该命名空间。
-keep class com.ryanheise.just_audio.** { *; }
-keep interface com.ryanheise.just_audio.** { *; }

-dontwarn 只会隐藏警告,不会保留类,也无法修复 ClassNotFoundException。只有确认警告来自可选依赖,或者对应代码不会在当前平台执行时,才应添加这条规则。

确认插件的实际包名

不要根据 Flutter 包名直接推测 Android 命名空间。可以先检查 Gradle 缓存中的 AAR 或 JAR:

find ~/.gradle/caches -type f \
  \( -name "*.aar" -o -name "*.jar" \) \
  | grep -E "ffmpeg|just_audio"

查看 AAR 内容:

unzip -l path/to/library.aar

如需查看其中的类:

unzip -p path/to/library.aar classes.jar > /tmp/plugin-classes.jar
jar tf /tmp/plugin-classes.jar

按照输出中实际存在的包路径编写 -keep。例如,类路径是:

com/example/plugin/AudioPlugin.class

对应规则应写为:

-keep class com.example.plugin.** { *; }

根据 R8 输出缩小规则范围

release 构建完成后,可以检查类似以下目录中的 R8 输出:

build/app/outputs/mapping/release/

常见文件包括:

  • mapping.txt:混淆前后的类名映射。
  • usage.txt:被 R8 删除的类和成员。
  • seeds.txt:被 keep 规则保留的内容。
  • configuration.txt:最终合并的 R8 配置。

这些文件是否生成,取决于 Android Gradle Plugin 和项目配置。如果崩溃日志指向某个类,可以先在 usage.txt 中确认它是否被删除,再添加最小范围的规则:

-keep class com.example.plugin.RequiredBridge {
    public <init>(...);
    public *;
}

如果只需要保留通过反射调用的方法,还可以进一步收紧:

-keepclassmembers class com.example.plugin.RequiredBridge {
    public void methodUsedByReflection(...);
}

这样仍可保留 R8 对其他代码的压缩和优化。

检查插件自带的 consumer rules

规范发布的 Android 库通常会在 AAR 中附带 consumer ProGuard 规则,应用构建时会自动合并。如果某个 Flutter 插件只在启用 R8 后崩溃,可以检查以下情况:

  • 插件没有提供 consumer rules。
  • fork 更改了包名,但规则没有同步更新。
  • 插件新增了反射或 JNI 调用,却没有补充 keep 规则。
  • 当前使用的旧版本存在后来已经修复的 R8 兼容问题。
  • 自定义 Gradle 配置覆盖或遗漏了插件规则。

还需要检查当前依赖版本的 Android 源码、AAR 内容和随包规则。如果问题出在插件本身,直接为插件补充 consumer rules,通常比在每个应用中复制大范围的 -keep 更合适。

处理顺序

先用 isMinifyEnabled = false 验证问题是否与 R8 有关,再从 adb logcat 中取得具体异常和类名。确认包名后添加精确规则,然后恢复:

isMinifyEnabled = true
isShrinkResources = true

最后至少测试冷启动、音频播放、FFmpeg 初始化、前后台切换,以及不同 ABI 的安装包。应用能进入首页,并不代表所有通过 JNI 或反射调用的路径都得到了正确保留。

如果关闭 R8 后仍然崩溃,或者日志显示原生库加载失败、ABI 不匹配、权限或资源问题,就不要继续扩大 ProGuard 规则。此时应根据日志排查对应原因。只有日志指向渲染初始化时,才需要继续检查 Impeller。

备注:内容仅供参考。