Termux手机环境Java自动化构建:Shell脚本实战指南
2026/9/3 7:01:16 网站建设 项目流程

最近在手机上折腾 Java 开发环境时,我发现自己反复在“打开终端、敲编译命令、再敲运行命令”的流程里打转。虽然手机上的 Termux 已经能装 OpenJDK,但每次手动敲一串javacjava命令,既容易打错路径,又浪费时间。于是我把这系列操作整理成了一个自动化构建脚本,让手机也能像电脑上执行 Maven 打包一样,一键完成编译、运行和日志输出。

这篇文章会从环境搭建开始,讲清楚如何在 Android 手机上用 Termux 安装 Java 开发环境,再逐步拆解一个 Shell 构建脚本的编写思路,最后给出完整的脚本代码和常见问题排查方法。适合想用手机写 Java、练习算法、跑小型工具项目的开发者,也适合想理解自动化构建脚本原理的初学者。

1. 为什么要用手机构建 Java 项目

1.1 手机端 Java 开发的真实场景

很多人听到“手机写 Java”第一反应是“屏幕太小、操作不便”,但在某些场景下,手机终端确实能发挥作用。

比如通勤路上想验证一个算法思路,或者临时要看某个 Java 类的运行结果,又或者手边没有电脑但急需跑一段代码。手机上的 Termux 提供了完整的 Linux 终端环境,安装 OpenJDK 后就能执行javac编译和java运行命令,本质上和电脑上的命令行操作是一样的。

还有一个实用场景是学习 Java 基础语法。初学者在手机上编写简单的类、测试分支循环、观察异常输出,完全不需要打开重量级 IDE。配合脚本自动化构建,整个体验会接近“输入文件名、自动编译、自动运行”。

1.2 自动构建脚本解决了什么问题

手动执行构建命令的问题在于:

  • 每次都要输入完整的包路径和输出目录。
  • 编译错误和运行输出混在一起,肉眼排查效率低。
  • 项目文件一多,javac的参数就会变得很长。
  • 手机虚拟键盘输入特殊字符和长命令容易出错。

把这些命令写进 Shell 脚本后,代码的编译、清理、运行、日志记录都变成固定流程。脚本内部可以检查 JDK 是否安装、自动创建输出目录、编译所有源码、运行主类,并把标准输出和错误输出分开记录。

从工程角度来看,这也是一种“构建脚本思维”。即使以后回到电脑上开发,理解这种“环境检测 -> 编译 -> 运行 -> 日志收集”的流程,对学习 Maven、Gradle 也有帮助。

1.3 技术边界说明

本文以 Android 手机上的 Termux 环境为例,Java 版本使用 OpenJDK 17。如果你用的是 iOS 手机,或者想在 Windows 平板上操作,思路类似但终端环境和命令会有所不同。本文不涉及在手机上运行 Spring Boot 等大型框架的场景,专注解决“轻量级 Java 项目的自动化构建”。

2. 环境准备与版本说明

2.1 硬件和系统要求

准备一部 Android 手机,系统版本尽量在 Android 8 以上,这样可以获得较好的 Termux 兼容性。需要确保手机有足够的存储空间,OpenJDK 完整安装后大约会占用几百 MB 空间。

2.2 安装 Termux

Termux 是 Android 上的终端模拟器,它提供了一个 Linux 环境,可以通过pkg包管理器安装软件。安装方式很简单:

打开 Termux 后,先执行软件源更新:

pkg update pkg upgrade

这个步骤会同步软件包索引,并升级已有的基础组件。首次执行可能需要几分钟,取决于网络状况。

2.3 安装 OpenJDK

Termux 软件源中提供了 OpenJDK 17 的安装包,执行以下命令安装:

pkg search openjdk pkg install openjdk-17

安装完成后,验证 Java 环境是否正常:

java -version javac -version

预期输出会显示类似 openjdk 17 的版本信息,具体版本号以你安装时软件仓库中的实际版本为准。如果提示command not found,说明安装失败或者 PATH 没有正确配置,可以重新执行安装命令。

2.4 项目目录规划

为了让脚本更通用,我们约定一个统一的目录结构:

~/ └── java-projects/ └── calculator/ ├── src/ │ └── com/ │ └── example/ │ └── Main.java ├── out/ # 编译输出目录,由脚本自动创建 ├── logs/ # 运行日志目录,由脚本自动创建 └── build.sh # 构建脚本

实际操作中,你可以把calculator换成自己的项目名,把com.example换成自己的包名。脚本设计成通过变量控制项目和主类名称,方便复用。

3. Shell 脚本核心语法拆解

在编写完整构建脚本之前,先梳理实现这个脚本需要用到的 Shell 知识点。这些语法都很基础,但组合起来就能实现一个功能完整的自动化工具。

3.1 shebang 与脚本执行权限

脚本第一行通常是:

#!/data/data/com.termux/files/usr/bin/bash

注意 Termux 中 bash 的路径和普通 Linux 发行版不一样。也可以写成更通用的方式,但 Termux 默认的 bash 路径就是上述这个。

创建脚本后,需要给脚本添加执行权限:

chmod +x build.sh

以后就可以通过./build.sh直接执行,也可以使用bash build.sh的方式执行,后者不依赖执行权限。

3.2 变量与用户输入

脚本中可以使用变量保存项目名、主类名等固定值:

PROJECT_NAME="calculator" MAIN_CLASS="com.example.Main"

为了让脚本更灵活,可以允许用户通过参数覆盖默认主类。使用$1$2获取命令行参数:

if [ -n "$1" ]; then MAIN_CLASS=$1 fi

这里的-n "$1"表示判断第一个参数是否非空。

3.3 命令存在性检测

在脚本编译之前,检查javajavac是否可用是很有必要的。使用command -v可以判断命令是否存在:

if ! command -v java >/dev/null 2>&1; then echo "错误:未检测到 java,请先执行 pkg install openjdk-17" exit 1 fi

command -v java如果找到了命令,会返回命令路径,否则返回空。加上>/dev/null 2>&1是为了屏蔽命令的额外输出。

3.4 目录自动创建

编译输出目录和日志目录不一定每次存在,使用mkdir -p可以递归创建目录,并且目录已存在时不报错:

mkdir -p out logs

-p参数是关键,它让命令在目录已存在时静默跳过。

3.5 编译命令与 classpath

编译所有 Java 源码文件时,使用通配符收集src目录下所有.java文件:

find src -name "*.java" > sources.txt javac -encoding UTF-8 -d out @sources.txt

这里使用@sources.txt的方式,把要编译的文件列表从一个文本文件中读取,避免命令行参数过长。-d out指定编译生成的.class文件输出目录。-encoding UTF-8指定源码编码,避免中文注释或字符串乱码。

3.6 运行 Java 程序

编译完成后,运行主类:

java -cp out "$MAIN_CLASS"

-cp out表示 classpath 为out目录,MAIN_CLASS是完整类名(含包名)。如果需要向 Java 程序传递参数,可以在脚本中把额外的参数追加到 java 命令后面。

3.7 日志收集与退出码处理

手机上的终端输出不容易回看,把运行日志写入文件是一个好习惯:

java -cp out "$MAIN_CLASS" "$@" 2>&1 | tee logs/run_$(date +%Y%m%d_%H%M%S).log

tee命令会同时把输出显示在终端并写入文件。2>&1把标准错误重定向到标准输出,让编译或运行时的异常信息也能被记录。

$?变量保存上一条命令的退出码,如果 Java 程序非正常退出,脚本可以据此返回非零状态,方便上层调用者判断构建是否成功。

3.8 清理编译产物

在重新构建之前,清空旧的out目录可以避免残留类文件带来的问题:

rm -rf out

这个命令要慎重,所以脚本中会把out变量化,并提示用户当前清理的是哪个目录。

4. 完整实战:手写一个自动化构建脚本

4.1 创建示例 Java 项目

我们先创建一个简单的“手机端计算器”程序,用于验证脚本的有效性。这个程序从命令行接收两个数字和运算符,输出计算结果。

~下创建项目目录:

mkdir -p ~/java-projects/calculator/src/com/example cd ~/java-projects/calculator

创建src/com/example/Main.java文件,内容如下:

// 文件路径:src/com/example/Main.java package com.example; public class Main { public static void main(String[] args) { if (args.length < 3) { System.out.println("用法: java com.example.Main <数字1> <运算符> <数字2>"); System.out.println("示例: java com.example.Main 10 + 5"); return; } try { double num1 = Double.parseDouble(args[0]); String operator = args[1]; double num2 = Double.parseDouble(args[2]); double result = 0; boolean valid = true; switch (operator) { case "+" -> result = num1 + num2; case "-" -> result = num1 - num2; case "x", "X", "*" -> result = num1 * num2; case "/" -> { if (num2 == 0) { System.out.println("错误:除数不能为 0"); valid = false; } else { result = num1 / num2; } } default -> { System.out.println("错误:不支持的运算符 " + operator); valid = false; } } if (valid) { System.out.println(num1 + " " + operator + " " + num2 + " = " + result); } } catch (NumberFormatException e) { System.out.println("错误:请输入有效的数字"); } } }

这个程序用到了switch箭头语法,这是 Java 14 之后引入的增强 switch 写法,OpenJDK 17 完全支持。它的作用是判断运算符并执行对应计算。程序只有一个主类,结构清晰,适合脚本验证。

4.2 编写构建脚本

在项目根目录创建build.sh

#!/data/data/com.termux/files/usr/bin/bash # ============================================= # 项目根目录构建脚本(适配 Termux 环境) # 功能:清理 -> 编译 -> 运行 -> 记录日志 # ============================================= # 固定配置 PROJECT_NAME="calculator" SOURCE_DIR="src" OUTPUT_DIR="out" LOG_DIR="logs" MAIN_CLASS="com.example.Main" # 自动切换到当前脚本所在目录 cd "$(dirname "$0")" || exit 1 # 打印分隔线函数 print_line() { echo "-------------------------------------------" } # 检查 Java 环境 check_java() { echo "[1/4] 检查 Java 环境..." if ! command -v java >/dev/null 2>&1; then echo "错误:未检测到 java,请执行:pkg install openjdk-17" exit 1 fi if ! command -v javac >/dev/null 2>&1; then echo "错误:未检测到 javac,请执行:pkg install openjdk-17" exit 1 fi echo "Java 环境正常:" java -version print_line } # 清理旧的编译产物 clean_output() { echo "[2/4] 清理输出目录..." if [ -d "$OUTPUT_DIR" ]; then rm -rf "$OUTPUT_DIR" echo "已删除 $OUTPUT_DIR" else echo "$OUTPUT_DIR 不存在,无需清理" fi mkdir -p "$OUTPUT_DIR" "$LOG_DIR" echo "已创建 $OUTPUT_DIR 和 $LOG_DIR" print_line } # 编译源码 compile_project() { echo "[3/4] 开始编译 Java 源码..." find "$SOURCE_DIR" -name "*.java" > sources.txt if [ ! -s sources.txt ]; then echo "错误:在 $SOURCE_DIR 下没有找到 .java 文件" exit 1 fi javac -encoding UTF-8 -d "$OUTPUT_DIR" @sources.txt if [ $? -ne 0 ]; then echo "编译失败,请查看上方错误信息" exit 1 fi echo "编译成功,class 文件已输出到 $OUTPUT_DIR" print_line } # 运行主类 run_project() { echo "[4/4] 运行主类 $MAIN_CLASS" if [ ! -d "$OUTPUT_DIR" ]; then echo "错误:输出目录不存在,请先编译" exit 1 fi LOG_FILE="$LOG_DIR/run_$(date +%Y%m%d_%H%M%S).log" java -cp "$OUTPUT_DIR" "$MAIN_CLASS" "$@" echo "程序退出码: $?" echo "运行日志: $LOG_FILE" print_line } # ============ 脚本入口 ============ echo "开始构建项目:$PROJECT_NAME" print_line check_java clean_output compile_project run_project "$@" echo "全部操作完成"

把上面的脚本内容保存到build.sh后,添加执行权限:

chmod +x build.sh

4.3 运行脚本

先不带参数运行,查看程序是否正确输出用法提示:

./build.sh

预期终端输出类似:

开始构建项目:calculator ------------------------------------------- [1/4] 检查 Java 环境... Java 环境正常: openjdk version "17.0.x" ... [2/4] 清理输出目录... out 和 logs 已准备好 [3/4] 开始编译 Java 源码... 编译成功,class 文件已输出到 out [4/4] 运行主类 com.example.Main 用法: java com.example.Main <数字1> <运算符> <数字2> 示例: java com.example.Main 10 + 5 程序退出码: 0 运行日志: logs/run_20250601_120000.log ------------------------------------------- 全部操作完成

再带参数验证计算功能:

./build.sh 10 + 5

预期输出:

10 + 5 = 15.0

乘法测试:

./build.sh 7 x 8

预期输出:

7 x 8 = 56.0

4.4 检查生成的日志文件

运行完成后,查看日志目录:

ls -lh logs/

你会看到类似run_20250601_120000.log的文件。用cat查看内容:

cat logs/run_20250601_120000.log

日志中记录了程序的标准输出。这种日志方式非常适合稍后回溯程序运行结果,尤其是算法练习或批量测试时。

5. 核心实现原理与扩展方向

5.1find@sources.txt编译方式

脚本中使用了这样一个组合操作:

find "$SOURCE_DIR" -name "*.java" > sources.txt javac -encoding UTF-8 -d "$OUTPUT_DIR" @sources.txt

find会递归查找src目录下所有.java文件,并把文件列表写入sources.txtjavac@参数表示从文件中读取参数,这样做的优势是不用手动维护一个长长的编译列表,新添加的类文件会自动包含进来。

需要注意,如果sources.txt中保存的是相对路径,脚本在cd到项目根目录后执行find,相对路径仍然有效。

5.2java -cp out与类路径的关系

-cp-classpath的缩写,指定 JVM 在运行时搜索类的路径。Java 的包名com.example会映射到文件系统目录out/com/example/Main.class。如果不指定-cp,JVM 默认使用当前目录.,如果当前目录不是out,就找不到编译产物。这个细节在手动敲命令时特别容易踩坑,脚本化之后就自动规避了。

5.3 脚本的自动切换目录设计

脚本开头的一行:

cd "$(dirname "$0")" || exit 1

作用是把当前工作目录切换到脚本所在目录。这样无论你从哪个目录执行脚本,编译输出和日志都会落在项目根目录里,不会出现“脚本不知道从哪里找源码”的问题。

dirname "$0"获取脚本所在路径,$0是脚本自身的路径,|| exit 1表示如果切换失败就退出。

5.4 扩展为多 Java 文件项目

上面的示例只有一个Main.java,实际项目往往包含多个类。你只需要在src目录下继续添加.java文件,脚本的find逻辑会自动拾取。比如添加一个Calculator.java

// 文件路径:src/com/example/Calculator.java package com.example; public class Calculator { public static double add(double a, double b) { return a + b; } }

再修改Main.java调用它,脚本完全不需要改动,重新执行./build.sh即可完成编译和运行。这正是自动化脚本相比手动命令的核心价值:项目结构变化时,构建入口保持稳定。

5.5 扩展为 Maven 或 Gradle 托管

当项目规模继续扩大,依赖第三方库时,Shell 脚本的源码级编译就会变得吃力。此时可以考虑在 Termux 中安装 Maven:

pkg install maven

然后在项目根目录创建pom.xml,用 Maven 管理依赖和构建。但基础构建脚本的价值仍然存在:你可以编写一个build.sh,内部调用 Maven 命令并把日志输出重定向到文件。构建脚本作为统一入口,内部实现可以随时替换。

6. 常见问题与排查思路

6.1 问题速查表

问题现象常见原因解决思路
执行脚本提示command not found脚本没有执行权限,或 shebang 路径错误先执行chmod +x build.sh,再尝试bash build.sh
javac: command not foundOpenJDK 未安装或安装不完整重新执行pkg install openjdk-17,然后javac -version验证
编译时报编码错误源码包含中文,未指定编码javac命令中加上-encoding UTF-8,并确保文件实际保存为 UTF-8 编码
错误: 找不到或无法加载主类classpath 配置错误,或类名写错检查out目录的包结构,确认MAIN_CLASS包含完整包名
运行结果出现乱码终端字符集不一致在 Termux 中执行export LANG=en_US.UTF-8,或直接在脚本中设置 LANG
OutOfMemoryError: Insufficient Memory手机内存不足或 JVM 启动参数太小java命令中增加堆内存参数,如-Xmx256m,同时关闭其他后台进程
脚本中rm -rf误删文件路径变量未正确设置脚本中所有路径必须基于变量,并在清理前打印当前删除路径
每次重新编译后旧类还存在没有清理out目录确认clean_output函数执行成功,查看输出日志确认“已删除 out”
日志文件没有写入日志目录未创建检查mkdir -p "$LOG_DIR"是否执行,日志路径是否在运行前完成创建

6.2 排查步骤清单

遇到脚本运行异常时,按以下顺序排查更高效:

  1. 首先检查 Java 环境:执行java -versionjavac -version
  2. 检查脚本语法:执行bash -n build.sh,该命令只检查语法不运行脚本。
  3. 开启调试模式:执行bash -x build.sh,会显示每一条命令的实际执行结果,便于定位哪一步出错。
  4. 检查项目路径:确认脚本中的SOURCE_DIROUTPUT_DIR与实际目录一致。
  5. 检查日志文件:如果脚本已经把错误输出写入了日志,直接查看日志内容。

6.3 Termux 环境特有的坑

Termux 的存储目录与 Android 内部存储存在映射关系。如果项目放在 Android 公共存储目录(如/sdcard),可能遇到权限问题。建议把项目放在 Termux 的私有目录下,也就是$HOME中。如果需要访问共享文件,可以执行:

termux-setup-storage

授权后,存储路径通常在~/storage/shared下。但性能上,Termux 私有目录读写更快,项目源码优先放这里。

7. 最佳实践与工程建议

7.1 路径变量化,禁止硬编码

脚本中所有路径都通过变量引用,例如SOURCE_DIROUTPUT_DIRLOG_DIR。如果需要重新定位项目目录,只需要修改变量即可。避免在脚本中直接写出绝对路径,否则更换设备或目录结构一变,脚本就无法工作。

7.2 每次构建前清理旧产物

增量编译有时会留下旧 class 文件,导致运行结果不是最新代码。项目规模不大时,建议每次构建前执行rm -rf out,从零编译,保证“构建结果可复现”。这个原则和 CI/CD 中 clean build 的思路一致。

7.3 加入环境自检逻辑

脚本开头检查javajavac是否可用是个好习惯。尤其是换了手机或者重新安装 Termux 后,环境中可能没有 Java。提前检测并给出安装提示,比等编译时报command not found更友好。

7.4 合理记录退出码

在关键步骤后检查$?并决定是否继续执行,是自动化脚本可靠性的关键。比如编译失败时立刻退出,不执行运行步骤,避免拿着旧 class 文件去运行导致误判。

7.5 输出设计尽量直观

脚本输出采用类似[1/4][2/4]的步骤编号,让使用者在手机上也能看清当前执行到了哪一步。每个步骤完成后打印分隔线,长时间编译时也能通过终端输出判断是否卡住。

7.6 结合定时任务与自动构建

Termux 支持cronie定时任务,结合构建脚本后可以实现“每天自动编译并运行测试”。安装和启用方式:

pkg install cronie crond

然后使用crontab -e配置定时任务,例如每天早上八点执行一次构建脚本,并把结果写入日志。这种方式非常适合轻量级的数据处理或示例程序定时跑批。

7.7 使用 Git 管理项目源码

手机上的项目同样建议用 Git 做版本管理。在项目目录中执行:

git init git add . git commit -m "初始化手机构建脚本"

这样即使脚本或代码改坏了,也能快速回退到历史版本。Termux 中安装 Git 的命令是pkg install git

7.8 安全注意事项

脚本中涉及rm -rf清理操作,生产环境中必须谨慎。本文示例中清理的目录是编译输出目录,相对安全。但如果你把脚本改造成通用工具,务必要校验变量值是否为空、是否指向项目根目录之外,防止误删。更稳妥的做法是在清理前打印“即将删除目录”并等待用户确认。

另外,不要在脚本中硬编码密码、Token 等敏感信息。如果 Java 程序需要访问 API 密钥,建议通过环境变量传入,脚本留空让用户每次手动填写。

7.9 性能优化思路

手机性能有限,如果项目包含大量 Java 源文件,编译会比较耗时。可以只编译修改过的文件,但复杂度和正确性都会提升,对学习项目来说收益不大。更有效的优化是运行 Java 程序时增加 JVM 堆内存参数:

java -Xmx256m -cp "$OUTPUT_DIR" "$MAIN_CLASS" "$@"

-Xmx256m表示 JVM 最大堆内存为 256 MB,具体数值根据手机内存调整。如果手机内存较紧张,可以适当调低,例如-Xmx128m

8. 从脚本构建到工程化思维

当你习惯了用脚本完成编译和运行,下一阶段可以学习更规范的构建工具。

如果你希望接触 Android 开发,可以了解 Gradle 在 Android 项目中的角色;如果你希望做后端服务,可以学习 Maven 的生命周期管理。但无论用哪种工具,核心思想都包含几个固定阶段:

  • 清理旧产物
  • 获取依赖
  • 编译源码
  • 运行测试
  • 输出产物
  • 记录日志

本文的 Shell 脚本本质上就是这些阶段的轻量实现。理解它可以帮你更快理解 Maven 的cleancompiletestpackage等命令背后的逻辑。

手机上敲 Java 虽然不如电脑高效,但作为一种随时可用的“轻量开发环境”,配合自动化脚本,足以支撑 Java 语法学习、算法练习和简单工具开发。建议你先把上面的脚本复制到自己的手机项目里,改成自己的包名和主类名,跑通一次后再逐步添加新功能。遇到脚本报错的时候,优先用bash -x build.sh查看执行细节,这比凭空猜测更有效。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询