通过JNI调用APK中打包的控制台程序遇权限问题,求解决方案及替代实现方式
看起来你踩中了Android沙箱权限和文件系统限制的坑,我来帮你拆解问题并给出可行的解决方案~
为什么你的chmod会失败?
你把test_run复制到了Documents/Download这类外部存储目录,从Android 10(API 29)开始,Scoped Storage机制严格限制了外部存储的文件权限:外部存储的文件只能进行读/写操作,不允许设置执行权限(chmod +x),这就是你遇到"Operate permission not allowed"的核心原因。
另外你的代码还有个隐藏bug:if (errno = chmod (cmd_str,i) < 0)这里的运算符优先级有问题——<的优先级比=高,所以实际是先判断chmod的返回值是否小于0,再把布尔值(0或1)赋值给errno,导致你无法正确获取chmod的错误码,得改成if ((errno = chmod(cmd_str, i)) < 0)。
正确的解决方案步骤
1. 把可执行文件复制到App私有目录
App的私有目录(比如/data/data/[你的包名]/files/)不受Scoped Storage限制,App拥有完全的权限,包括设置执行权限。在C++Builder里,你可以用TPath.GetHomePath() + "/files/"来获取这个路径(对应Java层的context.getFilesDir()),而不是用外部存储的DocumentsFolder。
2. 修正chmod代码并添加日志
替换你原来的chmod判断逻辑,同时用Android日志替代ShowMessage(JNI线程里调用GUI相关函数容易出问题):
extern "C" JNIEXPORT jstring JNICALL ExecuteCommand(JNIEnv *env, jstring cmd) { const char *cmd_str = env->GetStringUTFChars(cmd, NULL); char mode[] = "0755"; // 标准可执行权限 int i = strtol(mode, 0, 8); int err_num; // 修正运算符优先级问题 if ((err_num = chmod(cmd_str, i)) < 0) { char err_buf[256]; // 用线程安全的strerror_r获取错误信息 strerror_r(err_num, err_buf, sizeof(err_buf)); char buffer[512]; sprintf(buffer, "%s: chmod failed with mode %s - %d (%s)", cmd_str, mode, err_num, err_buf); // 输出到Android日志,方便调试 __android_log_print(ANDROID_LOG_ERROR, "JNI_EXEC", "%s", buffer); }
3. 安全执行控制台程序
你当前用的execvp会直接替换当前JNI线程的进程空间,导致后面的代码完全不会执行,甚至可能让App崩溃。推荐用popen来捕获程序输出,或者fork子进程执行:
// 用popen执行并捕获输出 FILE* fp = popen(cmd_str, "r"); if (!fp) { __android_log_print(ANDROID_LOG_ERROR, "JNI_EXEC", "popen failed: %s", strerror(errno)); env->ReleaseStringUTFChars(cmd, cmd_str); return env->NewStringUTF("Execution failed"); } char output[1024]; std::string result; while (fgets(output, sizeof(output), fp) != NULL) { result += output; } pclose(fp); env->ReleaseStringUTFChars(cmd, cmd_str); return env->NewStringUTF(result.c_str()); }
更推荐的替代实现方式
其实单独打包可执行文件是比较麻烦的方案,更符合Android NDK开发习惯的方式是:把test_run的代码编译成共享库(.so),然后直接通过JNI调用库中的函数,这样完全不需要处理文件复制、权限设置这些步骤,既高效又安全。
比如你可以把test_run的核心逻辑封装成一个函数:
// 在libtestrun.so中定义 extern "C" int run_test(int argc, char** argv) { // 原来test_run的核心代码 return 0; }
然后在你的JNI代码里直接调用这个函数,省去了所有文件操作的麻烦。
备注:内容来源于stack exchange,提问作者DarkSpy

