Flutter Android应用Release模式下jniLibs无法提取至nativeLibDirectory
问题描述
我用Flutter开发了一款依赖jniLibs的Android应用,没用到ffi,而是通过Process.run直接从nativeLibDirectory调用.so库。Debug模式运行正常,但Release模式下会抛出Process异常,提示指定.so文件不存在。
解压发布的APK能看到所有库都在libs文件夹里,但安装后应用的nativeLibDirectory里找不到这些库(Debug模式下能正常提取)。
查资料知道这是Android 6引入的特性:原生库可以存在APK内不用解压,直接从APK加载,不需要复制到文件系统。我已经在AndroidManifest.xml里加了android:extractNativeLibs="true"但没用,也试过在build.gradle里加以下配置(包括组合配置),还是解决不了:
ndk { abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64' } // --- packagingOptions { jniLibs { useLegacyPackaging = true } } // --- sourceSets { main { jniLibs.srcDirs = ['libsrc/main/jniLibs'] } }
怎么强制Android把库提取到nativeLibDirectory,而不是只存在base.apk里?
解决方案
1. 修正AndroidManifest.xml里的extractNativeLibs配置
先检查application标签下的extractNativeLibs属性格式,别多套引号:
<application android:extractNativeLibs="true" ...>
之前如果写成"android:extractNativeLibs="true""这种格式,系统会识别不了这个属性,直接无效。
2. 在build.gradle里强制开启提取逻辑
在app/build.gradle的android块里加这些配置,确保打包时标记必须提取原生库:
android { ... defaultConfig { ... // 针对AGP 7.0+,明确传递提取参数到Manifest manifestPlaceholders = [extractNativeLibs: "true"] } packagingOptions { // 禁止剥离.so的调试符号,同时避免系统跳过提取 jniLibs { doNotStrip "**/*.so" } // 强制用旧版打包逻辑,保证.so会被提取到文件系统 jniLibs.useLegacyPackaging = true } }
doNotStrip防止.so被优化剥离,同时让系统不会因为“可直接加载”而跳过提取useLegacyPackaging强制使用传统的jniLibs打包方式,确保.so被放到能被提取的位置
3. 手动复制.so到可访问目录
如果上面的配置都没用,那就手动在应用启动时把APK里的.so复制到可读写目录(比如应用文档目录):
首先添加依赖到pubspec.yaml:
dependencies: archive: ^3.4.0 package_info_plus: ^4.0.0 path_provider: ^2.0.0
然后写提取逻辑:
import 'dart:io'; import 'package:archive/archive.dart'; import 'package:path_provider/path_provider.dart'; import 'package:package_info_plus/package_info_plus.dart'; Future<void> extractSoFiles() async { final packageInfo = await PackageInfo.fromPackageName(await getPackageName()); final apkPath = packageInfo.applicationInfo.sourceDir; final destDir = await getApplicationDocumentsDirectory(); final destPath = destDir.path; // 读取APK文件 final bytes = File(apkPath).readAsBytesSync(); final archive = ZipDecoder().decodeBytes(bytes); // 遍历并提取所有.so文件 for (final file in archive) { if (file.name.startsWith('lib/') && file.name.endsWith('.so')) { final fileName = file.name.split('/').last; final outputFile = File('$destPath/$fileName'); if (!outputFile.existsSync()) { outputFile.writeAsBytesSync(file.content); // 给.so添加执行权限 await Process.run('chmod', ['755', outputFile.path]); } } } }
在应用启动时调用这个方法,之后就从destPath目录调用.so文件就行。
4. 检查Flutter的ABI过滤配置
确保Flutter打包时没过滤掉需要的ABI,在app/build.gradle里确认ndk.abiFilters和Flutter的目标平台一致:
android { ... buildTypes { release { ... ndk { abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64' } } } }
同时在android/gradle.properties里添加:
flutter.targetPlatform=android-arm,android-arm64,android-x86,android-x64
内容的提问来源于stack exchange,提问作者Beer_belly 665

