AI 摘要(由 ChatGPT 总结生成):
本文介绍了如何在 Android 设备上运行 Java 程序。由于 Android 基于 Linux 内核,虽没有官方的 Android 版本 JDK,但可以通过下载并配置 aarch64 架构的 JDK、GLIBC 动态链接库、PatchELF 和 Busybox 来实现。文章详细描述了相关软件的下载、环境变量设置和文件操作步骤,最终成功运行 Java 程序服务,并结合 shell 脚本处理环境路径,提供了较为完整的方案,适合对 Android 系统有一定了解的用户尝试。

前言

最近又来折腾了下,准备把换下来的 红米K40 手机充分利用起来,由于 Android 系统底层还是基于 Linux 内核,故也有了很多可玩的东西。

本次主要是解决在 Android 设备上运行 Java 程序服务,虽然 Java 官方一直没发布基于 Android 版本的 JDK,但也一直有 aarch64/armhf 版本的,这种架构手机也是受支持的。话不多说,下面开始教程!

准备工作

此处及下文全部基于您的设备架构默认为 aarch64 、文件放置的默认位置为 /data/local/tmp 目录( /sdcard 目录无法设置文件权限为可执行,因此请注意!),已开启 ADB 调试或有能力的自行解决文件传输问题。
  • PatchELF: 文件下载地址:patchelf ,选择 aarch64-linux-android-patchelf 文件。
  • JDK:文件下载地址: JDK (下载 Linux 平台 aarch64 架构的 JDK)。
  • GLIBC 动态链接库: 文件下载地址:glibc (下载 libc6_x.x-xubuntu3_arm64.deb,解包提取 usr/lib/aarch64-linux-gnu 目录下的所有文件,或者从其他发行版镜像中提取都可以,前提注意架构)。
  • busybox:文件下载地址:BusyboxTools (其它部分命令的拓展,如:realpathtarfile 等)。
  • 以及有一定的 Linux 操作知识。

设置并安装

先使用 ADB 将文件发送到手机:

adb push OpenJDK17U-jdk_aarch64_linux_hotspot_17.0.13_11.tar.gz /data/local/tmp
adb push aarch64-linux-gnu.zip /data/local/tmp
adb push aarch64-linux-android-patchelf /data/local/tmp

使用 ADB 进入设备 Shell 并操作:

adb shell

# 进入 /data/local/tmp 目录
cd /data/local/tmp

# 将 patchelf 重命名一下:
mv aarch64-linux-android-patchelf patchelf

# 解压 JDK
tar -zxvf OpenJDK17U-jdk_aarch64_linux_hotspot_17.0.13_11.tar.gz

# 重命名一下 jdk 目录名称(该操作随意,可有可无)
mv jdk-17.0.13+11 jdk-17.0.13

# 将 libc6_2.40-1ubuntu3_arm64.deb 解压再解压,得到目录文件
# 将 usr/lib/aarch64-linux-gnu 目录(上一步骤已压缩为 aarch64-linux-gnu.zip)所有文件释放到 libs 目录中。
unzip -c libs aarch64-linux-gnu.zip

最终的 /data/local/tmp 相关文件目录格式如下图:

image-20241203170132320

此时我们尝试执行一下 java 命令,会发现有奇怪的问题:

[root@k40:/data/local/tmp]# ./jdk-17.0.13/bin/java
sh: ./jdk-17.0.13/bin/java: No such file or directory
[root@k40:/data/local/tmp]#

这是因为原本的 JDK 是为通用 Linux 发行版所编译,这些发行版的目录格式也是遵循 Linux 标准目录格式,JDK 也因此会在 /lib 目录下查找它所需要的动态链接库文件,但是 Android 上没有,所以就报了上述的错误。我们可以使用 file 命令进行简单验证:

[root@k40:/data/local/tmp]# file ./jdk-17.0.13/bin/java
./jdk-17.0.13/bin/java: ELF shared object, 64-bit LSB arm64, dynamic (/lib/ld-linux-aarch64.so.1), not stripped
[root@k40:/data/local/tmp]#

所以我们需要提取上述的 aarch64-linux-gnu 里的动态库,然后对 ELF 二进制文件修改其库链接的位置即可使用:

# 授予 patchelf 文件可执行权限
chmod +x patchelf

# 修改动态链接库,注意链接库必须是绝对路径
[root@k40:/data/local/tmp]# ./patchelf --set-interpreter /data/local/tmp/libs/ld-linux-aarch64.so.1 ./jdk-17.0.13/bin/java
warning: working around a Linux kernel bug by creating a hole of 61440 bytes in ‘./jdk-17.0.13/bin/java’
[root@k40:/data/local/tmp]#

此时,我们再使用 java --version 命令还是看不到相关版本信息并输出错误,这是因为 Java 使用的运行库与 Android 不完全兼容,因此我们还要解决动态库索引问题,由于 Android 基于 Linux 内核,因此可以配置 LD_LIBRARY_PATH 环境变量来加载我们所需的动态库文件,顺便再配置一下 JDK 的环境,如下:

[root@k40:/data/local/tmp]# export LD_LIBRARY_PATH=/data/local/tmp/libs
[root@k40:/data/local/tmp]# export JAVA_HOME=/data/local/tmp/jdk_test/jdk-17.0.13/
[root@k40:/data/local/tmp]# export PATH="$JAVA_HOME/bin:$PATH"
[root@k40:/data/local/tmp]# java --version
openjdk 17.0.13 2024-10-15
OpenJDK Runtime Environment Temurin-17.0.13+11 (build 17.0.13+11)
OpenJDK 64-Bit Server VM Temurin-17.0.13+11 (build 17.0.13+11, mixed mode, sharing)
[root@k40:/data/local/tmp]#

此时可以看到命令成功输出 java 版本信息。如果使用其它命令(如:javac 等),还是会报上述一样的问题,所以我们需要一次性解决 JDK 的 bin 目录下的所有文件依赖,命令如下:

# 操作在 /data/local/tmp 目录,命令如下:
[root@k40:/data/local/tmp]# for i in $(ls ./jdk-17.0.13/bin);do ./patchelf --set-interpreter /data/local/tmp/libs/ld-linux-aarch64.so.1 ./jdk-17.0.13/bin/$i;done

这样设置之后,JDK 的 bin 目录下其它二进制文件也就可以执行了,如下所示:

[root@k40:/data/local/tmp]# ./jdk-17.0.13/bin/javac --version
javac 17.0.13
[root@k40:/data/local/tmp]#

到此,基本可以在 Android 上使用 java 了。

运行项目

由于 Android ADB Shell 环境相关环境与标准 Linux 环境目录有异,因此运行 jar 包时遇到如 /tmphome 目录等可能会无法寻找到,所以需要增加一些额外参数。我们可以先在 /data/local/tmp 下创建一些我们所需的目录,如下:

mkdir home tmp

此处为方便使用,我把一些东西写在 Shell 脚本中,后续执行将借助其脚本运行,脚本内容如下:

#!/system/bin/sh
export HOME=/data/local/tmp/home
export TMPDIR=/data/local/tmp/tmp
export LD_LIBRARY_PATH=/data/local/tmp/libs
export JAVA_HOME=/data/local/tmp/jdk-17.0.13
export CLASSPATH=.:${JAVA_HOME}/lib
export PATH=${JAVA_HOME}/bin:${PATH}

exec "$@"

赋予脚本可执行权限,然后使用脚本执行 jar 包,并使用相关虚拟机参数重新定义相关目录,测试 jar 包运行效果如下:

[root@k40:/data/local/tmp/tmp]# chmod +x java_run.sh
[root@k40:/data/local/tmp/tmp]# ./java_run.sh java -Duser.home=$HOME -Djava.io.tmpdir=$TMPDIR -jar zipkin-server-3.4.1-exec.jar

                  oo
                 oooo
                oooooo
               oooooooo
              oooooooooo
             oooooooooooo
           ooooooo  ooooooo
          oooooo     ooooooo
         oooooo       ooooooo
        oooooo   o  o   oooooo
       oooooo   oo  oo   oooooo
     ooooooo  oooo  oooo  ooooooo
    oooooo   ooooo  ooooo  ooooooo
   oooooo   oooooo  oooooo  ooooooo
  oooooooo      oo  oo      oooooooo
  ooooooooooooo oo  oo ooooooooooooo
      oooooooooooo  oooooooooooo
          oooooooo  oooooooo
              oooo  oooo

     ________ ____  _  _____ _   _
    |__  /_ _|  _ \| |/ /_ _| \ | |
      / / | || |_) | ' / | ||  \| |
     / /_ | ||  __/| . \ | || |\  |
    |____|___|_|   |_|\_\___|_| \_|

:: version 3.4.1 :: commit 1dc238c ::

2024-12-03T10:07:09.761Z  WARN [/] 4860 --- [           main] i.m.p.PrometheusMeterRegistry            : A MeterFilter is being configured after a Meter has been registered to this registry. All MeterFilters should be configured before any Meters are registered. If that is not possible or you have a use case where it should be allowed, let the Micrometer maintainers know at https://github.com/micrometer-metrics/micrometer/issues/4920. Enable DEBUG level logging on this logger to see a stack trace of the call configuring this MeterFilter.
2024-12-03T10:07:10.269Z  INFO [/] 4860 --- [oss-http-*:9411] c.l.a.s.Server                           : Serving HTTP at /[0:0:0:0:0:0:0:0]:9411 - http://127.0.0.1:9411/

服务测试成功运行并可访问:

image-20241203180951874

小结

这也是继上一个把 Python 弄进 Android 系统后又一个将 Java 弄进Android 系统内使用。这下可折腾的就多了,大家自行发挥吧~

End

本文标题:突破限制!让你的 Android 解锁 JAVA 能力!

本文链接:https://www.isisy.com/1584.html

除非另有说明,本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

声明:转载请注明文章来源。

如果觉得我的文章对你有用,请随意赞赏