8. Android加载流程(打包与启动)

移动安全的学习离不开对Android加载流程的分析,包括Android虚拟机,Android打包,启动流程等...

这篇文章  就对Android的一些基本加载进行学习。

Android虚拟机

Android开发中接触的是与Java虚拟机类似的的Dalvik虚拟机和ART虚拟机

Dalvik虚拟机

什么是Dalvik虚拟机

Dalvik虚拟机简称Dalvik VM或者DVM,是Google专门为Android平台开发的虚拟机,它运行在Android运行库中,需要注意的是DVM并不是一个Java虚拟机(Jvm虚拟机)

DVM与JVM虚拟机的区别

1.基于的架构不同

Jvm基于栈,需要去栈中读写数据,所需要的指令会更多,这样会导致速度慢,对于性能有限的移动设备,显然不是很适合。

DVM基于寄存器,它没有基于栈的虚拟机在拷贝数据而使用的大量的出入栈指令,同时指令更紧凑更简洁。但是由于显示指定了操作数,所以基于寄存器的指令会比基于栈的指令要大,但是由于指令数量的减少,总的代码数不会增加多少。

2.执行的字节码不同

JVM虚拟机中Java代码从编写到执行的过程:

1) 编写Java代码

2) 所有的Java代码通过Java编译器(javac)编译成java字节码,即.class文件

3) Java字节码在Java虚拟机上被解释成机器语言后,程序执行

DVM虚拟机Java代码从编写到执行的过程:

1) 编写Java代码

2) 所有的Java代码通过Java编译器(javac)编译成java字节码,即.class文件

3) Java字节码通过Android的dx工具转换成Dalvik字节码,即.dex文件

4) Dalvik字节码在Dalvik虚拟机上运行

文件结构对比图

ART虚拟机

ART虚拟机是Android4.4发布的,用来替换Dalvik虚拟机,Android 4.4默认采用的还是DVM,不过系统会提供一个选项来开启ART。在Android 5.0时,默认采用ART。

ART与DVM虚拟机的区别

1.DVM虚拟机中应用每次运行时,字节码都需要通过即时编译器转换成机器码,这会使应用的运行效率降低,而在ART中,系统在安装应用时会进行一次预编译,将字节码预先编译成机器码并存储在本地,这样应用每次运行时就不需要执行编译,提升了运行效率。

2.ART由于将字节码预先编译成机器码存储在本地,所以ART虚拟机占用空间比Dalvik大。

Apk打包流程

Android构建系统编译的应用资源和源代码,然后将它们打包成可测试、部署、签署和分发的 APK。

一般使用 Android Studio开发的时候使用Gradle构建工具包来自动执行和管理构建流程,同时也可以灵活地自定义构建配置。

在了解Android打包流程之前,先查看一个apk包内容,可以知道它里面都有哪些文件组成:

文件或目录说明
assets文件夹 存放需要打包到APK中的静态文件
lib文件夹 存放应用程序依赖的native库文件
META-INF文件夹

1.该目录下存放的是签名信息,用来保证apk包的完整性和系统的安全性

2.CERT.RSA   这个文件保存了签名和公钥证书

3.CERT.SF  这个是对每个文件的头3行进行SHA1 hash

4.MANIFEST.MF  版本号以及每一个文件的哈希值(BASE64),包括资源文件。这个是对每个文件的整体进行SHA1(hash)。

res文件夹 存放资源文件的目录(图片,文本,xml布局)
AndroidManifest.xml 一个清单文件,它描述了应用的名字、版本、权限、注册的服务等信息。
classes.dex java源码编译经过编译后生成的dalvik字节码文件,主要在Dalvik虚拟机上运行的主要代码部分
resources.arsc 用来记录资源文件和资源ID之间的映射关系,用来根据资源ID寻找资源

知道了apk包的内容,会更好的理解Android打包流程:

先了解一下各个步骤使用的工具(绿色框):

名称功能介绍在操作系统中的路径
aapt Android资源打包工具,并生成R.java和resources.arsc文件 ${ANDROID_SDK_HOME}/platform-tools/appt
aidl Android接口描述语言.aidl文件转化为.java文件的工具 ${ANDROID_SDK_HOME}/platform-tools/aidl
javac

Java Compiler(编译器)将R.java、AIDL接口生成的java文件、应用代码java文件编译成.class文件。

${JDK_HOME}/javac或/usr/bin/javac

dex 转化.class文件为Davik VM能识别的.dex文件 ${ANDROID_SDK_HOME}/platform-tools/dx
apkbuilder

将资源文件和.dex文件生成未签名的.apk文件

${ANDROID_SDK_HOME}/tools/opkbuilder
jarsigner .jar文件的签名工具 ${JDK_HOME}/jarsigner或/usr/bin/jarsigner
zipalign

字节码对齐工具

${ANDROID_SDK_HOME}/tools/zipalign

整个apk打包流程为:

  1. 通过aapt工具进行资源文件(包括AndroidManifest.xml、布局文件、各种xml资源等)的打包,生成R.java文件。
  2. 通过aidl工具处理AIDL文件,生成相应的Java文件。
  3. 通过Javac工具编译项目源码,生成Class文件。
  4. 通过dx工具将所有的Class文件转换成DEX文件,该过程主要完成Java字节码转换成Dalvik字节码,压缩常量池以及清除冗余信息等工作。
  5. 通过apkbuilder具将资源文件、DEX文件打包生成APK文件。
  6. 利用jarsigner对生成的APK文件进行签名。
  7. 如果是正式版的APK,还会利用ZipAlign工具进行对齐处理,对齐的过程就是将APK文件中所有的资源文件举例文件的起始距离都偏移4字节的整数倍,这样通过内存映射访问APK文件的速度会更快。

-------------------------------------------------------------------

具体每一步打包流程为: 

1. aapt阶段:

使用aapt来打包res资源文件,生成R.java、resources.arsc和res文件(二进制 & 非二进制如res/raw和pic保持原样)

  • res目录,有9种子目录
  • R.java文件。里面拥有很多个静态内部类,比如layout,string等。每当有这种资源添加时,就在R.java文件中添加一条静态内部类里的静态常量类成员,且所有成员都是int类型。
  • resources.arsc文件。这个文件记录了所有的应用程序资源目录的信息,包括每一个资源名称、类型、值、ID以及所配置的维度信息。我们可以将这个文件想象成是一个资源索引表,这个资源索引表在给定资源ID和设备配置信息的情况下,能够在应用程序的资源目录中快速地找到最匹配的资源。

2. aidl阶段:

AIDL,Android接口定义语言,Android提供的IPC的一种独特实现。这个阶段处理.aidl文件,生成对应的Java接口文件。

3. Java Compiler阶段:

通过Java Compiler编译R.java、Java接口文件、Java源文件,生成.class文件。

4. dex阶段:

通过dex命令,将.class文件和第三方库中的.class文件处理生成classes.dex。

5. apkbuilder阶段:

将 classes.dex,resources.arsc,res文件夹(res/raw资源被原装不动地打包进APK之外,其它的资源都会被编译或者处理)、Other Resources(assets文件夹),AndroidManifest.xml打包成apk文件。

6. Jarsigner阶段

对apk进行签名,可以进行Debug和Release 签名。

7. zipalign阶段

release mode 下使用 aipalign 进行align,即对签名后的apk进行对齐处理。

Zipalign是一个android平台上整理APK文件的工具,它对apk中未压缩的数据进行4字节对齐,对齐后就可以使用mmap函数读取文件,可以像读取内存一样对普通文件进行操作。如果没有4字节对齐,就必须显式的读取,这样比较缓慢并且会耗费额外的内存。

在 Android SDK 中包含一个名为 zipalign 的工具,它能够对打包后的 app 进行优化。 其位于 SDK 的 \build-tools\23.0.2\zipalign.exe 目录下

Android启动流程

这里不再多说,推一下大佬博客:

https://www.jianshu.com/p/9f978d57c683

讲的很详细

Android的通信方式

IPC(Inter-Process Communication, 进程间通信)分类:

Linux: 管道、消息队列、共享内存、Socket、信号量、信号这些IPC机制。

Android:提供了Binder IPC机制。

对于Android来说:

  1)同一进程内线程间通信:Handler消息机制。

  2)进程间通信:Binder IPC机制,当然也包含有Socket通信。

system server、media server以及上层App之间更多的是采用Binder IPC方式来完成跨进程间的通信。

Zygote进程的IPC采用的是Socket机制。

如果想更了解Android的通信方式,请移步 https://www.jianshu.com/p/a4b2a3bc3cb2

APP启动流程,从点击桌面开始

启动流程:

①点击桌面App图标,Launcher进程采用Binder IPC向system_server进程发起startActivity请求;

②system_server进程接收到请求后,向zygote进程发送创建进程的请求;

③Zygote进程fork出新的子进程,即App进程;

④App进程,通过Binder IPC向sytem_server进程发起attachApplication请求;

⑤system_server进程在收到请求后,进行一系列准备工作后,再通过binder IPC向App进程发送scheduleLaunchActivity请求;

⑥App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息;

⑦主线程在收到Message后,通过发射机制创建目标Activity,并回调Activity.onCreate()等方法。

⑧到此,App便正式启动,开始进入Activity生命周期,执行完onCreate/onStart/onResume方法,UI渲染结束后便可以看到App的主界面。

启动流程:

(1)创建进程:

①先从Launcher的startActivity()方法,通过Binder通信,调用ActivityManagerService的startActivity方法。

②一系列折腾,最后调用startProcessLocked()方法来创建新的进程。

③该方法会通过前面讲到的socket通道传递参数给Zygote进程。Zygote孵化自身。调用ZygoteInit.main()方法来实例化ActivityThread对象并最终返回新进程的pid。

④调用ActivityThread.main()方法,ActivityThread随后依次调用Looper.prepareLoop()和Looper.loop()来开启消息循环。

 

更直白的流程解释:

①App发起进程:当从桌面启动应用,则发起进程便是Launcher所在进程;当从某App内启动远程进程,则发送进程便是该App所在进程。发起进程先通过binder发送消息给system_server进程;

②system_server进程:调用Process.start()方法,通过socket向zygote进程发送创建新进程的请求;

③zygote进程:在执行ZygoteInit.main()后便进入runSelectLoop()循环体内,当有客户端连接时便会执行ZygoteConnection.runOnce()方法,再经过层层调用后fork出新的应用进程;

④新进程:执行handleChildProc方法,最后调用ActivityThread.main()方法。

(2)绑定Application:

①上面创建进程后,执行ActivityThread.main()方法,随后调用attach()方法。

②将进程和指定的Application绑定起来。这个是通过上节的ActivityThread对象中调用bindApplication()方法完成的。该方法发送一个BIND_APPLICATION的消息到消息队列中, 最终通过handleBindApplication()方法处理该消息. 然后调用makeApplication()方法来加载App的classes到内存中。

更直白的流程解释:

(3)显示Activity界面:

经过前两个步骤之后, 系统已经拥有了该application的进程。 后面的调用顺序就是普通的从一个已经存在的进程中启动一个新进程的activity了。

实际调用方法是realStartActivity(), 它会调用application线程对象中的scheduleLaunchActivity()发送一个LAUNCH_ACTIVITY消息到消息队列中, 通过 handleLaunchActivity()来处理该消息。

在 handleLaunchActivity()通过performLaunchActiivty()方法回调Activity的onCreate()方法和onStart()方法,然后通过handleResumeActivity()方法,回调Activity的onResume()方法,最终显示Activity界面。

 

Binder通信

 

 

 

posted @ 2019-11-09 19:28  bmjoker  阅读(...)  评论(... 编辑 收藏