Android笔记(一)
Android 小白的入门之旅。
转载、整理一些基础知识点系列。
Android 虚拟机
Android虚拟机包含 Dalvik虚拟机
(DVM)、 ART
(Android Runtime)。
Android
应用程序运行在 Dalvik/ART
虚拟机,并且每一个应用程序对应有一个单独的 Dalvik
虚拟机实例。
Dalvik
虚拟机实则也算是一个 Java
虚拟机(JVM),只不过它执行的是 dex
文件,而不是 class
文件。
Dalvik
虚拟机执行的是 dex
字节码,从 Android2.2
版本开始,支持 JIT即时编译 (Just in Time),在程序运行的过程中进行选择热点代码(经常执行的代码)进行编译或者优化。
而 ART(Android RunTime)
是在Android 4.4
中引入的一个开发者选项, Android 从5.0以后就完全使用了新的虚拟机ART,其与Dalvik有很大的区别。其推出的目的主要是为了提高Android的执行效率,减少卡顿现象。那它是怎么做的呢?ART 采用了一种叫AOT (ahead of time) 来代替目前的在 runtime
时的 Interpreter 与 JIT。ART 不是等到App运行的时候才去运行dex文件,而是在App安装的时候就通过 AOT编译器 将.dex
文件编译为对应的.oat
二进制文件,当用户点击App的启动图标时,ART直接加载.oat
文件去执行。其中那个.oat
文件就是一个**ELF**文件,其是当前机器的可执行的文件了。
Android
的运行时从 Dalvik
虚拟机替换成 ART
虚拟机,并不要求开发者将自己的应用直接编译成目标机器码,APK
仍然是一个包含 dex
字节码的文件。
ART
和 Dalvik
都是运行 dex
字节码的兼容运行时,因此针对 dalvik
开发的应用也能在 ART
环境中运作。
dex
文件是很多 .class
文件通过 DX编译器
处理压缩后的产物,最终可以在 Android
运行时环境执行。
Java虚拟机 和 Dalvik虚拟机的区别
- 运行的字节码不同
Java虚拟机运行的是Java字节码,Dalvik虚拟机运行的是Dalvik字节码(可以理解为汇编)。
传统的Java程序经过编译,生成Java字节码并保存在class文件中,Java虚拟机通过解码Class文件内容运行程序;
Dalvik虚拟机运行的Dalvik字节码是由Java字节码转换而来,并被打包到一个DEX可执行文件中(为什么使用JEB反编译DEX文件,就是通过DEX还原源JAVA代码),Dalvik虚拟机通过解释DEX文件来执行这些字节码。
- Dalvik可执行文件的体积更小
为什么Dalvik可执行文件dex体积更小?
在Android SDK中,将Java字节码转换为Dalvik字节码的工具是”dx”。dx能够重新排列Java类文件,消除在类文件中的冗余信息,避免重复加载和解析。
原理是这样子的:
Java类中通常有多个方法签名,如果它被调用或者继承,这些签名和方法也会被继承在调用方,所以大量的类包含同样的签名和字符串常量造成冗余。DX把这些常量统一存放在”常量池”中,一个常量只出现一次,供所有类使用,所以DEX文件体积更小。
- 虚拟机架构不同
Java虚拟机基于栈架构,运行时指令频繁入栈和出栈,占用大量CPU时间,对于资源有限的设备资源比较吃紧。
而Dalvik是基于寄存器架构的,指令发送给寄存器。直接操作寄存器的汇编语言执行速度是最快的。
- 虚拟机执行流程
Dalvik属于Android运行时环境。

Zygote是所有进程的孵化器,应用程序通过system_server将指令发给Zygote,Zygote为其分配一个单独的进程,并由Dalvik执行。
Dalvik通过loadClassFromDex()装载类。类被解析后变成ClassObject类型的数据结构存储,虚拟机采用gDvm.loadedClass全局散列表(类似map)存储和查询。
字节码验证器使用dvmVerifyCodeFlow()函数对装入的代码进行校验,虚拟机调用FindClass()函数查找并找到main()方法类。最后调用dvmInterpret()函数来初始化解释器并执行字节流。
- 虚拟机执行方式
即时编译。
Android系统架构
Andorid系统架构从上到下分别有应用层、应用框架层、系统运行时库层、硬件抽象层、Linux内核层。

应用层:包括系统应用比如闹钟、日历等这些在内的以及非系统级别的应用都属于应用层。负责用户交互,也就是我们需要开发的东西。
应用框架层:这一层主要是为咱们开发人员提供用来开发应用程序的API,平常我们开发程序大部分都是调用这部分的API来进行开发。这一层主要提供一些ActivityManager 管理应用生命周期、locationManager 地理位置服务、还有就是NotificationManager 消息通知管理等等
系统运行库层:这一层主要分为两部分,分别是c/c++程序库和Android运行库
c++库:主要是能被Andorid系统不同组件所使用,并通过应用程序框架为开发者提供服务。它主要功能有openggl 绘图方法库,多媒体库支持常用的音频 视频格式录制回访 ,还有我们常用会用到的一个轻量级的数据库 sqlLite 等还有ssl网络协议等等
Android运行时库:它主要又分为核心库和虚拟机ART,核心库主要包含Java核心库的大部分功能。Android 5.0系统之前使用的虚拟机是 dalvik,它相对于JVM来说,dalvik虚拟机是专门为移动设备定制的,主要在有限的内存中同时运行多个虚拟机实例,每一个dalvik虚拟机就是一个独立的linux进程,这样独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。 5.0之后,dalvik虚拟机被ART所取代,它们主要区别就是dalvik虚拟机,应用每次在运行的时候,都需要把字节码文件转换为机器码来运行,这样应用的运行效率就会比较低。 而ART虚拟机,应用在第一次安装的时候,字节码文件就会预先编译成机器码,让应用成为真正的本地应用。
硬件抽象层:它是位于操作系统内核和硬件电路之间的接口层,主要目的在于将硬件抽象化,隐藏了特定平台的硬件接口细节,为操作系统提供虚拟硬件平台,使其具有硬件无关性,可在多平台上进行移植。
linux内核层:Android的核心系统服务基于Linux内核,在此基础上添加了部分Android专用的驱动,android系统的安全管理、内存管理、进程管理、网络协议等都依赖于该内核。
Android 编译流程
一个App从源代码到 .apk
安装文件都经历了哪些过程呢?
下图非常清楚的描述了这一过程:

通过AAPT(Android Assets Packing Tool) 编译资源文件,将资源文件打包编译并生成生成R.java文件,就是放各种资源Id的那个文件。
通过Java编译器javac将
.java
源代码文件编译为.class
字节码文件通过Dalvik 编译器 将
.class
文件转化为.dex
文件通过Apk builder 将打包后的资源与
.dex
文件一起生成APK文件。
Android系统的启动流程


启动电源以及系统启动
加载引导程序Bootloader到RAM,然后执行。引导程序BootLoader启动
引导程序BootLoader是在Android操作系统开始运行前的一个小程序,它的主要作用是把系统OS拉起来并运行。Linux内核启动
内核启动时,设置缓存、被保护存储器、计划列表、加载驱动。当内核完成系统设置,它首先在系统文件中寻找init.rc文件,并启动init进程。init进程启动
init进程是系统空间内的第一个进程,进行初始化和启动属性服务,在main方法中进行,包括初始化资源文件和启动一系列的属性服务。通过执行init.rc文件的脚本文件来启动Zygote进程。Zygote进程启动
所有的应用程序包括system系统进程 都是zygote进程负责创建,因此zygote进程也被称为进程孵化器,它创建进程是通过复制自身来创建应用进程,它在启动过程中会在内部创建一个虚拟机实例,所以通过复制zygote进程而得到的应用进程和系统服务进程都可以快速地在内部的获得一个虚拟机实例拷贝。SystemServer进程启动
启动Binder线程池和SystemServiceManager,systemServiceManger主要是对系统服务进行创建、启动和生命周期管理,就会启动各种系统服务。(android中最核心的服务AMS就是在SystemServer进程中启动的)Launcher启动
Launcher组件是由之前启动的systemServer所启动的
这也是andorid系统启动的最后一步,launcher是andorid系统home程序,主要是用来显示系统中已安装的应用程序。 launcher应用程序的启动会通过请求packageManagerService返回系统中已经安装的应用信息,并将这些应用信息通过封装处理成快捷列表显示在系统屏幕上,这样咱们就可以单击启动它们。
被SystemServer进程启动的ActivityManagerService会启动Launcher,Launcher启动后会将已安装应用的快捷图标显示到界面上。
上述七步是手机开机andorid系统启动的流程。
Android 程序执行流程
现代虚拟机一般有两种执行方式,根据具体的使用场景各有侧重。

Interpreter :解释执行。一边把字节码翻译成当前硬件平台的机器码一边执行。优点是启动快,缺点就是执行效率太低下了。Interpreter 对应的编译器称为解释执行编译器。
JIT(Just In Time) :即时编译。当一些代码被频繁执行到时,虚拟机就将其编译成机器码。这些被频繁执行的代码有个专有名词:“热点代码” ,相信大家最熟悉的JVM就是sun公司的的虚拟机HotSpot,其也是因为热点探测技术比较牛逼而得名。JIT 对应的编译器为即时编译器。
Dalvik 虚拟机启动App流程

如上图所示,在Dalvik虚拟机上首次启动App一定是使用Interpreter解释执行的,期间会探测热点代码,使用JIT编译执行,如果我没记错的话,JIT应该是在Android2.2之后加入的,可见最早期的Android很缓慢。
ART 安装及启动App流程
前面说过,ART是在App安装的时候将 .apk
文件减压,并将 .dex
文件预编译为 .oat
可执行文件,当App启动的时候就不需要在Runtime解释执行了,但是这种方式也有它自己的缺点。
- 增加了App的安装时间,这个很容易理解,因为多了一个预编译过程。
- 增加了App所需要的安装空间,与Dalvik相比,手机上多了一份
.oat
文件。特别是无论一个App的某一功能是否被使用到以及被使用的频率,例如一个App的某个功能,用户几乎不会去打开,ART都将其编译为.oat
文件就显得有点低效了。
Google的那些天才工程师既然发现了问题,那肯定就会去想办法优化的。技术的每一次进步,都是站在前面技术的肩膀上的,这一次也不例外,Google的工程师将Interpreter,JIT与AOT 三种技术相结合来优化这个过程。
- 当你首次安装并一个App的时候,AOT不将
.dex
文件编译为.oat
文件,这一步减少了安装时间。系统通过Interpreter的方式来启动App. - 当在App运行过程中探测到了热点代码”hot code”,就使用JIT编译
- 这些通过JT编译的 平台代码 及编译配置文件都会被存储在缓存中,加快下次访问的速度
当设备处于空闲时间时,AOT 编译器就会启动,结合编译配置文件将热点代码编译为 .oat
可执行文件
当再次运行App时,ART就可以直接运行 .oat
文件了
PS: Android 虚拟机是不能直接执行 .class
文件的,而其只能执行 .dex
文件,所以就必须有一个将Java字节码 .class
文件转化为Dalvik字节码 .dex
文件的的编译器 —— DX编译器(其中一个)
应用进程启动流程
首先,activity分为两种分别是根activity和子activity,根activity就是显示在手机屏幕上快捷应用图标,在launcher应用程序启动器,根activity也就是app的第一个activity是由Launcher组件来启动,但它又是通过activity管理服务ActivityManagerService来启动根activity。但是activity Launcher activityManagerService分别运行在不同进程里面,这三个进程是通过binder进程间通信机制来完成进行通信完成activity的启动。应用的启动也就是根activity的启动。
ActivityManagerService 是一个系统关键服务,运行在systemService系统进程中,负责启动和调度应用程序组件。
luncher是andorid系统的home程序,管理和安装手机里的所有应用
时序图如下:

应用进程启动流程详情图如下:

(1)Launcher首先向activityManagerService发送一个启动activity的进程间通信请求
(2)ams会先把要启动的activity信息保存下来,然后再想Launcher发送一个进入中止状态的进程间通信请求。
(3)Launcher组件进入终止状态后,就会给ams发送一个已进入终止状态的一个进程间通信请求,ams收到后就会继续执行启动activity操作
(4)ams如果发现用来运行运行activity的进程不存在,它就会给zygote进程发送一个进程间通信请求,zaygote会调用fork()方法创建一个新的应用程序进程。zaygote进程在启动的时候在内部创建一个虚拟机实例,它通过复制它本身得到一个应用程序进程。
(5)新的应用程序进程启动完成之后,就会向ams发送一个启动完成的通信请求,
(6)进程创建好之后,经过一系列调用就会调用startactivity方法,最后 activity调用oncreate方法构建出页面至此我们的应用正式启动完成。
TODO:
- IPC原理
- AMS服务
- Binder进程
- Launcher组件(Android的各个组件)
- zaygote进程
- JNI