Flutter release build 启动即崩溃,如何配置 R8?
结论
先把 isMinifyEnabled 设为 false 做一次对照测试。如果 release 包不再崩溃,问题大概率出在 R8 压缩或混淆,而非 Impeller。
关闭 R8 可以暂时避免崩溃。长期处理时,建议按以下步骤操作:
- 通过
adb logcat找到被删除或改名的类。 - 为受影响的插件添加精确的 ProGuard/R8 keep 规则。
- 重新启用
isMinifyEnabled = true。 - 使用与正式发布配置完全一致的 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
启动应用后,重点搜索以下内容:
ClassNotFoundExceptionNoClassDefFoundErrorNoSuchMethodErrorUnsatisfiedLinkErrorMissingPluginExceptionGeneratedPluginRegistrantJNI DETECTED ERRORFATAL 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。
备注:内容仅供参考。
