前言
众所周知,Android 手机底层是 Linux 内核,这就给了这个话题折腾的机会。虽然 Java 官方没有发布 Android 版的 JDK,但是一直有 aarch64/armhf 版本的,而我们的 Android 手机一般也是这个架构。
准备工作
下面的步骤假定你的手机是 aarch64 架构,其他架构只需下载对应版本的文件。
- 了解一定的 Linux 和 Android 基础
- Patch ELF 下载地址 (aarch64-linux-android-patchelf)
- JDK aarch64 下载地址 (下载 Linux 平台 aarch64 架构的 JDK)
- glibc 动态链接库 下载地址 (下载 libc6_x.x-0ubuntu3_arm64.deb,解包提取lib/aarch64-linux-gnu 目录下的所有文件,或者从其他发行版镜像中提取都可以,如果不知道如何提取的话直接下载附件)
- 如果手机上没有 xxd realpath file unzip tar 等命令的话可以下载一个 busybox
附件:
glibc 动态运行库
安装
提示:下文的命令请不要直接复制粘贴,至少把版本号和路径改成你自己的。
这里通过 usb 调试的方式连接手机,方便直接在手机上执行命令,你也可以用其他方式,然后直接使用手机上的终端模拟器。
将文件拷贝到手机:
adb push OpenJDK8U-jdk_aarch64_linux_hotspot_8u312b07.tar.gz /sdcard/
adb push aarch64-linux-gnu.zip /sdcard/
adb push aarch64-linux-android-patchelf /sdcard/
使用 adb shell
或者打开终端模拟器:
cd /data/local/tmp # 不要装在 sdcard 下,那里无法赋予执行权限,如果使用终端模拟器,一般会有自己的家目录,或者直接进入 Android 给应用分配的独立空间中。
mv /sdcard/OpenJDK8U-jdk_aarch64_linux_hotspot_8u312b07.tar.gz ./
mv /sdcard/aarch64-linux-gnu.zip ./
mv /sdcard/aarch64-linux-android-patchelf ./patchelf
mkdir libs
unzip -c libs aarch64-linux-gnu.zip
tar -zxvf OpenJDK8U-jdk_aarch64_linux_hotspot_8u312b07.tar.gz
这时候执行 java 命令会出现奇怪的问题:
$ ./jdk8u312-b07/bin/java
/system/bin/sh: ./jdk8u312-b07/bin/java: No such file or directory
这是因为原本的 JDK 是为通用 Linux 发行版编译的,这些发行版一般会在 /lib 目录下有它需要的动态链接库文件,但是 Android 上没有,所以就报错找不到文件。可以使用 file 命令验证这一点。
$ file ./jdk8u312-b07/bin/java
./jdk8u312-b07/bin/java: ELF shared object, 64-bit LSB arm64, dynamic (/lib/ld-linux-aarch64.so.1), not stripped
这样的话可以下载这些动态运行库,然后修改 ELF 文件的链接位置就可以使用了。
chmod +x patchelf
./patchelf --set-interpreter /data/local/tmp/libs/ld-linux-aarch64.so.1 ./jdk8u312-b07/bin/java # 链接库必须是绝对路径
这个时候仍然是没办法执行的,会提示如下信息:
$ ./jdk8u312-b07/bin/java
./jdk8u312-b07/bin/java: error while loading shared libraries: libpthread.so.0: cannot open shared object file: No such file or directory
由于 Java 使用的运行库与 Android 不完全兼容,我们需要使用 GNU 标准的库,按照文档,可以通过 /etc/ld.so.conf
配置文件和 LD_LIBRARY_PATH
环境变量来指定搜索路径,但是由于 Android 文件系统的特殊性,我们只能使用环境变量的方式了。
$ export LD_LIBRARY_PATH=/data/local/tmp/libs
$ export JAVA_HOME=/data/local/tmp/jdk8u312-b07
$ export PATH="$JAVA_HOME/bin:$PATH"
$ java -version
openjdk version "1.8.0_312"
OpenJDK Runtime Environment (Temurin)(build 1.8.0_312-b07)
OpenJDK 64-Bit Server VM (Temurin)(build 25.312-b07, mixed mode)
这样 JDK 就安装完毕了。如果需要使用其他的命令,同样的修改 ld.so 的路径即可。我写了一个脚本,可以自动设置 ld.so 的位置,代码如下。
#!/system/bin/sh
if [ ! -d bin ]; then
echo "请在 JDK 目录下执行此脚本" >&2
exit 1
fi
if [ -z "$PATCH_ELF_EXEC" ]; then
PATCH_ELF_EXEC=patchelf
fi
"$PATCH_ELF_EXEC" --version > /dev/null 2>&1
if [ $? -gt 0 ]; then
echo "无法执行 patchelf" >&2
echo "PATCH_ELF_EXEC=$PATCH_ELF_EXEC" >&2
exit 2
fi
if [ -z "$XXD_EXEC" ]; then
XXD_EXEC=xxd
fi
"$XXD_EXEC" --version > /dev/null 2>&1
if [ $? -gt 0 ]; then
echo "无法执行 xxd" >&2
echo "XXD_EXEC=$XXD_EXEC" >&2
exit 3
fi
if [ -z "$LD_SO" ]; then
LD_SO="$1"
fi
# 如果没有 realpath 可以去掉这一行,只要保证输入的参数是绝对路径即可。
LD_SO=$(realpath "$LD_SO")
if [ ! -f "$LD_SO" ]; then
echo "用法: $0 [ld-linux-aarch64.so 的路径]" >&2
exit 4
fi
ls -1 bin | while read line
do
HEX=$(xxd -p -l 4 "bin/$line")
if [ -x "bin/$line" ] && [ "$HEX" == "7f454c46" ]; then
"$PATCH_ELF_EXEC" --set-interpreter "$LD_SO" "bin/$line"
fi
done
另外,这样移植的 Java 可能会遇到无法解析 DNS 的问题,如果遇到一直无法解析域名的问题的话需要加上几个属性才行。
# 可以使用 -Dxxx JVM 参数
java -Dsun.net.spi.nameservice.nameservers=119.29.29.29 -Dsun.net.spi.nameservice.provider.1=dns,sun
运行 Web 项目
由于 Android 没有 HOME 目录,也没有 /tmp 目录,所以正常参数是无法运行的,需要加一些额外的参数。
# 首先,定义一个 HOME 变量,用来存放 maven 仓库
mkdir /sdcard/home
export HOME=/sdcard/home
# 创建新的 Spring Boot Web 项目,写一个 Hello World(创建和修改代码略过)
cd demo
./mvnw -Dusr.home=$HOME -Dsun.net.spi.nameservice.nameservers=119.29.29.29 -Dsun.net.spi.nameservice.provider.1=dns,sun package
# 定义临时目录
mkdir /sdcard/tmp
# 运行
java -Duser.home=$HOME -Djava.io.tmpdir=/sdcard/tmp -jar target/demo-0.0.1-SNAPSHOT.jar
运行效果如下:
近期评论