Android调用Termux执行命令
前言
Termux 是安一款开源的安卓终端模拟器,支持 apt 包管理器,功能强大。
本文尝试利用 Termux 的 RunCommandService 调用 Termux 执行命令。基于此原理,可以快速开发一些结合终端的应用程序。
一、基本原理
自 Termux
0.95
版起,第三方应用程序可以通过向RunCommandService
发送 intent 或成为termux-tasker
插件客户端的插件,在 Termux 应用程序上下文中运行命令。
根据参考文档 1,我们选择使用比较基础的 RunCommandService
途径来调用 Termux 执行命令。为进一步了解原理,我们不使用封装的 termux-shared
库来调用,而是直接构造 RUNCOMMAND Intent
来调用。
二、配置
(1) Termux 配置
打开 Termux,修改 ~/.termux/termux.properities
文件,添加一行:
allow-external-apps = true
即允许外部应用调用。
另外,对于安卓 >=10
,需要给 Termux 启用关联应用权限,否则将不允许第三方应用直接后台启动 Termux,而是当 Termux 在后台运行时才能调用。
启用关联应用权限:应用信息 -> 应用 -> Termux -> 权限 -> 关联应用。
(2) 第三方应用配置
首先需要在 AndroidManifest.xml
文件中请求 com.termux.permission.RUN_COMMAND
权限,并在代码中给 RunCommandService
发送 Intent
。
AndroidManifest.xml
权限配置:
<uses-permission android:name="com.termux.permission.RUN_COMMAND"/>
Java 代码中调用:
intent.setClassName("com.termux", "com.termux.app.RunCommandService");
intent.setAction("com.termux.RUN_COMMAND");
intent.putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/top");
intent.putExtra("com.termux.RUN_COMMAND_ARGUMENTS", new String[]{"-n", "5"});
intent.putExtra("com.termux.RUN_COMMAND_WORKDIR", "/data/data/com.termux/files/home");
intent.putExtra("com.termux.RUN_COMMAND_BACKGROUND", false);
intent.putExtra("com.termux.RUN_COMMAND_SESSION_ACTION", "0");
startService(intent);
此处调用了 top
命令,相关参数:
com.termux.RUN_COMMAND_PATH
:调用的 ELF 文件;com.termux.RUN_COMMAND_ARGUMENTS
:调用命令参数;com.termux.RUN_COMMAND_WORKDIR
:工作目录;com.termux.RUN_COMAND_BACKGROUND
:是否后台运行。如果为true
则不会进入 Termux Activity;com.termux.RUN_COMMAND_SESSION_ACTION
:会话动作。
三、实例:C 语言编译器
笔者基于这一方案实现 C 语言编译器,好处是编译器不用自己集成 gcc
工具,而是直接调用 Termux,能大大降低安装包体积,同时不必自己编译 gcc
工具,减小维护难度。
应用有一个简单的文本编辑器,用以编写 C 语言源代码,并具备文件 IO 操作。编译/运行功能则通过相关 Intent 调用 Termux 执行,传入 gcc 编译命令及执行命令。
核心代码:
public void run() {
Intent intent = new Intent();
intent.setClassName("com.termux", "com.termux.app.RunCommandService");
intent.setAction("com.termux.RUN_COMMAND");
intent.putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/bash");
intent.putExtra("com.termux.RUN_COMMAND_RUNNER", "app-shell");
intent.putExtra("com.termux.RUN_COMMAND_ARGUMENTS", new String[]{"-c", "gcc \""+escape(PATH_C)+"\" -lm -Wall -o $TMPDIR/m && $TMPDIR/m && echo -n \"\nPress any key to exit...\" && read"});
intent.putExtra("com.termux.RUN_COMMAND_WORKDIR", "/data/data/com.termux/files/home");
intent.putExtra("com.termux.RUN_COMMAND_BACKGROUND", false);
intent.putExtra("com.termux.RUN_COMMAND_SESSION_ACTION", "0");
startService(intent);
}
注意一些实现细节:
- 为了让编译和运行一起执行,这里调用的 ELF 文件是
bash
,然后通过bash
执行一段bash
代码实现; - 为了让编译成功后才能执行代码,这里用
&&
运算符连接,其在 Bash 中的意义是:当前一个指令正常运行时(主程序返回值为 0)才执行后面的指令。即 gcc 编译成功后才执行代码; PATH_C
就是源代码文件的完整路径。为了防止文件路径中出现如空格、双引号、$
等特殊符号被bash
解析或干扰gcc
参数,这里定义了一个escape()
反转义函数,就是字符转义的逆过程,比如将\
转为\\
,将"
转为\"
;- 编译时生成临时 ELF 文件,保存在
$TMPDIR
中,这样当关闭 Termux 应用后临时文件将被自动清除; - 调用 Termux 的程序执行完成后,如果正常执行(主函数返回值为 0),则会立即退出。为了能看到函数执行的结果,这里再用
&&
符号连接了一段指令,实现当程序执行完毕时提示Press any key to exit...
,同时等待用户输入。
笔者的开源项目:TermuC - github.com/RainbowC0。