因为近期的工作接触了许多 android 工具链的东西,所以我们就来介绍下 APK 这个耳熟能详的文件。首先,我们先看看如何使用 Dex 文件在手机终端上输出一个 HelloWorld
学习过 Android 的人一定知道,在 Android OS 上跑的虚拟机曾经叫 dalvik,现在叫 ART (Android Runtime),为了方便,下文不再区分两者差别,暂时统称 dalvik。如果把 dalvik 当作一个黑盒,无视细节,我们就能拿他和 jvm 进行类比。那么,在学习 java 语言之初,使用 IDE 进行 java 开发之前,我们一定知道有两个二进制文件叫做 javac 和 java,一个是将 xxx.java 源代码编译成 xxx.class 字节码,一个是启动虚拟机加载运行字节码。那么在 Android 中,dx 类似 javac,但是它的输入不是 java 源代码,而是 class 字节码,输出是大名鼎鼎的 dex
文件,今天我们不探讨 dex
和 class
文件的区别,我们只要知道,把 class
文件和 dex
文件分别指向给不同的二进制做输入,就可以执行里面的逻辑。jvm 里面运行 class
的是 java
,那么 Android 里面运行 dex
的二进制文件,是 dalvikvm
> adb shell > dalvikvm -version
一如既往令人讨厌的单横杠
我的手机是一台运行 Android 9 的手机,输出的结果是:
ART version 2.1.0 arm64
如果我们在 jvm 的环境下,运行
> java -version
那么输出的结果是
~/Desktop/ java -version
java version "1.8.0_77"
Java(TM) SE Runtime Environment (build 1.8.0_77-b03)
Java HotSpot(TM) 64-Bit Server VM (build 25.77-b03, mixed mode)
可以看见我的机器上运行的是 java 8,好,运行工具暂时介绍到这里,接下来我们看下如何让 jvm 和 dalvik 运行 HelloWorld 程序。
首先,我们需要写代码,写一个简单的 HelloWorld.java 文件:
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World!"); } }
这四行 java 代码不能更简单了,我们应该不能更熟悉了。我们从上一个章节知道 dx
的输入格式是 class
文件, javac
的输入格式是 java 源代码,输出是 class
文件,也就是说,不管怎么样,我们都需要生成 class
文件,那么,生成的方式很简单,只需要运行 javac HelloWorld.java
即可,在当前目录下,就会出现一个 HelloWorld.class
文件,jvm 上需要的文件就准备好了,接下来看看 dalvik 上需要准备的东西。学习过 Android 的人可能会了解到,class -> dex 需要的工具是 dx
,它属于 Android Platform Build Tools 的一部分,会随着 SDK 的分发更新而更新,在我这使用的是 28.0.3 版本,所以它的路径就是 $ANDROID_HOME/build-tools/28.0.3/dx
,以下简称 dx
,这个二进制文件平常我们虽然天天会用,但是不会直接接触,所以对于我们来说是陌生的,知道这个二进制文件所在的路径,第一步我的习惯是使用 --help
命令看一下它能做什么工作(又要吐槽下垃圾 java 的单横杠),执行 dx --help
,我们看见如下输出(省略暂时不重要的部分)
dx --dex [--debug] [--verbose] [--positions=<style>] [--no-locals] [--no-optimize] [--statistics] [--[no-]optimize-list=<file>] [--no-strict] [--keep-classes] [--output=<file>] [--dump-to=<file>] [--dump-width=<n>] [--dump-method=<name>[*]] [--verbose-dump] [--no-files] [--core-library] [--num-threads=<n>] [--incremental] [--force-jumbo] [--no-warning] [--multi-dex [--main-dex-list=<file> [--minimal-main-dex]] [--input-list=<file>] [--min-sdk-version=<n>] [--allow-all-interface-method-invokes] [<file>.class | <file>.{zip,jar,apk} | <directory>] ... Convert a set of classfiles into a dex file, optionally embedded in a jar/zip. Output name must end with one of: .dex .jar .zip .apk or be a directory.
根据这部分的说明,我们知道 dx 可以接受一个 class 文件的集合,转成一个 dex 文件或者 jar/zip 文件,里面的内容有少许的不同,但是不管是 jar 文件还是 zip 文件,里面其实核心还是一个 dex 文件,此处为了方便,我们就直接转出 dex 文件,执行如下命令:
$ANDROID_HOME/build-tools/28.0.3/dx --dex --output=classes.dex HelloWorld.class
当然此处的名字不一定是 classes.dex。
执行完后,我们在当前目录下也能看见刚刚产出的 dex 文件。
我们拿到了 class 文件和 dex 文件,那么在 jvm 上,我们只要使用 java HelloWorld 就搞定了。
~/Desktop/ java -cp . HelloWorld
输出了 Hello World。这里我们都很熟悉,那么如何在 dalvik 上运行呢?其实也很简单。首先把需要的 dex 文件传到手机上,(以下都以 Android P 为例)
adb push classes.dex /sdcard/
然后我们 adb shell 进入到 /sdcard/ 下面。
在运行之前,我们再回忆以下,dex 文件和 class 文件不同的地方是,一个 class 文件里面通常最多只包含了一个 public 类,但是 dex 文件是 class 文件的集合,有点像 jar,但是不是 jar 文件那样简单的压缩,它是一个转换后的字节码集合文件。因此,dalvik 上面的 -cp
(classpath)参数和 jvm 上的 -cp
参数有点不同,dalvik 上指的是 dex,那么只要执行如下命令:
:/sdcard $ dalvikvm -cp HelloWorld.dex HelloWorld
就输出了我们想要的 Hello World,其中 cp 指定的是 classpath,后面指定的类名,毕竟 dex 文件一旦有多个类存在 main 函数的话,就不知道选哪个类去运行了。
之前如果有的小伙伴对于 Android 上的类加载器有所耳闻的话,我们还可以在这里故意输错类名,看一下堆栈输出,比如
> /sdcard $ dalvikvm -cp HelloWorld.dex HelloWorl Unable to locate class 'HelloWorl' java.lang.ClassNotFoundException: Didn't find class "HelloWorl" on path: DexPathList[[dex file "HelloWorld.dex"],nativeLibraryDirectories=[/system/lib64, /system/lib64]] at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:134) at java.lang.ClassLoader.loadClass(ClassLoader.java:379) at java.lang.ClassLoader.loadClass(ClassLoader.java:312) Exception in thread "main" java.lang.ClassNotFoundException: Didn't find class "HelloWorl" on path: DexPathList[[dex file "HelloWorld.dex"],nativeLibraryDirectories=[/system/lib64, /system/lib64]] at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:134) at java.lang.ClassLoader.loadClass(ClassLoader.java:379) at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
我们可以看到,此处的类加载器是 DexClassLoader,里面存在一个 DexPathList。
dalvikvm 除了能接受一个裸露的 dex 文件以外,还能接受一个 zip 格式的文件,只要求里面的 dex 文件名必须是 classes.dex 就行。比如我们传一个 zip/apk/jar 都能接受,毕竟他们的本质都是 zip。
以上就是 jvm 和 dalvik 运行各自字节码的步骤和一些约定,知道了以上的情况,后续的文章我们再详细介绍下 apk 里面的东西,以及我们如何 手动 调用一些命令生成一个 apk 供 Android 运行。
本文由Gemini Wen 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Jul 10, 2019 at 04:25 pm