最近在做 Taskwarrior 的安卓端,根据文档的推荐决定直接运行二进制文件,而非自行解析数据。众所周知,安卓的存储根目录(/storage/emulated/*)是没有执行权限的,所以通常会将可执行文件放到应用的私有目录/data/data/PACKAGE_NAME/files。但是在 API 29 之后,谷歌移除了应用主目录的执行权限,虽然我经常怒喷谷歌,但是对于这个改动我觉得是比较合理的,然而谷歌只是提了一嘴说建议把可执行文件打包到安装包里面,具体怎么操作却不舍得多讲。被迫使用谷歌解决谷歌留下来的烂摊子。

可执行权限

首先把要执行的二进制文件放到libs/对应ABI/中,并且文件名格式必须为libxxx.so,否则 release 中不会打包进去,这是非常没有道理的设定,谷歌也知道这不合理,但就是不改

然后在AndroidManifest.xml中将android:extractNativeLibs设为true

随后在build.gradle中设置一番,最后结果如下(略过了一些不重要的部分):

android {

    defaultConfig {
        targetSdk 32

        ndk {
            abiFilters 'arm64-v8a', 'x86'
        }

        ......
    }


    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["lib*.so"])
    ......
}

至此,(理论上)你已经可以像这样运行binary了:

val libName = "libtask.so"
val nativeLibsDir: String = MyApplication.appContext.applicationInfo.nativeLibraryDir
val libPath = "$nativeLibsDir/$libName"
shell.run("$libPath --version") // 此处使用了 ktsh

依赖

然而,像上面那样直接运行大概率会跳出来一堆error while loading shared libraries,所以要用ldd看下有哪些依赖,把它们丢进安装包里,然后设置LD_LIBRARY_PATH

依赖中很有可能有libnettle.so.8这种文件,而libs目录是不接受这种文件名的此时有两种选择:

  1. 自行把它们解压私有目录里,然后自行判断abi,显然这是个比较糟糕的解决方案。
  2. 使用patchelf手动将依赖的名字改成libnettle.so,然后放到libs里面,这样做就无需自行解压、判断架构了。

另外,如果遇到类似如下报错:

terminating with uncaught exception of type std::__ndk1::basic_string<char, std::__ndk1::char_traits<char>, std::__ndk1::allocator<char>

有可能是程序自动使用了错误的共享库:

# 报错:
$ ldd `which libtask.so`
libc++_shared.so => /system/lib64/libc++_shared.so (0x7f0b396000)
......

# 以 root 用户正常运行:
$ ldd `which libtask.so`
libc++_shared.so => /data/user/0/com.senventise.taskroid/files/libc++_shared.so (0x7450b5a000)
......

手动复制一份过去就行了,这方面我也不太懂,有可能说得不太准确,欢迎指正。

参考

java native interface - Android Q execute binary file on release build - Stack Overflow
What path to put executable to run on Android 29? - Stack Overflow
Android can’t execute process for Android API 29 (Android 10) from /lib/ - Stack Overflow
android - Running binary files - Stack Overflow
libs - How do I embed a native code library in an Android app (SDK 29+)? - Stack Overflow

Android — 關於Native的一些STL問題 - JLin - Medium