阅读 262

从编译到运行,Android 开发者应该知道的一些基础概念

概念

aapt2

The Android Asset Packaging Tool

资源打包工具,将 .dex 文件压缩成 apk 文件中,.apk 文件是发布Android应用的标准方式,除了编译的classes.dex文件外,它还将包含所有应用所需的资源和清单文件。

Android Debug Bridge (adb)

完成Android设备与电脑的通信,

后文将直接用 Android 代指 Android设备

adb 有啥用

  • 向安卓设备发送文件和从安卓设备接收文件。
  • 在Android设备上运行命令
  • 调试设备上的应用程序
  • 打开设备上的shell
  • 读取设备上的日志

在terminal输入adb后,发生了什么

  • Client 调用某个 adb 命令
  • adb 进程 fork 出一个子进程作为 Server
  • Server 查找当前连接的 emulator/device
  • Server 接收到来自 Client 请求
  • Server 处理请求,将本地处理不了的请求发给 emulator/device
  • 位于 emulator/device 的 adbd拿到请求后交给对应的java虚拟机进程。
  • adbd 将结果发回给 Server
  • Server 将结果发回给 Client

Dalvik

  • 一种字节码格式,类似 .class里的 Java 字节码。
  • 一个运行时环境,有点像JVM。

API 21 后, Android Runtime(ART) 取代 Dalvik 作为Dalvik的运行时环境 但是字节码格式还是用的 Dalvik。

D8

.class.jar 文件 转换成 ART 可执行的 .dex 文件。 紧接执行 aapt2 进一步压缩,

OAT

ART 能直接执行的本地库格式。

当一个应用安装在Android设备上时,应用中的Dalvik字节码会被转换为ELF共享对象:即本地Linux库。

这个库不仅包括原生的机器代码,还包括原始的Dalvik字节码,以便对代码进行调试。转换后的库的格式称为OAT,从.dex文件转换为OAT文件是由一个名为dex2oat的工具来完成的。

编译及部署 APP 流程

AS 简化了整个编译流程,简单到我们只需要按下 run, 就可以完成应用的编译和部署。 你会对整个流程的背后发生了什么好奇不?

编译

  1. Java 编译

如果是 Kotlin 源码,在JVM运行前,kotlinc 会将 Kotlin 编译成 Java字节码。

所谓的字节码,就是为了让解释器高效执行的一种指令集形式,而Java字节码就是JVM的指令集。 无论是 Java 还是 kotlin, 只要最终运行在 jvm 上, 都需要被编译成 Java字节码,即 .class.class 的内容就是 Java 字节码

  1. 生成字节码

Android 平台有自己的字节码格式,Dalvik。 即 dalvik 字节码可以直接被 ART 执行。

通过dx命令将.class文件和任何.jar库转换为一个classes.dex文件,其内容Dalvik字节码表达。

以上过程综合后:

  1. 生成 .apk 文件

通过 aapt2 将 classes.dex文件和应用程序中的资源,如图片和布局,压缩成一个文件,apk(Android Package)

到这一步, apk 就可以直接运行了,其实,可能你还需要完成一些事情。。。。。

  1. 签名 .apk 文件

如果你要将 apk 发布到应用市场,就要对它进行签名,通过在.apk中存储一个基于.apk内容的校验和以及一个单独生成的私钥文件。

  • jarsigner: 为了签署.jar文件而创建的,但也可以应用在.apk 文件,因为它们也是压缩文件。
  • zipalign:将确保文件的压缩部分在字节边界上排成一行。这样 Android 就可以轻松地读取它们,而不需要解压文件。

部署

ADB

adb进程将打开一个网络套接字,并监听对应端口上的命令。你输入的每一条adb命令都会向这个端口发送指令。

.apk文件传输到设备

adb命令将.apk文件传输到Android设备的文件系统中。位置是由应用的包名定义的。例如,如果包是com.zhugsnifftherose.test,那么.apk文件将被放置在/data/app/com.zhugsnifftherose.test。

开始运行

API 21后,Dalvik虚拟机已经被新的Android Runtime所取代。让我们来看看当一个应用运行时,会发生什么。

用户启动某个应用程序

Zygote进程用来启动一个应用,其内存空间包含了任何应用所需要的所有核心库,但不包括特定应用的代码。

Zygote通过 系统调用fork 创建一个自己的副本。Android是一个Linux系统,fork可以很快复制一个像Zygote这样的进程。这就是使用Zygote进程的原因:复制一个像Zygote这样半启动的进程,比从主系统文件中加载一个新进程要快得多。Zygote的存在,让应用启动变得更快了。

Android 将.dex代码转换为本地OAT格式。

class.dex 文件从 .apk 文件解析后会存在特定目录,但Android并不是简单地放置一个classes.dex文件的副本,此时ART 会使用设备自带的 dex2oat,将classes.dex中的Dalvik字节码转换成本地机器代码。

保存的路径将包括应用程序的包名,以确保它不会覆盖任何其他应用程序。

转换后的代码将是针对Android设备的CPU的机器代码。比如 x86的, 比如 arm 的。

这也能体现,ART 的优化处理虽然拉长了 应用安装时间,但是运行时的效率就更加高了。

加载 OAT

OAT 格式的代码被直接映射进 app 进程。

接着, app 就开始初始化 activity, 并显示在屏幕上了.

疑问

通过以上分析,我们知道了 Android 源码 存在于 zygote 进程里, 那是不是就可以猜想,其实 Android 源码是以 动态链接库的形式存在,我们在使用时,以动态链接的方式,完成方法调用?

启发

D8阶段,就完成了应用层的代码编译,其中包括了三方库,即使 R8 会对无用的方法进行优化去除,于我而言以后在项目中最好还是统一 三方库的版本,尽量不要将多个版本的库源码打入 dex 中。

参考

Android Runtime (ART) and Dalvik

The internals of Android APK build process — Article

文章分类
Android
文章标签