1.2 Android架构解析
Android应用开发从入门到精通
Android系统的底层建立在Linux系统之上,该平台采用一种称为软件叠层(Software Stack)的方式进行构建。这种软件叠层结构使得层与层之间相互分离,明确各层的分工。这种分工是软件工程中常说的低耦合高内聚的设计概念。
1.2.1 Android系统架构图
Android作为一个移动设备的平台,其软件层次结构包括了内核层、中间件和应用程序。下面看看Android的系统架构图,如图1.2所示。
如图1.2所示,Android系统采用分层的架构。从架构看,Android分为4层,从高层到底层分别是应用程序层、应用程序框架层、系统运行库层和Iinux核心层。
下面对Android框架图进行庖丁解牛式的分析,同样从高层到底层,首先是应用程序。
1.2.2 应用程序(Applications)
Android会同一系列核心应用程序一起发布,包括E-mail客户端、SMS短消息程序、日历、地图、浏览器、联系人管理程序等。所有的应用程序都是使用Java语言编写的。通过打包工具将应用程序的字节码文件、资源文件和清单文件打包到一个以“apk”为后缀的文件中,该文件就是应用程序的载体,被复制到Android设备上之后,会被解析成机器能识别的机器码。那么开发应用是不是有设计规则呢?回答是肯定的,请看下面3点。
(1)每个应用程序都运行在自己的进程空间中。在需要执行该应用程序时,Android将启动该进程;当不再需要该应用程序,并且系统资源不够分配时,系统将终止该进程。
(2)每个进程都有自己的运行库(ART),所以,任何应用程序的代码与其他的应用程序的代码是相互隔离的。
(3)每个应用程序被分配给一个唯一的ID。所以,应用程序的文件只能对该应用程序可见。
Android平台的一个核心要点是,一个应用程序能够利用其他应用程序的组件。例如,一个程序A用于显示指定文件夹中的全部数据库名,而另外一个程序B用于查看某一指定数据库的信息,如数据表名、数据表模式(Schema)、数据表内容等。在程序A中,当用户单击列表中的某一数据库名称项时,可以调用程序B中的模块去显示指定数据库的信息,而无需重复开发。
而为了做到这一点,系统必须能够在应用程序需要调用指定模块时找到并启动包含该模块的组件。因此,不像其他系统的应用程序,Android应用程序没有main方法,代码框架也必须遵照Android平台所定义的形式。所以,Android应用程序需要包含系统能够识别并调用的一些基本组件。Android系统四大组件分别是Activity(活动)、Service(服务)、Broadcast Receiver(广播接收器)和Content Provider(内容提供者)。
1.Activity(活动)
Activity表现为用于与用户进行交互的可视化界面,是所有Android应用程序的门面,凡是在应用中看得到的东西,都是放在Activity中的。例如,一个Activity可以是系统登录界面,另外一个Activity可以是显示已登录用户信息的列表。
用户定义的每一个Activity都继承于父类Activity,代码如下所示。
public class MainActivity extends Activity{}
一个应用程序可能由一个或多个Activity组成,Android平台通过Activity栈来对Activity进行管理。
2.Service(服务)
服务是一类无需可视用户界面,更适合在后台长期运行的应用程序,如背景音乐播放器或后台数据处理服务等。
同Activity一样,用户定义的每一个服务都继承于父类Service,该父类由Android平台框架预定义。代码如下所示。
public class TouchService extends Service{}
3.Broadcast Receiver(广播接收器)
广播接收器是一类只接收和处理广播消息的组件。广播接收器也没有显示用户界面,但是,可以在响应其接收信息时启动一个Activity,或者通过通知管理器来显示提示界面从而警示用户。
一个应用程序可能有任意数量的广播接收器来响应任何它认为重要的通告。所有用户定义的广播接收器都继承于父类BroadcastReceiver。该父类由Android平台框架预定义。
4.Content Provider(内容提供者)
内容提供者可以将指定的一组应用程序数据让其他应用程序使用。这些数据可以存储于文件系统或者SQLite数据库。
用户定义的内容提供者都继承于父类ContentProvider,并实现一套标准的方法来允许其他应用程序获取并存储其所控制的数据类型。该父类由Android平台框架预定义。
一个内容提供者可以和其他内容提供者进行交互,甚至和其他内容提供者协作来管理任何进程内的通信。
通过前面的介绍,相信读者对Android程序的主要组成部分应该有所认识。但是,组件的调用或者组件与组件之间的切换又是如何实现的呢?例如,应用程序启动时如何调用指定的Activity或者服务,Activity之间如何进行切换调用?
对于组件间通信的问题,读者就需要认识Intent,Intent负责对应用中一次操作的动作、动作涉及的数据、附加数据进行描述,Android则根据此Intent的描述,负责找到对应的组件,将Intent传递给调用的组件,并完成组件的调用。简而言之就是,解决Android应用的各项组件之间的通信。Intent在这里起着一个媒体中介的作用,专门提供组件互相调用的相关信息,实现调用者与被调用者之间的解耦。
1.2.3 应用程序框架层(Framework)
应用程序框架层是我们从事Android开发的基础,很多核心应用程序也是通过这一层来实现其核心功能的,该层简化了组件的重用,开发人员可以直接使用其提供的组件来进行快速的应用程序开发,也可以通过继承来实现个性化拓展。
通过Framework层,开发者自由地享有硬件设备的优势,拥有访问本地信息、运行后台服务、设置警示、向状态栏添加通知等功能。
Framework层是由Java语言实现的,在这层里定义的API都是用Java语言编写。但是又因为它包含了JNI的方法,JNI用C/C++编写接口,根据函数表查询调用核心库层里的底层方法,最终访问到Linux内核。Framework层的具体作用如下。
(1)用Java语言编写一些规范化的模块封装成框架,供App层开发者调用开发出具有特殊业务的手机应用。
(2)用Java Native Interface调用core lib层的本地方法,JNI的库是在ART虚拟机启动时加载进去的,ART会直接去寻址这个JNI方法,然后去调用。
隐藏在每个应用后面的是一系列的服务,其中包括以下几项。
活动管理器(Activity Manager):管理各个应用程序生命周期以及通常的导航回退功能。
窗口管理器(Window Manager):管理所有的窗口程序。
内容提供器(Content Provider):使得不同应用程序之间存取或者分享数据。
视图系统(View System):创建应用程序的基本组件,它包括列表(lists)、网格(grids)、文本框(text boxes)、按钮(buttons),甚至可嵌入的Web浏览器。
通告管理器(Notification Manager):应用程序可以在状态栏中显示自定义的提示信息。
包管理器(Package Manager):Android系统内的程序管理。
电话管理器(Telephony Manager):所有的移动设备功能。
资源管理器(Resource Manager):提供应用程序使用的各种非代码资源,如本地化字符串、图片、布局文件、颜色文件等。
位置管理器(Location Manager):供位置服务。
XMPP服务(XMPP Service):供Google Talk服务。
1.2.4 系统运行库(Libraries)
Android包含一些C/C++库,这些库能被Android系统中不同的组件所使用。它们通过 Android应用程序框架层为开发者提供服务。以下是一些核心库。
系统C库:一个从BSD继承来的标准C系统函数库(libc),它是专门为基于embedded linux的设备定制的。
媒体库:基于OpenMAX 库支持多种常用的音频、视频格式回放和录制,同时支持静态图像文件。编码格式包括MPEG4, H.264, MP3, AAC, AMR, JPG, PNG。
Surface Manager:对显示子系统的管理,并且为多个应用程序提 供了2D和3D图层的无缝融合。
LibWebCore:一个最新的web浏览器引擎用,支持Android浏览器和一个可嵌入的web视图。
SGL:底层的2D图形引擎。
3D libraries:基于OpenGL ES 3.0 APIs实现,该库可以使用硬件3D加速(如果可用)或者使用高度优化的3D软加速。
FreeType:位图(bitmap)和矢量(vector)字体显示。
SQLite:一个对于所有应用程序可用、功能强劲的轻型关系型数据库引擎。
1.虚拟机——Dalvik
在Android 4.4以前的系统上,所有的应用程序都是运行在Dalvik中。应用程序每次运行时其中一部分代码都需要机器重新编译。这个过程既消耗时间又要消耗系统资源,所以执行效率难免会降低。但其优点在于这种机制可以让各种各样的应用程序运行在多种硬件架构上。
Dalvik虚拟机是运行Java语言编写应用的必需步骤,正是它将应用编译为字节码(ByteCode),然后再转换成dex(Dalvik可执行格式),最终交给Dalvik虚拟机来运行。
Android 2.2的时候引入了即时编译器(JIT),大大提高了部分应用的执行效率。此后虽然不再有明显的性能提升,但是Google和厂商们一直都在优化Dalvik。事实上,如果执行了Dalvik缓存清理,就会看到“安卓正在更新”(Android is upgrading)的提示,此时幕后就正在对dex文件进行深入优化,同时重建Dalvik缓存分区模式。
Android 4.4以下系统中所运行的传统Dalvik模式主要在于占用更小的ROM存储空间,兼容性更好,另外应用加载打开的时候速度更快些。
Android 包括了一个核心库,该核心库提供了JAVA编程语言核心库的大多数功能。其实绝大部分功能是通过JNI调用Android平台的C/C++开发库。
Dalvik虚拟机用于执行Android应用程序,每一个Android应用程序都在它自己的进程中运行,都拥有一个独立的Dalvik虚拟机实例。Dalvik被设计成一个设备上同时高效地运行多个实例的虚拟系统,如图1.3 所示。
Dalvik虚拟机执行(.dex)的Dalvik可执行文件,该格式文件针对小内存使用做了优化。同时虚拟机是基于寄存器的,所有的类都经由Java编译器编译,然后通过SDK中的“dx”工具转化成.dex格式由虚拟机执行,如图1.4所示。
Dalvik虚拟机依赖于linux内核的一些功能,比如线程机制和底层内存管理机制。
2.虚拟机——ART
ART是Android Runtime的缩写,Google用于替代饱受诟病的Dalvik虚拟机的替代品。其实,ART早在Android KitKat(版本号为4.4)时就已经推出,不过当时它还很不完善,所以被放到设置程序中的“开发者选项”里供一些供感兴趣的开发者使用。
相比Dalvik,ART的处理机制完全不同,它会在应用程序安装时就把程序代码转换成机器语言,让程序成为真正的本地应用。这样做的好处是程序的启动时间被极大地提高,运行速度也会更快。电量消耗得更少,系统行也跟着更加流畅。
当然ART带来一些优点的同时,也会带来以下一些缺点。
(1)由于ART需要应用程序在安装时就把程序代码转换成机器语言,所以这会消耗掉更多的存储空间,但消耗掉空间的增幅通常不会超过应用代码包大小的20%。
(2)由于有了一个转码的过程,所以应用安装时间难免会延长。拿最新的Google+应用为例,其apk格式的安装包大小为28.3MB,但其代码仅为6.9MB。
对于手机用户而言,目前RAM内存普遍是只有1GB或者2GB,入门机有的仅512M内存,而ART模式号称只要512M RAM内存就足以流畅系统体验,另外对于传统安卓机的Dalvik模式运行久了容易出现卡顿的问题,ART模式也得到了很好的解决。
上面说了这么多,那么ART性能到底有多大提升?图1.5所示的ART/Davik性能测试对比数据相信很有说服力。
图1.5中在Nexus 5上所做的ART与Dalvik CPU性能测试中,ART的性能较Dalvik几乎有成倍的增长。
从性能上看ART有很大的提升,那么ART的具体技术有哪些呢?可以总结为以下 两点。
1.采用AOT(Ahead-Of-Time,预编译)编译技术
AOT编译技术的目的是将Java字节码直接转换成目标机器的机器码。对比Dalvik使用的JIT(Just-In-Time)编译技术:
使用JIT时,Java程序在第一次执行某个函数时,需要先将其字节码转成机器码。
使用AOT后,Java程序在安装前,其相关代码就会转成机器码。因此,在执行过程中,就不需要花费额外的时间来做字节码转换工作了。
函数调用的去虚拟化(devirtualize method calls)。Java语言中,接口及虚函数可以很容易地解耦调用模块和实际的实现模块。但在具体执行的时候,虚拟机却要花费不少时间来查找真正的实现函数。函数调用的去虚拟化也是一种常见的优化方法。
快速接口调用。
避免类初始化过程中的检查。
消除异常检查。
如前所述,AOT编译技术将在程序安装时做一些处理,相关流程如图1.6所示。
由图1.6可知,APK应用程序本身的创建不涉及AOT,只是在它的安装过程中,系统将通过dex2oat工具将Dex文件转换成ART平台上的可执行格式ELF。
另外,AOT这套处理框架可以很轻松地拓展到不同的硬件平台,如Intel的X86、MIPS及相应的64位平台等。
当然,AOT在带来益处的同时也会有一些不好的影响,例如。
用于将Dex字节码转换成机器码的dex2oat过程耗时较长,这将延长第一次开机时间和程序安装时间。
dex2oat生成的ELF文件尺寸较大,这会占用不少内置存储空间。
2.更高效的垃圾回收机制(GC)
在了解ART优势之前,我们需要了解什么是垃圾回收(即GC),Java垃圾回收是一项自动化的过程,用来管理程序所使用的运行时内存。通过这一自动化过程,虚拟机解除了程序员在程序中分配和释放内存资源的开销。GC的类型有以下三种。
(1)GC_EXPLICIT:应用主动调用System.gc()产生的GC事件。
(2)GC_FOR_ALLOC:内存分配时,发现可用内存不够时触发的GC事件。
(3)GC_CONCURRENT:给Java层的class分配内存后,计算已分配的大小达到阈值(当前DVM heap size)时会触发的GC事件。
了解了垃圾回收,来看看ART另一项值得称道的特性,大幅改进Java的垃圾回收机制。垃圾回收工作主要包括以下两个步骤。
遍历应用程序的堆栈,枚举该应用所分配的所有对象,然后标记哪些对象还有效(这些有效的对象叫Reachable Objects)。
释放不可到达对象所占据的空间。
Dalvik时代,上述两个步骤均会造成应用程序运行过程的中断,其原理示意如图1.7所示。
讲解上图之前先了解一个概念,GC_CONCURRENT表示触发垃圾收集的原因,有四种情况,见表1.2。
下面再看图1.7,是为了处理垃圾回收,两个虚拟机所处理的过程有所不同,首先看看Dalvik处理的过程。
应用程序试图分配一个对象(由左边的蓝色方框表示),可能会触发虚拟机做一次垃圾回收来释放一些空间。图1.7左图中的两个红色Pause阶段表示在垃圾回收过程中,应用程序会在枚举阶段和标记可到达对象阶段暂停工作。
GC影响最坏的情况出现在GC_FOR_ALLOCS回收时,由虚拟机没有足够空间来分配对象而造成。这种回收会导致整个程序被pause,如图1.7中的右图所示。
了解了Dalvik处理的过程,那么ART是如何优化垃圾回收的呢?总体有下面几个方法。
单独创建了一块名为“Large Object Space”(LOS)的空间用来保存较大尺寸的对象。
创建了一种名叫Runs-of-Slots-Allocator(RosAlloc)的分配器。这种分配器的特点是分配内存时,会采用更细粒度的锁控制。例如由不同的锁来保护不同的对象分配,或者当线程分配一些小尺寸对象时使用线程自己的堆栈,从而可完全不使用锁 保护。
另外,为了防止内存的碎片化,ART还引入了一个名为“Moving garbage collector”的方法,其目的是清理堆栈以减少内存碎片。由于这个工作会导致应用程序长时间中断,所以它必须等程序退到后台时才能开展。
最后,ART在64位平台上也表现良好,图1.8所示为Google给出的一组ART和Dalvik在64位平台上的对比测试结果。
1.2.5 Linux内核
Android 6.0的核心系统服务依赖于Linux 3.10内核、如安全性、内存管理、进程管理、网络协议栈和驱动模型等。Linux作为camera、GSM、Wifi、GPS、触摸屏等设备的载体,管理所有这些软硬件资源,屏蔽了底层的硬件实现细节,Linux 内核也同时作为硬件和软件栈之间的抽象层,为上层软件提供了统一的接口,保持上层软件的独立性。