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);
}

注意一些实现细节:

  1. 为了让编译和运行一起执行,这里调用的 ELF 文件是 bash,然后通过 bash 执行一段 bash 代码实现;
  2. 为了让编译成功后才能执行代码,这里用 && 运算符连接,其在 Bash 中的意义是:当前一个指令正常运行时(主程序返回值为 0)才执行后面的指令。即 gcc 编译成功后才执行代码;
  3. PATH_C 就是源代码文件的完整路径。为了防止文件路径中出现如空格、双引号、$ 等特殊符号被 bash 解析或干扰 gcc 参数,这里定义了一个 escape() 反转义函数,就是字符转义的逆过程,比如将 \ 转为 \\,将 " 转为 \"
  4. 编译时生成临时 ELF 文件,保存在 $TMPDIR 中,这样当关闭 Termux 应用后临时文件将被自动清除;
  5. 调用 Termux 的程序执行完成后,如果正常执行(主函数返回值为 0),则会立即退出。为了能看到函数执行的结果,这里再用 && 符号连接了一段指令,实现当程序执行完毕时提示 Press any key to exit...,同时等待用户输入。

笔者的开源项目:TermuC - github.com/RainbowC0


参考文档

  1. RUNCOMMAND_Intent - github.com/termux/termux-app
posted @ 2024-03-07 20:00  RainbowC0  阅读(620)  评论(0编辑  收藏  举报