Android 10+如何运行动态模块中打包为共享库的可执行文件?
问题背景
我开发的Android平台IDE集成了多套面向不同MCU的GCC工具链SDK,为绕开Google Play的AAB 200MB体积限制,将每个SDK单独打包为动态模块:把工具链的可执行文件重命名为共享库格式(例如xtensa-esp32-elf-g++改名为libesp32_xtensa-esp32-elf-g++.so),放在动态模块的jniLibs/arm64-v8a目录,其余文件作为资源打包。
原本在Android 10+系统中,主APK/AAB内的这类共享库可以通过创建符号链接到可执行目录正常运行,但动态模块安装后,文件路径为/data/user/0/%package%/files/splitcompat/7900/native-libraries/esp32_sdk.config.arm64_v8a/libesp32_xtensa-esp32-elf-g++.so,尝试在context.filesDir下创建符号链接并执行时,触发error=13,权限被拒绝异常。将文件复制到codeCacheDir、cacheDir、filesDir目录后执行,同样报错。主APK中放置同类文件则可正常运行,但会大幅增加包体积,且预加载用户可能不需要的SDK,不符合需求。测试覆盖Android 16模拟器及多台Android 10+实体设备。
问题原因
- 动态模块splitcompat目录下的原生库文件,系统未自动设置执行权限,而主APK的原生库在安装阶段会被系统处理权限,因此可正常执行。
- 复制文件到应用私有目录时,仅复制了文件内容,未手动设置可执行权限,导致文件无法被执行。
解决方案
1. 复制文件后手动设置可执行权限
优先选择codeCacheDir存放可执行文件,该目录是系统专为应用可执行文件设计的,权限管控更适配。复制完成后,通过代码设置文件的执行权限:
// 从动态模块获取源文件路径 String sourcePath = "/data/user/0/%package%/files/splitcompat/7900/native-libraries/esp32_sdk.config.arm64_v8a/libesp32_xtensa-esp32-elf-g++.so"; File sourceFile = new File(sourcePath); // 目标文件存放在codeCacheDir,恢复原可执行文件名 File targetFile = new File(context.getCodeCacheDir(), "xtensa-esp32-elf-g++"); // 复制文件内容 try (InputStream in = new FileInputStream(sourceFile); OutputStream out = new FileOutputStream(targetFile)) { byte[] buffer = new byte[1024]; int length; while ((length = in.read(buffer)) > 0) { out.write(buffer, 0, length); } } catch (IOException e) { e.printStackTrace(); } // 设置可执行权限 targetFile.setExecutable(true, false); // false表示仅应用所有者拥有执行权限,更安全 // 执行文件 try { Process process = new ProcessBuilder(targetFile.getAbsolutePath()) .redirectErrorStream(true) .start(); // 处理进程输出等逻辑 } catch (IOException e) { e.printStackTrace(); }
2. 避免使用符号链接
动态模块原文件本身无执行权限,符号链接仅指向原文件,无法解决权限问题。直接复制文件到可写目录并设置权限是更可靠的方案。
3. 动态模块打包配置优化
在动态模块的build.gradle中添加配置,防止系统自动压缩或剥离工具链文件的符号信息:
android { packagingOptions { doNotStrip "*/libesp32_xtensa-esp32-elf-g++.so" exclude "lib/arm64-v8a/libesp32_xtensa-esp32-elf-g++.so.gz" } }
内容的提问来源于stack exchange,提问作者4ntoine

