深入理解Android卷II
HAL(Hardware Abstract Layer 硬件抽象层)
ActivityThread.java 路径位于:\frameworks\base\core\java\android\app\ActivityThread.java
Activity.java 路径位于:\frameworks\base\core\java\android\app\Activity.java
Instrumentation.java 路径位于 :\frameworks\base\core\java\android\app\ActivityThread.java
ActivityManagerService.java 路径位于:\frameworks\base\services\java\com\android\server\am\ActivityManagerService.java
ProcessRecord.java 路径: \frameworks\base\services\java\com\android\server\am\ ProcessRecord.java
TaskRecord.java 路径:\frameworks\base\services\java\com\android\server\am\TaskRecord.java
第1章 搭建Android源码工作环境
本章主要内容
简单介绍系统架构、编译环境的搭建
简单介绍利用Eclipse调试system_process进程的方法
第2章 深入理解Java Binder和MessageQueue
本章主要内容
分析Binder系统的Java层框架
分析MessageQueue
概述
本章作为本书Android分析之旅的开篇,将重点关注两个基础知识点
1、Binder系统在Java世界是如何布局和工作的
2、MessageQueue的新职责
一、Java层中的Binder架构分析
Java层Binder架构总结
1、对于代表客户端的BinderProxy来说,Java层的BinderProxy在Native层对应一个BpBinder对象。凡是从Java层发出的请求,首先从Java层的BinderProxy传递到Native层的BpBinder,继而由BpBinder将请求发送到Binder驱动。
2、对于代表服务端的Service来说,Java层的Binder在Native层有一个JavaBBinder对象。前面介绍过,所有Java层的Binder在Native层都对应为JavaBBinder,而JavaBBinder仅起到中转作用,即把来自客户端的请求从Native层传递到Java层。
3、系统中依然只有一个Native的ServiceManager。
二、心系两界的 MessageQueue
MessageQueue总结
1、消息处理的大家族合照
MessageQueue只是消息处理大家族中的一员。
1.1 Java层提供了Looper类和MessageQueue类,其中Looper类提供循环处理消息的机制,MessageQueue类提供一个消息队列,以及插入、删除和提取消息的函数接口。另外,Handler也是在java层常用的与消息处理相关的类。
1.2 MessageQueue内部通过mPtr变量保存一个Native层的NativeMessageQueue对象,mMessage保存来自Java层的Message消息。
1.3 NativeMessageQueue保存一个native的Looper对象,该Looper从ALooper派生,提供pollOnce和addFd等函数。
1.4 Java层有Message类和Handler类,而Native层对应也有Message类和MessageHandler抽象类。在编码时,一般使用的是MessageHandler的派生类WeakMessageHandler类。
2、MessageQueue处理流程总结
MessageQueue核心逻辑下移到Native层后,极大地拓展了消息处理的范围,总结一下有以下几点:
2.1 MessageQueue继续支持来自Java层的Message消息,也就是早期的Message加Handler的处理方式。
2.2 MessageQueue在Native层的代表NativeMessageQueue支持来自Native层的Message,是通过Native的Message和MessageHandler来处理的。
2.3 NativeMessageQueue还处理通过addFd添加的Request。在后面分析输入系统时,还会大量碰到这种方式。
2.3 从处理逻辑上看,先是处理Native的Message,然后是处理Native的Request,最后才是处理Java的Message。
本章小结
本章先对Java层的Binder架构做了一次较为深入的分析。Java层的Binder架构和Native层Binder架构类似,但是Java层Binder架构在通信上还是依赖Native层的Binder架构,建议想进一步了解Native层Binder架构工作原理的读者,阅读卷I“第6章深入理解Binder”。另外,本章还对MessageQueue进行了较为深入的分析。Android2.2中那个功能简单的MessageQueue现在变得复杂了,原因是该类的核心逻辑下移到Native层,导致现在的MessageQueue除了支持Java层的Message派发外,还新增了支持Native层Message派发以及处理来自所监控的文件句柄的事件。另外,卷I“第5章深入理解常见类”对Android2.2中的MessageQueue和Looper有详细介绍。
第3章 深入理解SystemServer
本章主要内容
分析SystemServer
分析EntropyServer(熵(shang)服务,它和随机数的生成有关)、DropBoxManagerService(该服务和系统运行时日志的存储与管理有关)、DiskStatsService(用于查看和监测系统存储空间)
分析DeviceStorageMonitorServvice(用于查看和监测系统存储空间)、SamplingProfilerService(这个服务是Android4.9新增的)以及ClipboardService(剪贴板服务)、Watchdog(看门狗)
概述
SystemServer是什么?它是Android Java的两大支柱之一。另外一个支柱是专门负责孵化Java进程的Zygote。这两大支柱倒了任何一个,都会导致Android Java的崩溃(所有同Zygote孵化的Java进程都会被销毁,而SystemServer就是由Zygote孵化而来)。若Android Java真的崩溃了,则Linux系统中的进程init会重新启动“两大支柱”以重建Android Java。
SystemServer和系统服务有着重要的关系。Android系统中几乎所有的核心服务都在这个进程中,如ActivityManagerService、PowerManagerService和WindowManagerService等。那么,作为这些服务的本营,SystemServer会是什么样的呢?
3.2 SystemServer分析
SystemServer是由Zygote孵化而来的一个进程,通过ps命令,可知其进程名为system_server
3.2.2 Service群英会
创建一个新的线程ServcerThread,Android平台中众多Service都汇集于此。共有7大类43个Service(包括Watchdog),7大类服务主要包括:
1、位于第一大类的是Android的核心服务,如ActivityManagerService、WindowManagerService等。
2、位于第二大类的是和通信相关的服务,如Wifi相关服务、Telephone相关服务。
3、位于第三大类的是和系统功能相关的服务,如AudioService、MountService、UsbService等。
4、位于第四大类的是BatteryService、VibratorService等服务。
5、位于第五大类的是EntropyService、DiskStatsService、Watchdog等相对独立的服务。
6、位于第六大类的是蓝牙服务。
7、位于第七大类的是和UI紧密相关的服务,如状态栏服务、通知管理服务等。
第4章 深入理解PackageManagerService
本章主要内容
分析PackageManagerService
4.1 概述
PackageManagerService是本书分析的第一个核心服务,也是Android系统中最常用的服务之一。它负责系统中Package的管理,应用程序的安装、卸载、信息查询等。PackageManagerService及客户端的家族如下:
1、IPackageManager接口类中定义了服务端和客户端通信的业务函数,还定义了内部类Stub,该类从Binder派生并实现了IPackageManager接口。
2、PackageManagerService继承自IPackageManager.Stub类,由于Stub类从Binder派生,因此PackageManagerService将作为服务端参与Binder通信。
3、Stub类中定义了一个内部类Proxy,该类有一个IBinder类型(实际类型为BinderProxy)的成员变量mRemote,根据第2章介绍的Binder系统的知识,mRemote用于和服务端PackageManagerService通信。
4、IPackageManager接口中定义了许多业务函数,但是出于安全等方面的考虑,Android对外(即SDK)提供的只是一个子集,该子集被封装在抽象类PackageManager中,客户端一般通过Context的getPackageManager函数返回一个类型为PackageManager的对象,该对象的实际类型是PackaManager的子类ApplicationPackageManager。这种基于接口编程的方式,虽然极大降低了模块之间的耦合性,却给代码分析带来了不小的麻烦。
5、ApplicationPackageManager类继承自PackageManager类。它并没有直接参与Binder通信,而是通过mPM成员变量指向一个IPackageManager.Stub.Proxy类型的对象。
4.2初识PackmageManagerService(PKMS)
PKMS作为系统的核心服务,由SystemServer创建
4.3 PKMS的main函数分析
PKMS构造函数的工作流程大体可分3个阶段
1、扫描目标文件夹之前的准备工作
2、扫描目标文件夹
3、扫描之后的工作
4.3.1 构造函数分析之前期准备工作
1、初识Settings
可用adb shell 登录到自己的手机,然后用busybox提供的ps命令查看进程uid。
2、XML文件扫描
(1) readPermission函数分析
其函数名可猜测它种权限有关,笔者G7手机上/system/etc/permissions/platform.xml目录下的内容
platform.xml文件中主要使用了如下4个标签
permission和group用于建立Linux层gid和Android层permission之间的映射关系。
assign-permission用于指定的uid赋予相应的权限。这个权限由Android定义,用字符串表示
library用于指定系统库。当应用程序运行时,系统会自动为这些进程加载这些库
3、第一阶段工作总结
在继续征程前,先总结一下PKMS构造函数在第一阶段的工作,千言万语汇成一句话;扫描并解析XML文件,将其中的信息保存到特定的数据结构中。
4.3.2 构造函数分析之扫描Package
PKMS构造函数第二阶段的工作就是扫描系统中的APK了,由于需要逐个扫描文件,因些手机上装的程序越多,PKMS的工作量就越大,系统启动速度也就越慢。
PKMS将扫描以下几个目录
1、/system/frameworks;该目录中的文件都是系统库,例如framework.jar、services.jar、framework-res.apk,不过scanDirLI只扫描APK文件,所以framework-res.apk是该目录中唯一"受宠"的文件。
2、/system/app;该目录下全是默认的系统应用,例如Browser.apk、SettingsProvicer.apk等。
3、/vendor/app;该目录中的文件由厂商提供,即全是厂商特定的APK文件,目前市面上的厂商都把自己的应用放在/system/app目录下。
PKMS调用scanDirLI函数进行扫描
(1)scanDirLI函数分析
(2)初会scanPackageLI函数
(3)PackageParse分析:PackageParse主要负责APK文件的解析,即解析APK文件中的AndroidManifest.xml
4、第二阶段工作总结
PKMS构造函数第二阶段的工作任务非常繁重,要创建比较多的对象,所以它是一个耗时耗内存的操作。在工作中,我们一直想优化该流程以加快启动速度,例如延时扫描不重要的APK,或者保存Package信息到文件中,然后在启动时从文件中恢复这些信息以减少APK文件读取并解析XML的工作量。但是一直没有一个比较完满的解决方案,原因有很多。比如APK之间有着比较微妙的依赖关系,因此到底延时扫描哪些APK,尚不能确定。另外,笔者感到比较疑惑的一个问题是:对于多核CPU架构,PKMS可以启动多个线程以扫描不同的目录,但是目前代码中还没有寻找到相关的蛛丝马迹。难道此处真的就不能优化了吗?
4.3.3 构造函数分析之扫尾工作
这部分任务比较简单,就是将第二阶段收集的信息再集中整理一次,比如将有些信息保存到文件中。
4.3.4 PKMS构造函数总结
从流程角度看,PKMS构造函数的功能还算清晰,无非是扫描XML或APK文件,但是其中涉及的数据结构及它们之间的关系却较为复杂。这里有一些建议供读者参与。
1、理解PKMS构造函数工作的3个阶段及其各阶段的工作职责
2、了解PKMS第二阶段工作中解析APK文件的几个关键步骤。
3、了解重点数据结构的名字和大体功能
如果对PKMS的分析就到此为止,则未免有些太小视它了,下面将分析几个重量级的知识点,期望能带领读者全方位认识PKMS。
4.4 APK Installation分析
本节将分析APK的安装及相关处理流程,它可能比读者想象得要复杂,我们的行程从adb install开始
4.4.1 adb install分析
adb install有多个参数,这里仅考虑最简单的,如adb install frameworktest.apk。adb是一个命令,install是它的参数
1、Android4.0新增了APK安装过程中的Verification的功能。其实就是在安装时,把相关信息发送给指定的Verification程序(另外一个APK),由它对要安装的APK进行检查(Verify)。这部分内容在后面分析APK安装时会介绍。目前,标准代码中还没有从事Verification工作的APK。
2、调用pm_command进行安装,这是一个比较有意思的函数,稍后对其进行分析。
3、安装完成后,执行shell rm删除刚才传送给手机的APK文件。为什么会删除呢?因为PKMS在安装过程中会将该APK复制一份到/data/app目录下,所以/data/local/tmp目录下的对应文件可以删除了。
4.4.2 pm分析
手机端的adbd在收到客户端发来的shell pm命令时会启动一个shell,然后在其中执行pm。pm是什么?为什么可以在shell下执行?读者可以通过adb shell 登录到时自己的手机,然后执行pm,看看会发现什么。pm实际上是一个脚本。
注意:android系统中常用的monkeytest、pm、am等(这些都是脚本文件)都是以这种方式启动的,所以严格来说,app_process才是Android Java进程的老祖宗。
Pm解析参数后,最终通过PKMS的Binder客户端调用installPackageWithVerification以完成后续的安装工作,所以,下面进入PKMS看看安装到底是怎么一回事。
4.4.3 installPackageWithVerfication函数分析
1、INIT_COPY处理
2、MCS_BOUND处理
3、handleStartCopy分析
4、handleReturnCode分析
5、POST_INSTALL处理
4.4.4 APK安装流程总结
没想到APK的安装流程竟如此复杂,其目的无非是让APK中的"私人财产"公有化。相比之下,在PKMS构造函数中进行公有化改造就非常简单。另外,如果考虑安装到SD卡的处理流程,那么APK的安装将会更加复杂。
这里要总结APK安装过程中的几个重要步骤
1、安装APK到内部存储空间这一工作流程涉及的主要对象包括:PKMS、DefaultContainerService、InstallParams和FileInstallArgs。
2、此工作流程中每个对象涉及的关键函数。
3、对象之间的调用通过虚线表达,调用顺序通过1\2\3等标明
4.4.5 Verification介绍
PKMS的Verification工作其实就是收集安装包的信息,然后对应的校验者发送广播。Verification的目的主要是在APK安装时先触发校验程序对该APK进行检查,只有检查通过才能进行真正的安装。
4.5 queryIntentActivities分析
PKMS除了负责Android系统中Package的安装、升级、卸载外,还有一项很重要的职责,就是对外提供统一的信息查询功能,其中包括查询系统中匹配某Intent的Activities,BroadCastReceivers或Services等。本节将以查询匹配某Intent的Activities为例,介绍PKMS在这方面提供的服务。
正式分析queryIntentActivites之前,先来认识一下Intent及IntetnFilter。
4.5.1 Intent及IntentFilter介绍
1、Intent介绍
Intent中文是“意图”的意思,它是Android系统中一个很重要的概念,其基本思想来源于日常生活及行为的高度抽象。我们结合用人单位招聘例子介绍Intent背后的思想。
#假设某用人单位现需招聘人员完成某项工作。该单位首先应将其需求发给猎头公司。(这一步可为意图intent,需求传达给猎头)
#猎头公司从其内部的信息库中查找合适的人选。猎头公司除了考虑用人单位的需求外,还需要考虑求职者本身的要求,例如有些求职者对工作地点、加班等有要求。(这一步可以intentFilter)
#二者匹配后,就会得到满足要求的求职者。之后用人单位将工作交给满足条件的人员来完成。
2、IntentFilter介绍
"求职方"需要填写IntentFilter来表达自己的诉求。Android规定了3项内容。
#Action:"求职方"支持的Intent动作(和Intent中的Action对应)。
#Category:"求职方"支持的Intent种类(和Intent的Category对应)。
#Data:"求职方"支持的Intent数据(和Intent的Data对应,包括URI和MIME类型)。
到此,猎头公司已经有了需求,现在又有了求职者的信息,马上要做的工作就是匹配查询。在Android中,该工作称为Intent Resolution。将以Inent Filter列出的3项内容为参与标准,具体步骤如下:
#首先匹配IntentFilter的Action,如果Intent设置的Action不满足IntentFilter的Action,则匹配失败。如果IntentFilter未设定Action,则匹配成功。
#然后检查IntentFilter的Category,匹配方法同Action的匹配,唯一有些例外的是Category为CATEGORY_DEFAULT的情况。
#最后检查Data。Data的匹配过程 比较烦琐,因为它和IntentFilter设置的Data内容有关。Data可以包含两个内容 URI:完整的格式为“scheme://host:port/path”,用于指明服务器网络地址 Datatype:指定数据的MIME类型。
要特别注意的是,uri中也可以携带数据的类型信息,所以在匹配过程中,还需要考虑uri中指定的数据类型。
4.5.2 Activity信息管理
PKMS扫描APK时,PKMS将解析得到的package私有的Activity值加入到自己的数据结构mActivities中保存。
4.5.3 Intent匹配查询分析
1、客户端查询
客户端通过ApplicationPackageManager输出的queryIntentActivities函数向PKMS发起一次查询请求。
2、queryIntentActivities分析
#如果Intent指明了Component,则直接查询该Component对应的ActivityInfo。
#如果Intent指明了Package名,则根据Package名找到该Package,然后再从该Package包含的Activities中进行匹配查询。
#如果上面条件都不满足,则需要在全系统范围内进行匹配查询,这就是queryIntent的工作。
4.6 install及UserManager介绍
4.6.1 install介绍
前面对PKMS构造函数分析时介绍过一个Installer类型的对象mInstaller,它通过socket和后台服务installd交互,以完成一些重要的操作。
1、installd原貌
installd是一个native进程,代码非常简单,其功能就启动一个socket,然后处理来自Installer的命令。
2、dexOpt命令解析
PKMS在需要一个APK或Jar包做dex优化时,会发送dexopt命令给installd,相应的处理函数为do_dexopt;
dexopt进程由android源码/dalvik/dexopt/OptMain.cpp定义
3、movefiles命令分析
PKMS扫描完系统Package后,将发送该命令给installd,movefiles的功能和系统升级有关。
4、doFreeCache
第3章介绍了DeviceStorageMonitorService,当系统空间不足时,DSMS会调用PKMS的freeStorageAndNotify函数进行空间清理。该工作真正实施者是installd,相应的处理命令为do_free_cache。
4.6.2 UserManager介绍
UserManager是Android 4.0新增的一个功能,其作用是管理手机上的不同用户。这一点和PC上的Windows系统比较相似。在目前的Android版本中,该功能尚未完全实现。
4.7 本章学习指导
PKMS 是本书分析的第一个重要核心服务,其中的代码量、关联的知识点、涉及的数据结构都比较多。这里提出一些学习建议供读者参考。
#从工作流程上看, PKMS 包含几条重要的主钱。一条是PKMS 自身启动时构造函数的工作流程,另外几条和APK 安装、卸载相关。每一条主线的难度都比较大,读者可结合日常工作的需求进行单独研究。例如研究如何加快构造函数的执行时间等。
#从数据结构上看, PKMS 涉及非常多的数据类型。如果对每个数据结构进行孤立分析,很容易陷入不可自拔的状态。笔者建议,不妨跳出各种数据结构的具体形态,只从目的及功能角度去考虑。这里需要读者仔细查看前面的重要数据结构及说明示意图。
另外,由于篇幅所限,本章还有一些内容并没有涉及,需要读者在学习完本章内容的基础上自行研究。这些内容包括:
#APK 安装在SD 卡,以及APK 从内部存储空间转移到SD 卡的流程。
#和Package 相关的内容,例如签名管理、dex 优化等。
权限管理相关的内容,读者可自行阅读(Application Security for the Android Platforrn>> 一书。
4.8 本章小结
本章对PackageManagerService进行了较深入的分析,首先分析了PKMS创建时构造的函数的工作流程;接着以APK安装为例,较详细地讲解了这个复杂的处理流程;然后又介绍了PKMS另外一项功能,即根据Intent查找匹配的Activties;最后介绍了与installd和UserManager有关的知识。
第5章 深入理解PowerManagerService(PMS)
本章主要内容
深入分析PowerManagerService
深入分析BatteryService和BatteryStatsService
本章所涉及的源代码文件名及位置
PowerManagerService.java(frameworks/base/services/java/com/android/server/PowerManagerSerνice.java)
5.1 概述
PowerManagerService负责Android系统中电源管理方面的工作。作为系统核心服务之一,PMS与其他服务及HAL层都有交互关系,所以PMS相对PKMS来说,其社会关系更复杂,分析难度也会更大。
先来看直接与PowerManagerService有关的类家族成员,
#PMS从IPowerManager.Stub类派生,并实现了Watchdog.Monitor及LocalPowerManager接口。PMS内部定义了较多的成员变量,在后续分析中,我们会对其中比较重要的成员逐一介绍。
#根据第4章介绍的知识,IPowerManager.Stub及内部类Proxy均由aidl工具处理IPower-Manager.aidl后得到。
#客户端使用PowerManager类,其内部通过代表BinderProxy端的mService成员变量与PMS进行跨Binder通信。
5.2 初识PMS
PMS由SystemServer在ServerThread线程中创建。
5.2.1 PMS构造函数分析
5.2.2 init分析
第二个关键点是ini t 函数,该I函数将初始化PMS 内部的?些重要成员变量, 由于此函数代码较长,此处将分段讨论。二、
从流程角度看γinit 大体可分为3 段。
1、init分析之一
第一阶段的工作可分为3 步:
#对一些成员变量进行赋值。
#调用nativelnit 函数初始化Native 层相关资源。
#调用updateNativePowerStateLocked 更新Native 层的电源状态。这个函数的调用次数较为频繁,后续分析时会讨论。
2、init分析之二
init第二阶段工作将创建两个HandlerThread 对象,即创建两个带消息循环的工作线程。
PMS本身由ServerThread 线程创建,但它会将自己的工作委托给两个线程,它们分别是:
#mScreenOffThread:按Pøwer键关闭屏幕时,屏幕不是突然变黑的,而是一个渐暗的过程。mScreenOffThread线程就用于控制关闭屏幕过程中的亮度调节。
#mHandlerThread:该线程是PMS的主要工作线程。
(1)mScreenOffThread和mHandlerThread分析
(2)initInThread分析
config.xml(包含PMS使用的配置参数)文件的全路径是Android4.0源码中的/frameworks/base/core/res/values/config.xml
3、init分析之三
4、init函数总结
5.2.3 systemReady分析
下面来分析PMS第三阶段的工作。此时系统中大部分服务都已创建好,即将进入就绪阶段。就绪阶段的工作在systemReady中完成。
systemReady主要工作为:
#PMS创建SensorManager,通过它可与对应的传感器交互。关于Android传感器系统,将放到本书后续章节讨论。PMS仅仅启用或禁止特定的传感器,而来自传感器的数据将通过回调的方式通知PMS,PMS根据接收到的传感器事件做相应处理。
#通过setPowerState函数设置电源状态为ALL_BRIGHT(不考虑UseSoftwareAutoBrightness的情况)。此时屏幕及键盘灯都会点亮。关于setPowerState函数,后文再作详细分析。
#调用BatteryStatsService提供的函数,以通知屏幕打开事件,在BatteryStatsService内部将处理该事件。
当系统中的服务都在systemReady中进行处理后,系统会广播一次ACTION_BOOT_COMPLETED消息,而PMS也将处理该广播。
5.2.4 BootComplete处理
5.2.5 初识PowerManagerService总结
这一节向读者展示了PMS的大体面貌,包括:
#主要的成员变量及它们的作用和来历。
#见识了PMS中几个主要的函数,其中有一些将留到后文进行深入分析,现在只需要了解其大概作用即可。
5.3 PMS WakeLock分析
WakeLock是Android提供给应用程序获取电子资源的唯一方法。只要还有地方在使用WakeLock,系统就不会进入休眠状态。
WakeLock的一般使用方法如下:
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
//(1)创建一个WakeLock,注意它的参数
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK,"My Tag");
//(2)获取资源
wl.acquire();
......//完成其他工作
//(3)释放该锁
wl.release();
以上代码中共列出3个关键点,本章将分析前两个,这3个函数都由PMS的Binder客户端的PowerManager使用,所以将本次分析划分为客户端和服务端两大部分。
5.3.1 WakeLock客户端分析
1、newWakeLock分析
客户端仅通过acquireWakeLock函数交互。
5.3.2 PMS acquireWakeLock分析
1、acquireWakeLockLocked分析之一
开始分析之前,有必要先介绍另外一个数据结构,它为PowerManagerService的内部类,名字也为WakeLock;
2、acquireWakeLockLocked分析之二
两个关键函数,一个是gatherState,另外一个是setPowerState,
(1)gatherState分析
gatherState将统计当前系统内部活跃的WakeLock的minState。
(2)setPowerState分析
setPowerState用于设置电源状态
(3)sendNotificationLocked函数分析
sendNotificationLocked函数用于触发SCREEN_ON/OFF广播的发送
(4)acquireWakeLocked第二阶段工作总结
acquireWakeLocked第二阶段工作是处理和屏幕相关的WAKE_LOCK方面的工作(isScreenLock返回为true的情况)。其中一个重要的函数就是setPowerState,该函数将根据不同的状态设置屏幕光、键盘灯等硬件设备。
注意,和硬件交互相关的工作是通过Power类提供的接口完成的。
3、acquireWakeLockLocked分析之三
这部分acquireWakeLocked主要处理WAKE_LOCK为Partial_wake_lock的情况。
当客户端使用Partial_wake_lock时,PMS会调用Power.acquireWakeLock申请一个内核的WakeLock;
4、acquireWakeLock总结
#如果对应的WakeLock 不存在,则创建一个WakeLock 对象,同时将WAKE_LOCK标志转换成对应的minState;否则,从mLocks中查找对应WakeLock对象,然后更新其中的信息。
#当WAKE_LOCK 标志和屏幕有关时,需要做相应的处理,例如点亮屏幕、打开按键灯等。实际上这些工作不仅影响电源管理,还会影响到用户感受,所以其中还穿插了一些和用户体验有关的处理逻辑(如上面注释的mPrevèntScreenOn 变量)。
#当WAKE_LOCK 和PARTEAL_WAKE_LOCK有关时,仅简单调用Power的acquireWakeLock即可,其中涉及和Linux kernel电源管理系统的交互。
5.3.3 Power类及LightService类介绍
根据前面的分析,PMS有时需要进行点亮屏幕、打开键盘灯等操作,为此Android提供了Power类及LightService满足PMS的要求。这两个类比较简单,但是其背后的Kernel层相对复杂一些。
1、Power类介绍
2、LightService介绍
LigthService,这里直接介绍Native层的实现,主要关注HAL层的初始化函数init_native及操作函数setLight_native。
5.3.4 WakeLock总结
相信读者此时已经对WakeLock机制有了比较清晰的认识,此处以flages标签为出发点,对WakeLock的知识点进行总结。
#如果flags和屏幕有关(除PARIAL_WAKE_LOCK外),则需要更新屏幕、灯光状态,其中,屏幕操作通过Power类来完成,灯光操作则通过LightService类来完成。
#如果FLAGS是pARIAL_WAKE_LOCK,则需要通过POWER提供的接口获取Kernel层的WakeLock。
#在WakeLock工作流程中还混杂了用户体验、光传感器、接近传感器等方面的处理逻辑。这部分代码集中体现在setPowerState函数中。
#WakeLock还要通知BatteryStatsService,以帮助其统计电量使用情况。
另外,PMS在JNI层也保存了当前屏幕状态信息,这是通过updateNativePowerStateLocked完成的。
5.4 userActivity及Power按键处理分析
5.4.1 userActivity分析
前面曾经提到过userActivity 的作用,此处举一个例子以加深读者对它的印象。
扛开手机,并解锁进入桌面。如果在规定时间内不操作手机,那么屏幕将变暗,是后关闭。在此过程中,如果触动屏幕,屏幕又会重新变亮。这个触动屏幕的操作将导致userActivity函数被调用。
在上述例子中实际上包含了两方面的内容:
#不操作手机,屏幕将变暗,最后关闭。在PMS这是一个状态切换的过程。
#操作手机,将触发userActivity,此后屏幕的状态将重置。
5.4.2 Power按键处理分析
5.5 BatteryService及BatteryStatsService分析
从前面介绍PMS的代码中发现,PMS和系统中其他两个服务(BatterService及BatteryStatsService)均有交互,其中:
#BatterService提供接口用于获取电池信息,充电状态等。
#BatterStatsService主要用于用电统计,通过它可知谁是系统中的“耗电大户”。
5.5.1 BatteryService分析
1、native_update函数分析
读者可通过dumpsys battery查看自己手机的电池信息
2、processValues分析
获取了电池信息后,BatteryService就要做一些处理,此项工作通过processValues完成。
另外,当电池信息发生改变时,系统会发送uevent事件给BatteryService,此时BatteryService只要重新调用update即可完成工作。
5.5.2 BatteryStatsService(BSS)分析
BSS主要功能是收集系统中各模块和应用进程用电量情况。抽象地说,BSS就是一块“电表”,不过这块“电表”不只是显示总的耗电量,而是分门别类地显示耗电量,力图做到更为精准。
和其它服务不太一样的是,BSS的创建和注册是在ActivityManagerService中进行的。
1、BatteryStatsService介绍
让人大跌眼镜的是,BSS其实只是一个壳,具体功能委托BatteryStatsImpl(简称BSImpl)来实现
2、初识BSImpl
BSImpl功能是进行电量统计,那么是否存在计量工具呢?答案是肯定的,并且BSImpl使用了不止一种计量工具。
(1)计量工具和统计对象介绍
BSImpl一共使用了4种计量工具
#一共有两大类计量工具,Counter用于计数,Timer用于计时
#BSImpl实现了StopwatchTimer(秒表)、SamplingTimer(抽样计时)、Counter(计数器)和SamplingCounter(抽样计数)等4个具体的计量工具。
#BSImpl中还定义了一个Unpluggable接口。
(2)BatteryStatus.Uid介绍
在Android4.0中,和进程相关的用电量统计并非以单个PID为划分单元,而是以Uid为组。
#Wakelock用于统计该Uid对应进程使用WakeLock的用电情况。
#Proc用于统计Uid中某个进程的电量使用情况。
#Pkg用于统计某个特定Package的使用情况,其内部类Serv用于统计该Pkg中Service的用电情况。
#Sensor用于统计传感器用电情况。
3、BSImpl流程分析
(1)构造函数分析
(2)ActivityManagerService和BSS交互
PowerProfile类,它将解析Android4.0源代码/frameworks/base/core/res/xml/power_profile.xml文件,此XML文件存储的是各种操作(和硬件相关)的耗电情况。
(3)BatterService和BSS交互
(4)PowerManagerService和BSS交互
5.5.3 BatteryService及BatteryStatsService总结
本节重点讨论了BatteryService和BatteryStasService。其中,BatteryService和系统中的供电系统交互,通过它可获取电池状态等信息。而BatteryStatsService用于统计系统用电量的情况。就难度而言,BSS较为复杂,原因是Android试图对系统耗电量作非常详细的统计,导致统计项目非常繁杂。另外,电量统计大多采用被动通知的方式(即需要其他服务主动调用BSS提供的onteXXXOn/noteXXXOff函数),这种实现方法一方面加重了其他服务的负担,另一方面影响了这些服务未来的功能扩展。
5.6 本章学习指导
本章最难的部分其实在BSS中,PMS和BatteryService相对较简单。在这3项服务中,PMS是核心。读者在研究PMS时,要注意把握以下几个方面。
#PMS的初期工作流程,即构造函数、init函数、systemReady函数和BootComaleted函数等。
#PMS功能为根据当前系统状态(包括mUserState和mWakeLockState)去操作屏幕和灯光。而触发状态改变的有WakeLock的获取和释放、userActivity函数的调用,因此读者也要搞清楚PMS在这两个方面的工作原理。
#PMS还有一部分功能和传感器有关,其功能无非还是根据状态操作屏幕和灯光。除非工作需要,否则只需要简单了解这部分的工作流程即可。
对BSS来说,复杂之处在于它定义了很多成员变量和数据类型,并且没有一份电量统计标准的说明文档,因此笔者认为,读者只要搞清楚那几个计量工具和各个统计项的作用即可,如果在其他服务的代码中看到和BSS交互的函数,那么只需知道原因和目的即可。
另外,电源管理需要HAL(Hardware Abstract Layer 硬件抽象层)层和Linux内核提供支持.
5.7 本章小结
电源管理系统的核心是PowerManagerService,还包括BatteryService和BatteryStatsService。本章对Android平台中的电源管理系统进行了较详细的分析,其中:
#对于PMS,本章分析了它的初始化流程、WakeLock获取流程、userActivity函数的工作流程及Power按键处理流程。
#BatteryService功能较为简单,读者大概了解即可。
#对于BatteryStatsService,本章对它的内部的数据结构、统计对象等进行了较详细的介绍,并对其工作流程展开了分析。建议读者结合Settinsg应用中的相关代码,加深对各种计量工具及统计对象的理解。
Settings中和电量相关的文件在Android4.0源代码的/packages/apps/Settings/src/com/android/settings/fuelgauge/目录中。
第6章 深入理解ActivityManagerService
本章主要内容:
详细分析ActivityManagerService
本章所涉及的源代码文件名及位置:
SystemServer.java(frameworks/base/services/java/com/android/server/SystemServer.java)
ActivityManagerService.java(frameworks/base/services/java/com/android/server/am/ActivityManagerService.java)
6.1 概述
相信绝大部分读者对本书提到的ActivityManagerService(以后简称AMS) 都有所耳闻。
AMS是Android中最核心的服务,主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调试等工作,其职责与操作系统中的进程管理和调度模块类似,因此它在Android中非常重要。
AMS是本书碰到的第一块"难啃的骨头",涉及的知识点较多。为了帮助读者更好地理解AMS,本章将带领读者按5条不同的线来分析它。
#第一条线: 同其他服务一样,将分析system_server中AMS的调用轨迹。
#第二条线:以am命令启动一个Activity为例,分析应用进程的创建、Activity的启动以及它们和AMS之间的交互知识。
#第三条线和第四条线: 分别以Broadcast和Service为例,分析AMS中Broadcast和Service的相关处理流程。其中,Service的分析将只给出流程图,希望读者能在前面学习的基础上自己分析并掌握相关知识。
#第五条线:以一个Crash的应用进程为出发点,分析AMS如何打理该应用进程的“身后事”。
除了这5条线外,还将统一分析在这5条线中频繁出现的与AMS中应用进程的调度、内存管理等相关的知识。
#AMS由ActivityManagerNative(以后简称AMN)类派生,并实现Watchdog.Monitor和BatteryStasImpl.BatteryCallback接口。而AMN由Binder派生,实现了IActivityManager接口。
#客户端使用ActivityManager类。由于AMS是系统核心服务,很多API不能开放供客户端使用,因此设计者没有让ActivityManager直接加入AMS家族。ActivtyManager类内部通过调用AMN的getDefault函数得到一个ActivityManagerProxy对象,通过它可与AMS通信。
6.2 初识ActivityManagerService
AMS由system_server的ServerThread线程创建,提取它的调用轨迹。
6.2.1 ActivityManagerService的main函数分析
AMS的main函数将返回一个Context类型的对象,该对象在system_server中被其他服务大量使用。
1、AThread分析
虽然AMS的main函数由ServerThread线程调用,但是AMS自己的工作并没有放在ServerThread中去做,而是新创建一个线程,即AThread线程。
(1)AThread分析
(2)AMS的构造函数分析
2、ActivityThread.systemMain函数
ActivityThread是Android Framework中一个非常重要的类,它代表一个应用进程的主线程(对于应用程序来说,ActivityThread的main函数确实是由该进程的主线程执行),其职责就是调度及执行在该线程中运行的四大组件。
(1)attach函数分析
(2)getSystemContext函数分析
(3)systemMain函数总结
systemMain函数调用结束后,我们得到了什么?
#得到一个ActivityThread对象,它代表应用进程的主进程。
#得到一个Context对象,它背后所指向的Application环境与framework-res.apk有关。
#进程:来源于操作系统,是在OS中看到的运行体。我们编写的代码一定要运行在一个进程中。
#Android运行环境:Android努力构筑了一个自己的运行环境。在这个环境中,进程的概念被模糊化了。组件的运行及它们之间的交互均在该环境中实现。
3、ActivityThread.getSystemContext函数分析
4、AMS的startRunning函数分析
5、ActivityManagerService的main函数总结
AMS的main函数的目的有两个
#第一个也是最容易想到的目的是创建AMS对象
#另外一个目的比较隐晦,但是非常重要,那就是创建一个供system_server进程使用的Android运行环境。
根据目前所分析的代码,Android运行环境将包括两个类成员:ActivityThread和ContextImpl(一般用它的基类Context)
6.2.2 AMS的setSystemProcess分析
1、ActivityThread的installSystemApplicationInfo函数
2、ProcessRecord和IApplicationThread
分析ProcessRecord之前,先来思考一个问题:AMS如何与应用进程交互?例如AMS启动一个位于其他进程的Activity,由于该Activity运行在另外一个进程中,因此AMS势必要和该进程进行跨进程通信,答案自然是通过Binder进行通信。
3、AMS的setSystemProcess总结
现在来总结回顾setSystemProcess的工作:
#注册AMS、meminfo、gfxinfo等服务到ServiceManager中。
#根据PKMS返回的ApplicationInfo初始化Android运行环境,并创建一个代表system_server进程的ProcessRecord,从此,system_server进程也并入AMS的管理范围内。
6.2.3 AMS的installSystemProvider函数分析
1、AMS的generateApplicationProvidersLocked函数分析
(1)PMS中queryContentProviders函数分析
(2)关于ContentProvider的介绍
2、ActivityThread的installSystemProvider函数分析
(1)ActivityThread的installProvider函数分析
(2)AMS的publishContentProviders分析
3、AMS的installSystemProviders总结
AMS的installSystemProviders函数其实就是用于启动SettingsProvider,其中比较复杂的是ContentProvider相关的数据结构。
6.2.4 AMS的systemReady分析
作为核心服务,AMS的systemReady会做什么呢?由于该函数内容较多,我们将它的工作分为三个阶段。
1、systemReady第一阶段的工作
systemReady第一阶段的工作并不轻松,其主要职责是发送并处理与PRE_BOOT_COMPLETED广播相关的事情。目前代码中还没有接收该广播的地方,不过从代码中的注释中可猜测到,该广播接收者的工作似乎和系统升级有关。
2、systemReady第二阶段的工作
#杀死那些在AMS还未启动完毕就先启动的应用进程。
#从Settings数据库中获取配置信息,目前只取4个配置参数,分别是:debug_app(设置需要debug的app的名称)、always_finish_activityes(当一个activity不再有地方使用时,是否立即对它执行destroy)、font_scale(用于控制字体放大倍数,这是Android4.0新增的功能)。以上配置项由Settings数据库的System提供。
3、systemReady第三阶段的工作
#调用systemReady设置的回调对象goingCallback的run函数。
#启动那些声明了persistent的APK。
#启动桌面。
先看回调对象goingCallback的run函数的工作
(1)goingCallback的run函数分析
#执行startSystemUi,在该函数内部启动SystemUIService,该Service和状态栏有关。
#调用一些服务的systemReady函数
#启动Vatchdog(看门狗)
注意 在精简ROM时,也不能删除SystemUi.apk。
(2)启动Home界面
如前所述,resumeTopActivtyLocked将启动Home界面,此函数非常重要也比较复杂。
至此,AMS的Service都启动完毕,Home也靓丽登场,整个系统准备完毕,只等待用户的体验了。不过在分析逻辑上还有一点没涉及,那会是什么呢?
(3)发送ACTION_BOOT_COMPLETED广播
原来,在Home启动成功后,AMS才发送ACTION_BOOT_COMPLETED广播。
4、AMS的systemReady总结
systemReady函数完成了系统就绪的必要工作,然后它将启动HomeActivity。至此,Android系统就全部启动了。
6.2.5 初识ActivityManagerService总结
本节所分析的4个关键函数均较复杂,与之相关的知识点总结如下:
#AMS的main函数:创建AMS实例,其中最重要的工作是创建Android运行环境,得到一个ActivityThread和一个Context对象。
#AMS的setSystemProcess函数:该函数注册AMS和meminfo等服务到ServiceManager中。另外,它为system_server创建了一个ProcessRecord对象。由于AMS是Java世界的进程管理及调试中心,要做到对Java进程一视同仁,尽管system_server贵为系统进程,此时也不得不将其并入AMS的管理范围内。
#AMS的installSystemProviders函数:为system_server加载SettingsProvider。
#AMS的systeReady函数:做系统启动完毕前最后一些扫尾工作。该函数调用完毕后,HomeActivity将呈现在用户眼前。
对AMS调用轨迹分析是我们破解AMS的第一条线,希望读者反复阅读,以真正理解其中涉及的知识点,尤其是和Android运行环境及Context相关的知识。
6.3 startActivity分析
本节将重点分析Activity的启动过程,它是五条线中最难分析的一条,只要用心,相信读者能啃动这块“硬骨头”。
6.3.1 从am说起
am和pm(见4.4.2节)一样,也是一个脚本,它用来和AMS交互,如启动Activity、启动Service、发送广播等。其核心文件在Am.java中。
可通过abb shell 登录手机,然后执行am,例:am start -W -n com.dfp.test/.TestActivity
通过am的run函数代码可知,am最终将调用AMS的startActivityAndWait函数来处理这次启动请求。
6.3.2 AMS的startActivityAndWait函数分析
建议读者先阅读SDK文档中关于Activity类定义的几个函数,如startActivity、startActivityForResult、及onActivityResult等。
1、Task、Back Stack、ActivityStack及Launch mode
(1)关于Task及Back Stack的介绍
例如:用户在Android系统上想干的三件事件,分别用A(发短信)、B(拍照片)、C(发邮件)表示,将每一件事情称为一个Task。一个Task还可细分为多个子步骤,即Activity。
提示:为什么叫Activity?它是一个有组织的单元,用于完成某项指定功能。
对于多个Tack的情况来说,系统只支持一个处于前台的Tack,即用户当前看到的Activity所属的Task的,其余Task均处于后台,这些后台Task内部的Activity保持顺序不变。用户可以一次将整个Task挪到后台或者置为前台。
提示:用过Android手机的读者应该知道,长按Home键,系统会弹出近期Task列表,使用户能快速在多个Task间切换。
(2)关于ActivityStack的介绍
如何用代码来实现这一设计呢?此处有两点需要考虑:
#Task内部Activity的组织方式。Android通过先入后出的方式来组织Activity。数据结构中的Stack即以这种方式工作。
#多个Task的组织及管理方式。
Android设计了一个ActivityStack类来负责上述工作,它的组成如下:
#Activity由ActivityRecord表示,Task由TaskRecord表示。ActivityRecord的task成员指向该Activity所在的Task。state变量表示该Activity所处的状态(包括Initializing、Resumed、Paused等状态)。
#ActivityStack用mHistory这个ArrayList保存ActivityRecord。令人大跌眼镜的是,该mHistory保存了系统中所有Task的ActivityRecord,而不是针对某个Task进行保存。
#ActivityStack的mMainStack成员比较有意思,它代表此ActivityStack是否为主ActivityStack。有主必然有从,但是目前系统中只有一个ActivityStack,并且它的mMainStack为true。从ActivityStack的命名可推测,Android在开发之初也想用ActivityStack来管理单个Task中的ActivityRecord,但不知何故现在的代码实现中将所有Task的ActivityRecord都放到mHistory中了,并且依然保留mMainStack。
#ActivityStack中没有成员用于保存TaskRecord。
由上述内容可知,ActivityStack采用数组的方式保存所有Task的ActivityRecord,并且没有成员保存TaskRecord。这种实现方式有优点亦有缺点。
#优点是少了TaskRecord一级的管理,直接以ActivityRecord为管理单元。这种做法能降低管理方面的开销。
#缺点是弱化了Task的概念,结构不够清晰。
ActivityStack还提供findActivityLocked函数以根据Intent及ActivityInfo来查找匹配的ActivityRecord,同样查找也是从mHistory尾端开始。其实,findTaskLocked是根据mHistory中ActivityRecord所属的Task的情况来进行相应的查找工作。
(3)关于Launch Mode的介绍
Launch Mode用于描述Activity的启动模式,目前一共有4种模式,分别是standard、singleTop、singleTask和singleInstance.初看它们,较难理解,实际上不过是Android玩的一个"小把戏"而已。启动模式就是用于控制Activity和Task关系的。
#standard:一个Task中可以有多个相同类型的Activity。注意,此处是相同类型的Activity,而不是同一个Activity对象。例如在Task中有A、B、C、D等4个Activity,如果再启动A类Activity,Task就会变成A、B、C、D、A。最后一个A和第一个A是同一类型,却并非同一对象。另外,多个Task中也可以有同类型的Activity。
#singleTop:当某Task中有A、B、C、D等4个Activity时,如果D想再启动一个D类型的Activity,那么Task将是什么样子呢?在singleTop模式下,Task中仍然是A、B、C、D,只不过D的onNewIntent函数将被调用,而在standard模式下,Task将变成A、B、C、D、D,最后的D为新创建的D类型Activity对象。在singleTop模式下,只有目标Activity当前正好在栈顶时才有效,例如只有启动处于栈顶的D时才有用,如果启动不处于栈顶的A、B、C等,则无效。
#singleTask:在这种模式下,该Activity只存在一个实例,并且将和一个Task绑定。当需要此Activity时,系统会以onNewIntent方式启动它,而不会新建Task和Activity。注意,该Activity虽只有一个实例,但是在Task中除了它之外,还可以有其他的Activity。
#singleInstance:它是singleTask的加强版,即一个Task只能有这么一个设置了singleInstance的Activity,不能再有别的Activity。而在singleTask模式中,Task还可以有其他的Activity。
注意,Android建议一般的应用开发者不要轻易使用最后两种启动模式。因为这些模式虽然名义上为Launch Mode,但是它们也会影响Activity出栈的顺序,导致用户按返回键返回时不一致的用户体验。
除了启动模式外,Android还有其他一些标志用于控制Activity及Task之间的关系。这里只列举其中一部分,详细信息请参阅SDK文档中Intent的相关说明。
#Flag_Activity_New_Task:将目标Activity放到一个新的Task中。
#Flag_Activity_Clear_Task:当启动一个Activity时,先把和目标Activity有关联的Task"干掉",然后启动一个新的Task,并把目标Activity放到新的Task中。该标志必须和Flag_Activity_New_Task标志一起使用。
#Flag_Activity_Clear_Top:当启动一个不处于栈顶的Activity时,先把排在它前面的Activity"干掉"。例如Task有A、B、C、D等4个Activity,要启动B,应直接把C、D“干掉”,而不是新建一个B。
2、ActivityStack的startActivityMayWait函数分析
startActivityMayWait函数的目的是启动com.dfp.test.TestActivity,假设系统之前没有启动过该Activity,本例最终的结果将是:
#由于在am中设置了Flag_Activity_New_Task标志,因此除了会创建一个新的ActivityRecord外,还会创建一个TaskRecord。
#还需要启动一个新的应用程序以加载并运行com.dfp.test.TestActivity的一个实例。
#如果TestActivity不是Home,还需要停止当前正在显示的Activity。
我们可将这个函数分三部分进行介绍,下面先来分析第一部分
(1)startActivityMayWait分析之一
startActivityMayWait第一阶段的工作内容相对较简单,主要包括以下几方面:
#首先需要通过PKM查找匹配该Intent的ActivityInfo.
#处理Flag_cant_save_state的情况,但系统目前不支持此情况
#另外,获取调用者的pid和uid.由于本例的caller为null,故所得到的pid和uid均为am所在进程的uid和pid。
(2)startActivityMayWait分析之二
启动Activity的核心函数是startActivityLocked,该函数异常复杂,后面将用一节专门分析。
(3)startActivityMayWait分析之三
第三阶段的工作就是根据返回值做一些处理,那么res返回成功(即res==IActivityManager.START_SUCESS的时候)后为何还需要等待呢?
这是因为目标Activity要运行在一个新应用进程中,就必须等待那个应用进程正常启动并处理相关请求。注意,只有am设置了-W选项,才会进入wait状态
6.3.3 startActivityLocked分析
startActivityLocked是startActivityMayWait第二阶段的工作重点,该函数有点长。ActivityStac.java::startActivityLocked
startActivityLocked函数的主要工作包括:
#处理sourceRecord及resultRecord.其中,sourceRecord表示发起本次请求的Activity,resultRecord表示接收处理结果的Activity(启动一个Activity肯定需要它完成某项事情,当目标Activity将事情成后,就需要告知请求者该事情的处理结果)。在一般情况下,sourceRecord和resultRecord应指向同一个Activity.
#处理app switch。(检查调用进程是否有权限切换Application)如果AMS当前禁止app switch,则只能把本次启动请求保存起来,以行动允许app switch时再处理。从代码中可知,AMS在处理本次请求前,会先调用doPendingActivityLaunchrLocked函数,在该函数内部将启动之前因系统禁止app switch而保存的Pending请求。
#调用startActivityUncheckedLocked处理本次Activity启动请求。
先来看app switch,它虽然是一个小变量,但是意义重大。
1、关于resume/stopAppSwitches的介绍
AMS提供了两个函数,用于暂时(注意,是暂时)禁止App切换。为什么会有这种需求呢?因为当某些重要(例如设置账号等)Activity处于前台(即用户当前所见的Activity)时,不希望系统因用户操作之外的原因而切换Activity(例如恰好此时收到来电信号而弹出来电界面)。
先来看stopAppSwitches。ActivityManagerService.java::stopAppSwitches
以上代码中有两点需要注意:
#此处控制机制名为app switch ,而不是activity switch 。为什么呢?因为如果从受保护的Activity 中启动另一个Activity ,那么这个新Activity 的目的应该是牡对同一任务,这次启动就不应该受app switch的制约,反而应该对其大开绿灯。目前,在执行Settings中设置设备策略(DevicePolicy)时就会stopAppSwitch。
#执行stopAppSwitch后号,应用程序应该调用resumeAppSwitches以允许app switch, 但是为了防止应用程序有意或无意忘记resume app switch,系统设置了一个超时时间( 5 秒),过了此超时时间,系统将处理相应的消息,其内部会resume app switch。
再来看resumeAppSwitches函数.ActivityManagerService.java::resumeAppSwithces。
在resumeAppSwitches中只设置mAppSwitchesAllowedTime的值为0,它并不处理在stop的resume这段时间内积攒起的Pending请求,那么这些请求是在何时被处理的呢?
#从前面代码可知,如果在执行resume app switch后,又有新的请求需要处理,则先处理那些pending请求(调用doPendingActivityLaunchesLocked).
#在resumeAppSwitches中并未撤销stopAppSwitches函数中设置的超时消息,所以在处理那些超时消息的过程中,也会处理pendign的请求。
在本例中,由于不考虑app switch的情况,那么接下来的工作就是调用startActivityUncheckedLocked函数本处理本次Activity的启动请求。此时,我们已创建了一个ActivityRecord用于保存目标Activity的相关信息。
2、startActivityUncheckedLocked函数分析
startActivityUncheckedLocked函数很长,但是目的比较简单,即为新创建的ActivityRecord找到一个合适的Task.本例最终的结果是创建一个新的Task,其中startActivityUncheckedLocked函数的处理逻辑却比较复杂,对它的分析可分为三个阶段,下面先看第一阶段。
(1)startActivityUncheckedLocked分析之一
startActivityUncheckedLocked第一阶段的工作还算简单,主要确定是否需要为新的Activity创建一个task,即是否设置Flag_Activity_New_Task标志。
(2)startActivityUncheckedLocked分析之二
在本例中,目标Activity首次登场,所以前面的逻辑处理都没有起作用,建议读者根据具体情况分析该段代码
(3)startActivityUncheckedLocked分析之三
startActivityUncheckedLocked的第三阶段工作也比较复杂,不过针对本例,它将创建一个新的TaskRecord,并调用startActivityLocked函数进行处理。
(4)startActivityLocked函数分析 ActivityStack.java::startActivityLocked
(5)startActivityUncheckedLocked总结
创建ActivityRecord和TaskRecord,并将ActivityRecord添加到mHistory末尾,然后调用resumeTopActivityLocked启动它。
3、resumeTopActivityLocked函数分析 ActivityStack.java::resumeTopActivityLocked
resumeTopActivityLocked函数中有两个非常重要的关键点:
#如果mResumedActivity不为空,则需要先暂停(pause)这个Activity。由代码中的注释可知,mResumedActivity代表上一次启动的(即当前正显示的)Activity.现在要启动一个新的Activity,须先停止当前Activity,这部分工作由startPausnigLocked函数完成。
#mResumedActivity什么时候为空呢?当然是在启动全系统第一个Activity时,即启动Home界面的时候。除此之外,该值都不会为空。
resumeTopActivityLocked最后将调用另外一个startSpecificActivityLocked,该函数将真正创建一个应用进程。
(1)startSpecificActivityLocked分析 ActivityStack.java::startSpecificActivityLocked
AMS的startProcessLocked函数将创建一个新的应用进程,下面分析这个函数
(2)startProcessLocked分析:ActivityManagerService.java::startProcesLocked
在以上代码中列出两个关键点,其中第一个和Flag_From_Background有关,相关知识点如下:
#Flag_From_Background标志发起这次启动的Task属于后台任务。很显然,手机中没界面供用户操作位于后台Task中的Activity.如果没有设置该标志,那么这次启动请求就是由前台Task因某种原因而触发的(例如,用户单击某个按钮).
#如果一个应用进程在1分钟内连续崩溃超过2次,则AMS会将其ProcessRecord加入所谓的mBadProcesses中。一个应用崩溃后,系统会弹出一个警告框以提醒用户。但是,如果一个后台Task启动了一个"Bad Process",然后该Process崩溃,结果弹出一个警告框,那么用户就会觉得奇怪:“为什么突然弹出一个框?”因此,此处将禁止后台Task启动"Bad Process"。如果用户主动选择启动(例如,单击一个按钮),则不能禁止该操作,并且要把应用进程从mBadProcesses中移除,以给它们"重新做人"人机会。当然,若作为测试工作者,要是该应用每次启动后都会崩溃,就需要其不停地去启动该应用以达到测试的目的。
提示:这其实是一种安全机制,防止不健全的程序不断启动可能会崩溃的组件,但是这种机制并不限制用户的行为。
第二个关键点,即另一个startProcessLocked函数
startProcessLocked通过发送消息给Zygote以派生一个应用进程,读者仔细研究所发消息的内容,会发现此处并未设置和Activity相关的信息,也就是说,该进程启动后,将完全不知道自己干什么,怎么办?下面就此进行分析。
4、startActivity分析之半程总结
到此,我们已经走完了startActivity分析之旅的一半路程,一路走来,我们越过了很多“险滩恶途”。此处用图6-14(见书)来记录前半程的各个关键点。
5、应用进程的创建及初始化
如前所述,应用进程的入口是ActivityThread的main函数,它是在主线程中执行的,其代码请见ActivityThread.java::main
我们知道,AMS创建一个应用进程后,会设置一个超时时间(一般是10秒)。如果超过这个时间,应用进程还没有和AMS交互,则断定该进程创建失败。所以,应用进程启动后,需要尽快和AMS交互,即调用AMS的attchApplication函数。在该函数内部将调用attachApplicationLocked,所以此处直接分析attachApplicationLocked.
(1)attchApplicationLocked分析之一 ActivityManagerService.java::attachApplicationLocked
attchApplicationLocked第一阶段的工作比较简单:
#设置代表该应用进程的ProcessRecord对象的一些成员变量,如用于和应用进程交互的thread对象、进程调度优先级及oom_adj的值等。
#从消息队列中撤销PROC_START_TIMEOUT_MSG.
至此,该进程启动成功,但是这一阶段的工作仅针对进程本身(如设置调度优先级,oom_adj等),还没有涉及和Activity启动相关的内容,这部分工作将在第二阶段完成。
(2)attchApplicationLocked分析之二
由以上代码可知,第二阶段的工作主要是为调用ApplicationThread的bindApplication做准备,将在后面的章节中分析该函数的具体内容。
(3)attchApplicationLocked分析之三
attchApplicationLocked第三阶段的工作就是通知应用进程启动Activity和Service等组件,其中用于启动Activity的函数是ActivityStack.realStartActivityLocked.
(4)ApplicationThread的bindApplication分析 ActivityThread.java::bindApplication
由以上代码可知,ApplicationThread接收到来自AMS的指令后,会将指令中的参数封装到一个数据结构中,然后通过发送消息的方式转交给主线程去处理。Bind_Application最终将由handleBinApplication函数处理。该函数并不复杂,但是其中有些点是值得关注的,这些点主要是初始化应用进程的一些参数。
handleBindApplication分析 ActionThread.java::handleBindApplication
由以上代码可知,bindApplication函数将设置一些初始化变量,其中最重要的有:
#创建一个Application对象,该对象是本进程中运行的第一个Application.
#如果该Application有ContentProvider,则应安装它们。
(5)应用进程的创建及初始化总结
本节从应用进程的入口函数main开始,分析了应用进程和AMS之间的两次重要交互,它们分别是:
#在应用进程启动后,需要尽快调用AMS的attachApplication函数,该函数是这个刚创建的应用进程第一次和AMS交互。此时的它还默默"无名",连一个确定的进程名都没有。不过没关系,attachApplication函数将根据创建该应用进程之前所保存的ProcessRecord为其准备一切"手续"。
#attachApplication准备好一切后,将调用应用进程的bindApplication函数,在该函数内部将发送消息给主线程,最终该消息由handleBindApplication处理。handleBindApplication将为该进程设置进程名,初始化一些策略和参数信息等。另外,它还创建一个Application对象,同时,如果该Application声明了ContentProvicer,还需要为该进程安装ContentProvider。
提示:这个流程有点类似生孩子,一般生之前需要到医院去登记,生完后又去注册户口如此这般,这个孩子才会在社会中有合法的身份。
6、ActivityStack realStartActivityLocked分析 ActivityStack.java::realStartActivityLocked
如前所述,AMS调用完bindApplication后,将通过realStartActivityLocked启动Activity.除此之前,要创建完应用进程并初始化Android运行环境(除此之外,连ContentProvider都安装好了)。
在以上代码中有两个关键函数,分别是:scheduleaunchActivity和completeResumeLocked.其中,scheduleLaunchActivity用于和应用进程交互,通知它启动目标Activity.而completeResumeLocked将继续AMS的处理流程。先来看第一个关键函数。
(1)scheduleLaunchActivity函数分析
handleLaunchActivity的工作包括:
#首先调用performLaunchActivity,该在函数内部通过java反射机制创建目标Activity,然后调用它的onCreate及onStart函数。
#调用handleResumeActivity,会在其内部调用目标Activity的onResume函数。除此这外,handleResumeActivity还完成了一件很重要的事情.
根据第2章对MessageQueue的分析,当消息队列中没有其他要处理的消息时,将处理以上代码中通过addIdleHandler添加的Idler对象,也就是说,Idler对象的优先级最低,这是不是说它的工作不重要呢?非也,至少在handlerResumeActivity函数中添加的这个Idler并不简单。
Idler类分析 ActivityThread.java::Idler
由以上代码可知,Idler将为那些已经完成onResume的Activty调用AMS的activityIdle函数。该函数是Activity成功创建并启动的流程中与AMS交互的最后一步。虽然对应用进程来说,Idler处理的优先级最低,但AMS似乎不这么认为,因为它还设置了超时等待,以处理应用进程没有及时调用activityIdle的情况。这个超时等待即由realStartActivityLocked中最后一个关键点completeResumeLocked函数设置。
(2)completeResumeLocked函数分析
由以上代码可知,AMS给了应用进程10秒的时间,希望它在10秒内调用activityIdle函数。这个时间不算长,和前面AMS等待应用进程启动的超时时间一样。所以,笔者有些困惑,为什么要把这么重要的操作放到Idler中去做。
下面来看activityIdle函数,在其内部将调用ActivityStace.activityIdleInternal.
(3)activityIdleInternal函数分析
在activityIdleInternal中有一个关键点,即处理那些因为本次Activity启动而被暂停的Activity.有两种情况需要考虑:
#如果被暂停的Activity处于finishing状态(如Activity在其onPause中调用了finish函数),则调用finishCurrentActivityLocked.
#否则,要调用stopActivityLocked处理暂停的Activity.
此处涉及除AMS和目标进程外的第三个进程,即被切换到后台的那个进程。不过至此,我们的目标Activity终于正式登上了历史舞台。
7、startActivity分析之后半程总结
总结startActivity后半部分的流程,主要涉及目标进程和AMS的交互。如时6-15(见书)
8、startPausingLocked函数分析
根据前面的介绍,当启动一个新Activity时,系统将先进行处理当前的Activity,即调用startPausingLocked函数来暂停当前Activity.
(1)startPausingLocked分析 ActivityStack.java::startPausingLocked
startPausingLocked将调用应用进程的schedulePauseActivity函数,并设置500毫秒的超时时间,所以应用进程需要尽快完成相关处理。和scheduleLaunchActivity一样,schedulePauseActivity将向ActivityThread主线程发送Pause_Activity消息,最终该消息由handlePauseActivity处理。
(2)handlePauseActivity分析
(3)completePauseLocked分析
以上代码最终还是通过resumeTopActivityLocked来启动目标Activity.由于之前设置了mPausingActivity为null.
(4)stopActivityLocked分析
根据前面的介绍,此次目标Activity将走完onCreate、onStart和onResume流程,但是被暂停的Activity才刚走完onPause流程,那么它的onStop什么时候调用呢?答案就在activityIdelInteranl中,它将为mStoppingActivities中的成员调用stopActivityLocked函数。
对应进程的sheduleStopActivity函数将根据visible的情况,向主线程消息循环发送H.Stop_Activity_Hide或H.Stop_Activity_Show消息。不论哪种情况,最终都由handleStopActivity处理。
虽然AMS没有为stop设置超时消息处理,但是严格来说,还是有超时限制的,只是这个超时处理与activityIdleInternal结合起来了。
(5)startPausingLocked总结(见书图6-16)
9、startActivity总结
Activity的启动就介绍到这里。先回顾一下此次旅程。
#行程的起点是am。am是Android中很重要的程序,读者务必要掌握它的用法。我们利用am start命令,发起本次目标Activity的启动请求。
#接下来进入ActivityManagerService和ActivityStack这两个核心类。对于启动Activity来说,这段行程又可细分为两个阶段:
第一阶段的主要工作就是根据启动模式和启动标志找到或创建ActivityRecord及对应的TaskRecord;
第二阶段工作就是处理Activity启动或切换相关的工作。
#然后讨论了AMS直接创建目标进程并运行Activity的流程,其中涉及目标进程的创建,目标进程中Android运行环境的初始化,目标Activity的创建以及onCreate、onStart及onResume等其生命周期中重要函数的调用等相关知识点。
#接着又讨论了AMS先暂停当前Activity,然后再创建目标进程并运行Activity的流程。其中牵扯到两个应用进程和AMS的交互,其难度之大可见一斑。
读者在阅读本节时,务必要区分此旅程中两个阶段工作的重点:
其一是找到合适的ActivityRecord和TaskRcord;
其二是调度相关进程进行Activity切换。
在SDK文档中,介绍最为详细的第一阶段中系统的处理策略,例如启动模式、启动标志的作用等。第二阶段工作其实是与Android组件调度相关的工作。SDK文档只是针对单个Activity进行生命周期方面的介绍。
此处列出几个供读者深入研究的点:
#各种启动模式、启动标志的处理流程。
#Configuration发生变化时Activity的处理,以及在Activity中对状态保存及恢复的处理流程。
#Activity生命周期各个阶段的转换及相关处理。Android2.3以后新增的与 Fragment的生命周期相关的转换及处理。
建议:在研究代码前,先仔细阅读SDK文档相关的内容,以获取必要的感性认识,否则直接看代码很容易迷失方向。
6.4.4 Broadcaset和BroadcastReceiver分析
Broadcast,中文意思为"广播"。它是Android平台中的一种通知机制。从广义角度来说,它是一种进程间通信的手段。有广播,就对应有广播接收者。Android中四大组件之一BroadcaseReceiver即代表广播接收者。目前,系统提供两种方式来声明一个广播接收者。
# 在AndroidManifest.xml中声明<receiver>标签。在应用程序运行时,系统会利用java反射机制构造一个广播接收者实例。本书将这种广播接收者称为静态注册或静态接收者。
# 在应用程序运行过程中,可调用Context提供的registerReceiver函数注册一个广播接收者实例。本书将这种广播接收者称为动态注册者或动态接收者。与之相对应,当应用程序不再需要监听广播时(例如当应用程序退到后台时),则要调用unregisterReceiver函数撤销之前注册的BroadcaseReceiver实例。
当系统将广播派发给对应的广播接收者时,广播接收者的onReceive函数会被调用。在此函数中,可对该广播进行相应处理。
另外,Android定义了3种不同类型的广播发送方式,它们分别是:
# 普通广播发送方式,由sendBroadcast及相关函数发送。以工作中的场景为例,当程序员们正在埋头工作之时,如果有人大喊一声"吃午饭去",前刻还在专心编码的人会马上站起来走向餐厅。这种方式即为普通广播发送方式,所有对"吃午饭"感兴趣的接收者都会响应。
# 串行广播发送方式,即ordered广播,由sendOrdedBroadcast及相关函数发送。在该类型方式下,按接收者的优先级将广播一个一个地派发给接收者。只有等上一个接收者处理完毕后,系统才将该广播派发给一下个接收者。其中,任意一个接收者都可中止后续的派发流程。还是以工作的场景为例:经常有项目经理(PM)深夜组织一帮人跟踪bug的状态。PM看见一个BUG,问某程序员,”这个bug你能改吗“?如果得到的答案是”暂时不会“或”暂时没时间“,他会将目光转向一下个神情轻松者,直到找到一个担当者为止。这种方式即为orderd广播发送方式,很明显,它的特点是”一个一个来“。
# sticky(粘)广播发送方式,由sendStickyBroadcast及相关函数发送。Sticky的意思是"粘",其背后有一个很重要的考虑。我们举个例子:假设某广播发送者每5秒发送一次携带自己状态信息的广播,此时某个应用进程注册了一个动态接收者来监听该广播,那么该接收者的OnReceive函数何时被调用呢?在正常情况下需要等这轮的5 秒周期结束后才调用(因为发送者在本周期结束后会主动再发一个广播)。而在Sticky模式下,系统将马上派发该广播给刚注册的接收者。注意,这个广播是系统发送的,其中存储的是上一次广播发送者的状态信息。也就是说,在 Sticky模式下,广播接收者能立即得到一个广播,而不用等到这一周期结束。其实就是系统会保存Sticky的广播,当有新广播接收者来注册时,系统就把Sticky广播发给它。
下面将以动态广播接收者为例,分析Android对广播的处理流程。
6.4.1 registerReceiver流程分析
1、ContextImpl.registerReceiver分析android.app.ContextImpl
registerReceiver函数用于注册一个动态广播接收者,该函数在Context.java中声明。根据本章前面对Context家族的介绍(见书图6-3)可知,其功能最终将通过ContextImpl类的registerReceiver函数来完成。此处可直接去看ContextImpl是如何实现此函数的。
殊途同归,最终的功能由registerReceiverInternal来完成
以上代码列出了两个关键点:其一是准备一个IIntentReceiver对象;其二是调用AMS的registerReceiver函数。
先来看IIntentReveiver,它是一个Interface,(见书图6-17)
由图6-17可知:
# BroadcasetReceiver内部有一个PendingResult类。该类是Android2.3以后新增的,用于异步处理广播消息。例如,当BroadcastReceiver收到一个广播时,其onReceive函数将被调用。一般都是在该函数中直接处理该广播。不过,当该广播处理比较耗时,还可采用异步的方式进行处理,即先调用BroadcastReveiver的goAsync函数得到一个PendingResult对象,然后将该对象放到工作线程中去处理,这样onRecevice函数就可以立即返回而不至于耽误太长时间(这一点对于onReceive函数被主线程调用的情况尤为有用)。在工作线程处理完这条广播后,需调用PendingResult的finish函数来完成整个广播的处理流程。
# 广播由AMS发出,而接收及处理工作却在另个一个进程中进行,整个过程一定涉及进程间通信。虽然在BroadcastReceiver中定义了一个广播接收者,但是它与Binder没有任何关系,故其并不直接参与进程间通信。与之相反,IIntentReceiver接口则和Binder有密切关系,故可推测广播的接收是由IIntentReceiver接口来完成的。确实,在整个流程中,首先接收到来自AMS的广播的将是该接口的Bn端,即LoadedApk.ReceiverDispather的内部类InnerReceiver。
接收广播的处理将放到本节最后再来分析,下面先来看AMS的registerReceiver函数。
2、AMS的registerReceiver分析
AMS的registerReceiver函数比较简单,但是由于其中将出现一些新的变量类型和成员,因些接下来按分两部分进行分析。
(1)registerReceiver分析之一 ActivityManagerService.java::registerReceiver
registerReceiver的返回值是一个Intent,它指向一个匹配过滤条件(由filter参数指明)的Sticky Intent.即使有多个符合条件的Intent,也只返回一个。
BroadcastFilter及相关成员变量(见书图6-18)
结合代码,对图6-18中各数据类型和成员变量的作用及关系的解释如下:
# 在AMS中,BroadcastReceiver的过滤条件由BroadcastFilter表示,该类从IntentFilter派生。由于一个BroadcastReceiver可设置多个过滤条件(即多次为同一个BroadcastReceiver对象调用registerReceiver函数以设置不同的过滤条件),故AMS使用ReceiverList(从ArrayList<BroadcastFilter>派生)这种数据类型来表达这种一对多的关系。
# ReceiverList除了能存储多个BroadcastFilter外,还应该有成员指向某一个具体BroadcastReceiver。如果不这样,那么又是如何知道到底是哪个BroadcastReceiver设置的过滤条件呢?前面说过,BroadcastReceiver接收广播是通过IIntetnReceiver接口进行的,故ReceiverList中有receiver成员变量指向IIntentReceiver。
# AMS提供mRegisterReceiver用于保存IIntentReceiver和对应ReceiverList的关系。此外,AMS还提供mReceiverResolver变量用于存储所有动态注册的BroadcastReceiver所设置的过滤条件。
清楚这些成员变量和数据类型之间的关系后,接着来分析registerReceiver第二阶段的工作。
(2)registerReceiver分析之二
这一阶段的工作用一句话就能说清楚:为每一个满足IntentFilter的Sticky的Inent创建一个BroadcastRecord对象,并将其保存到mParllelBroadcasts数组中,最后,根据情况调度AMS发送广播。
从上边的描述中可以看出,一旦存在满足条件的Sticky的Intent,系统需要尽快调度广播发送。说到这里,想和读者分享一种笔者在实际工作中碰到的情况。
我们注册了一个BroadcastReceiver,用于接收USB的连接状态。在注册完后,它的onReceiver函数很快就会被调用。当时笔者的一些同事认为是注册操作触发USB模块又发送了一次广播,却又感到有些困惑,USB模块应该根据USB的状态变化去触发广播发送,而不应理会广播接收者的注册操作,这到底是怎么一回事呢?
这种困惑的原因是,对于Sticky的广播,一旦有接收者注册,系统会马上将该广播传递给它们。
和ProcessRecord及ActivityfRecord类似,AMS定义了一个BroadcastRecord数据结构,用于存储和广播相关的信息,同时还有两个成员变量,它们作用和关系如图(见书6-19)。
在代码中,registerReceiver将调用scheduleBroadcastsLoceked函数,通知AMS立即着手开展广播发送工作,在其内部就发送Broadcast_Intent_Msg消息给AMS。相关的处理工作将放到后面来讨论。
6.4.2 sendBroadcast流程分析 ContextImpl.java::sendBroadcast
在SDK中同样定义了几个函数用于发送广播。不过,根据之前的经验,最终和AMS交互的函数可能通过一个接口就能完成。AMS的broadcastIntent函数的主要工作将交由AMS的broadcastIntentLocked来完成,此处直接分析broadcastIntentLocked。
1、broadcastIntentLocked分析之一 ActivityManagerService.java::broadcastIntentLocked
第一阶段的工作主要是处理一些特殊的广播消息。处理Time_Zone变化广播、处理Clear_Dns_Cache广播、处理Proxy_Change广播
2、broadcastIntentLocked分析之二
第二阶段的工作有两项:
# 查询满足条件的动态广播接收者及静态广播接收者。
# 当本次广播不为ordered时,需要尽快发送该广播。另外,非ordered的广播都被AMS保存在mParallelBroadcasts中。
3、broadcastIntentLocked分析之三
由以上代码可知,AMS将动态注册和静态注册者都合并到receiver中去了。注意,如果本次广播不是ordered,那么表明动态注册者就已经在第二阶段工作中被处理了。因此在合并时,将不会有动态注册者被加到receivers中。最终所创建的广播记录存储在mOrderedBroadcasts中,也就是说,不管是否串行发送,静态接收者对应的广播记录都将保存在mOrderedBroadcasts中。
为什么不将它们保存在mParallelBroadcasts中呢?结合代码会发现,保存在mParallelBroadcasts中的BroadcastRecord所包含的都是动态注册的广播接收者信息,这是因为动态接收者所在的进程是已经存在的(如果该进程不存在,如何去注册动态接收者呢?),而静态广播接收者就不能保证它已经和一个进程绑定在一起了(静态广播接收者此时可能还仅仅是在AndroidManifest.xml中声明的一个信息,相应的进程并未创建和启动)。根据前一节分析的Activity启动流程,AMS还需要进行创建应用进程,初始化Android运行环境等一系列复杂的操作。读到后面的内容时你会发现,AMS将在一个循环中逐条处理mParallelBroadcasts中的成员(即派发给接收者)。如果将静态广播接收者也保存到mParallelBroadcasts中,会有什么后果呢?假设这些静态接收者所对应的进程全部未创建和启动,那么AMS将在那个循环中创建多个进程!这样,系统压力一下就上去了。所以,对于静态注册者,它们对应的广播记录都被放到mOrderedBroadcasts中保存。AMS在处理这类广播信息时,一个进程一个进程的处理,只有处理完一个接收者,才继续下一个接收者。这种做法的好处是,避免了惊群效应的出现,坏处则是延时较长。
下面进入AMS的Broadcast_Intent_Msg消息处理函数,看看情况是否如上所说。
6.4.3 Broadcast_Intent_Msg消息处理函数
Broadcast_Intent_Msg消息将触发processNextBroadcast函数,下面分阶段来分析该函数。
1、processNextBroadcast分析之一 \frameworks\base\services\java\com\android\server\am\ActivityManagerService.java::processNextBroadcast
deliverToRegisteredReceiverLocked函数的功能就是派发广播给接收者
下面来看performReceiveLocked函数
对于动态注册者而言,在大部分情况下会执行上述代码中的if分支,所以应用进程ApplicationThread的scheduleRegisteredReceiver函数将被调用。稍后再分析应用进程的广播处理流程。
2、processNextBroadcast分析之二
至此,processNextBroadcast已经在一个while循环中处理完mParallelBroadcasts的所有成员了,实际了,这种处理方式也会造成惊群效应,但影响相对较少。这是因为对于动态注册都来说,它们所在的应用进程已经创建并初始化成功。此处的广播发送只是调用应用进程的一个函数而已。相比于创建进程,再初始化Android运行环境所需要的工作量,调用scheduleRegisteredReceiver的工作就比较轻松了。
下面来看processNextBroadcast第二阶段的工作。
processNextBroadcast第二阶段的工作比较简单:
# 首先根据是否处于pending状态(mPendingBrocast不为null)进行相关操作。读者要认真体会代码中的重要说明。
# 处理超时的广播记录。这个超时时间是2*Broadcast_TimeOut*numReceivers。Broadcast_TimeOut默认为10秒,初始化Android运行环境等"重体力活",故此处超时时间还乘以一个固定倍数2。
3、processNextBroadcast分析之三
下面来看processNextBroadcast第三阶段的工作。
对ProcessNextBroadcast第三阶段的工作总结如下:
# 如果广播接收者为动态注册对象,则直接调用deliverToRegisteredReceiverLocked处理它。
# 如果广播接收者为静态注册对象,并用该对象对应的进程已经存在,则调用processCurBroadcastLocked处理它。
# 如果广播接收者这静态注册对象,并且该对象对应的进程还不存在,则需要创建该进程。这是最糟糕的情况。
此处,不再讨论新进程创建及与Android运行环境初始化相关的逻辑,读者可返回阅读"attachApplicationLocked分析之三",其中有处理mPendingBroadcast的内容。
6.4.4 应用进程处理广播分析
下面来分析当应用进程收到广播后的处理流程,以动态接收者为例。
1、ApplicationThread scheduleRegisteredReceiver函数分析
如前所述,AMS将通过scheduleRegisteredReceiver函数将广播交给应用进程,该函数代码如下:ActivityThread.java::scheduleRegisteredReceiver
就本例而言,receiver对象的真实类型为LoadedApk.ReceiverDispatcher,来看它的performReceive函数
scheduleRegisteredReceiver最终向主线程的Handler投递了一个Args对象,这个对象的run函数将在主线程中被调用。
2、Args.run分析 LoadedApk.java::Args.run
finish的代码很简单,此处不再赘述,在其内部会通过sendFinished函数调用AMS的finishReceiver函数,以通知AMS。
3、AMS的finishReceiver函数分析
不论是ordered还是非ordered广播,AMS的finishReceiver函数都会被调用,它的代码如下:ActivityManagerService.java::finishReceiver
由以上代码可知,finishReceiver将根据情况调度下一次广播发送。
6.4.5 广播处理总结
广播处理的流程及相关知识点还算比较简单,可以用图(见书297页图6-20)来表示本例中广播的处理流程。
在图6-20中,将调用函数所属的实际对象类型标注了出来,其中第11步的MyBroadcastReceiver为本例中所注册的广播接收者。
注意:任何广播对于静态注册者来说,都是ordered,而且该order是全局性的,并非只针对该广播的接收者,故从广播发出到静态注册者的onReceive函数被调用中间经历的这段时间相对较长。
6.5 startService之按图索骥(ji)(按照画像去寻求好马,比喻按照线索寻找,也比喻办事机械、死板)
Serivce是Android的四大组件之一。和Activity,BroadcastReceiver相比,Service定位于业务层逻辑处理,而Activity定位于前端UI层逻辑处理,BroadcastReceiver定位于通知逻辑的处理。做为业务服务提供者,Service自有一套规则,先来看有关Service的介绍。
6.5.1 Service知识介绍
四大组件之一的Service,其定义非常符合C/S架构中的Service的概念,即为Client服务,处理Clicen的请求。在Android中,目前接触最多的是Binder中的C/S架构。在这种架构中,Client通过调用预先定义的业务函数向对应的Service发送请求。作为四大组件之一的Service,其响应Client的请求方式有两种:
# Client通过调用startService向Service端发送一个Intent,该Intent推荐请求信息。而Service的onStartCommand会接收该Intent,并处理之。 该方式是Android平台特有的,借助Intnet来传递请求。
# Client调用bindService函数和一个指定的Service建立Binder关系,即绑定成功后,Client端将得到处理业务逻辑的Binder Bp端。此后Client直接调用Bp端提供的业务函数向Service端发出请求。注意,在这种方式中,Service的onBind函数被调用,如果该Service支持Binder,则需要返回一个IBinder对象给客户端。
以上介绍的是Service响应客户端请求的两种方式,读者务必将两者分清楚。此处,这两种方式还影响Service对象的生命周期,简单总结如下:
# 对于以startService方式启动的Service对象,其生命周期一直延续到stopSelf或stopService被调用为止。
# 对于以bindService方式启动的Service对象,其生命周期延续到最后一个客户端调用完unbindService为止。
注意:生命周期控制一般都涉及引用计数的使用。如果某Service对象同时支持这两种请求方式,那么当总引用计数减为零时,其生命就走向终点。
和Service相关的知识还有,当系统内存不足时,系统如何处理Service。如果Service和UI某个部分绑定(例如类似通知栏中Music播放的信息),那么此Service优先级较高(可通过调用startForeground把自己变成一个前台Service),系统不会轻易杀死这些Service来回收内存。
以上这些内容都比较简单,阅读SDK文档中Service的相关说明即可了解,具体路径为SDK路径 /docs/guide/topics/fundamentals/services.html。网址:http://developer.android.com/guide/components/services.html
本章不分析和Service相关的函数的原因有二:
# Service的处理流程和本章重点介绍的Activity的处理流程差不多,并且Service的处理逻辑更简单。能阅读到此处的读者,想必地拿下Service信心满满。
# "授人以鱼,不如授人以渔"。希望读者在经历过如此大量而又复杂的代码分析考验后,能学会和掌握分析方法。
6.5.2 startService流程图
本节将以startService为分析对象,把相关的流程图描绘出来,旨在帮读者根据该流程图自行研读与Service相关的处理逻辑。startService调用轨迹如图(见书图6-21和6-22)所示。
图6-21列出了和startService相关的调用流程。在这个流程中,可假设Service所对应的进程已经存在。
单独提取图6-21中Service所在进程对H.CREATE_SERVICE等消息的处理流程具体如图6-22所示。
注意:图6-21和图6-22中也包含了bindService的处理流程。在实际分析时,读者可分开研究bindService和startService的处理流程。
6.6 AMS中的进程管理
前面曾反复提到,Android平台中很少能接触到进程的概念,取而代之的是明确定义的四大组件。但是作为运行在Linux用户空间内的一个系统或框架,Android不仅不能脱离进程,反而要大力利用Linux操作系统提供的进程管理机制和手段,更好地为自己服务。作为Android平台中组件运行管理的核心服务,ActivityManagerService当仁不让地接手了这方面的工作。目前,AMS对进程的管理仅涉及两个方面:
# 调节进程的调度优先级和调度策略。
# 调节进程的oom值。
先来看在Linex操作系统中这两方面的进程管理和控制手段。
6.6.1 Linux进程管理介绍(详情请见书300页)
1、Linux进程调度优先级和调度策略
调度优先级和调度策略是操作系统中一个很重要的概念。简而言之,它是系统中CPU资源的管理和控制手段。这又该如何理解?此处进行简单介绍。读者可自行阅读操作系统方面的书籍以加深理解。
# 相对于在OS(操作系统)上运行的应用进程个数来说,CPU的资源非常有限。
# 调度优先级是OS分配CPU资源给应用进程时(即调度应用进程运行)需要参考的一个指标。一般而言,优先级高的进程将更有机会得到CPU资源。
2、关于Linux进程oom_adj的介绍
从Linux kernel 2.6.11开始,内核提供了进程的OOM控制机制,目的是当系统出现内存不足(Out Of Memory,OOM)的情况时,kernel可根据进程的oom_adj值的大小来选择并杀死一些进程,以回收内存。
简而言之,oom_adj标示Linux进程内存资源的优先级,其可取范围从-16到15,另外有一具特殊值-17用于禁止系统在OOM情况下杀死该进程。和nicer值一样,oom_adj的值越高,那么在OOM情况下,该进程越有可能被杀掉。每个进程的oom_adj初值为0。
另外,有必要简单介绍一下Android为Linux Kernel新增的lowmemorykiller(以后简称LMK)模块的工作方式 。LMK的职责是根据当前内存大小去杀死对应oom_adj及以上的进程以回收内存。
6.6.2 关于Android中的进程管理的介绍
前面介绍了Linux操作系统中进程管理(包括调度和OOM控制)方面的API,但AMS是如何利用它们的呢?这就是涉及AMS中的进程管理规则了,这里简单介绍相关规则。
Android将应用进程分为五大类,分别为Forground类、Visible类、Service类、Background类、Empty类。这五大类的划分各有规则。
1、进程分类
(1)Forground类
该类中的进程重要性最高,属于该类的进程包括下面几种情况:
# 含一个前端Activity(即onResume函数被调用过了,或者说当前正在显示的那个Activity).
# 含一个Service,并且该Service和一个前端Activity绑定(例如Music应用包括一个前端界面和一个播放Service,当我们一边听歌一边操作Music界面时,该Service即和一个前端Activity绑定)。
# 含一个调用了startForground的Service,或者该进程的Service正在调用其生命周期的函数(onCreate、onStart或onDestroy)。
# 该进程中有BroadcastReceiver实际正在执行onReceive函数。
(2)Visible类
属于Visible类的进程中没有处于前端的组件,但是用户仍然能看到它们,例如位于一个对话框的Activity界面。目前该类进程包括两种:
# 该进程包含一个仅onPause被调用的Activity(即它还在前台,只不过部分界面被遮盖)。
# 包含一个Service,并且该Service和一个Visible(或Forground)的Activity绑定(从字面意义上看,这种情况不太好和Forground进程中第二种情况区分)。
(3)Service类、Baskground类及Empty类
这三类进程都没有可见的部分,具体情况如下.
# Service进程:该类进程包含一个Service.此Service通过startService启动,并且不属于前面两类进程。这种进程一般在后台默默地干活,例如前面介绍的MediaScannerService。
# Background进程:该类进程包含当前不可见的Activity(即它们的onStop被调用过)。系统保存这引起进程到一个LRU(最近最少使用)列表。当系统需要回收内存时,该列表中那些最近最少使用的进程被杀死。
# Empty进程:这类进程中不包含任何组件。为什么会出现这种不包括任何组件的进程呢?其实很简单,假设该进程仅创建一个Activity,它完成工作后主动调用finish函数销毁(destroy)自己,之后该进程就会成为Empty进程。系统保留Empty进程的原因是当又重新需要它们时(例如用户在别的进程中通过startActivity启动了它们),可以省去fork进程、创建Android运行环境等一系列漫长而艰苦的工作。
建议:可阅读SDK/doc/guide/topics/fundamentals/processes-and-threads.html获取更多信息。http://developer.android.com/guide/components/processes-and-threads.html
2、Process类API介绍
下面介绍Android平台中进程调度和OOM控制的API,它们统一被封装在Process.java中,代码:\core\java\android\os
注意:Process.java中的大多数函数是由JNI层实现的,其中Android在调度策略设置这一功能上还有一些特殊的地方,请阅读system/core/libcutils/sched_policy.c文件。
3、关于ProcessList类和ProcessRecord类的介绍
(1)ProcessList类的介绍
ProcessList类有两个主要功能:
# 定义一些成员变量,这些成员变量描述了不同状态下进程的oom_adj值。
# 在Android4.0之后,LMK的配置参数由ProcessList综合考虑手机总内存大小和屏幕尺寸后再行设置(在Android2.3中,LMK的配置参数在init.rc中由init进程设置,并且没有考虑屏幕尺寸影响)。读者可自行阅读updateOomLevels函数。
(2)ProcessRecord中相关成员变量的介绍
6.6.3 AMS进程管理函数分析
在AMS中,和进程管理有关的函数只有两个,分别是updateLruProcessLocked和updateOomAdjLocked。这两个函数的调用点有多处,本节以attachApplication为切入点,尝试对它们分析。
注意:AMS一共定义了3个updateOomAdjLocked函数,此处将其归一类。
先回顾一下attachApplication函数被调用的情况:AMS新创建一个应用进程,该进程启动后最重要的就是调用AMS的attachApplication。
attachApplication主要工作由attachApplicationLocked完成,故直接分析attachApplicationLocked。代码如下:ActivityManagerService.java::attachApplicationLocked
在以上代码中调用了两个重要函数,分别是updateLruProcessLocked函数和updateOomAdjLocked函数。
1、updateLruProcessLocked函数分析 ActivityManagerService.java::updateLruProcessLocked
根据前方所述,我们知道了系统中所有应用进程(同时包括system_server)的ProcessRecord信息都保存在mPidsSelfLocked成员中。除此之外,AMS还有一个成员变量mLruProcess也用于保存ProcessRecord.mLruProcesses的类型虽然是ArrayList,但其内部成员却是按时ProcessRecord的lruWeight大小排序的。在运行过程中,AMS会根据lruWeight的变化调整mLruProcess成员的位置。
由以上代码可知,updateLruProcessLocked的主要工作是根据app的lruWeight值调整它在数据中的位置,lruWeight值越大,其在数组中的位置就越靠后。如果该app和某些Service(仅考虑通过bindService建立关系的那些Service)或ContentProvider有交互关系,那么这些Service或ContentProvider所在的进程也需要调节lruWeight的值。
2、updateOomAdjLocked函数分析
(1)unpdateOomAdjLocked分析之一
unpdateOomAdjLocked第一阶段工作包含一些较难理解的内容,具体如下:
# 处理hidden adj,划分9个级别
# 根据mLruProcesses中进程个数计算每个级别平均会存在多少进程。在这个计算过程中出现了一个魔数4令人极度费解。
# 利用一个循环从mLruProcesses末端开始对每个进程执行另一个updateOomAdjLocked函数。关于这个函数的内容,我们放到下一节再讨论。
# 判断处于Hidden状态的进程数是否超过限制,如果超过限制,则会杀死一些进程。
(2)unpdateOomAdjLocked分析之二
通过上述代码,可获得两个信息。
# Android4.0增加了新的接口类ComponentCallbacks2,其中只定义了一个函数onTrimMemory,从以上描述中可知,它主要通知应用进程一定的内存释放。
# Android4.0 Settings新增了一个开放人员选项,通过它可控制AMS对后台Activity的操作。
接下来分析在以上代码中出现的针对每个ProcessRecord都调用updateOomAdjLocked函数
3、第二个updateOomAdjLocked分析
主要完成两项工作:
# 调用computeOomAdjLocked计算获得某个进程的oom_adj和调度策略。
# 调整进程的调度策略和oom_adj。
提示:思考一个问题:为何AMS只设置进程的调度策略,而不设置进程的调度优先级?
看来AMS调度算法的核心就在computeOonAdjLocked中。
4、computeOomAdjLocked分析
这段代码较长,其核心思想是综合考虑各种情况以计算进程的oom_adj和调度策略。
computeOomAdjLocked的工作比较琐碎,实际也谈不上什么算法,仅仅是简单地根据各情况来设置几个值。
5、updateOomAdjLocked调用点统计
updateOomAdjLocked调用点很多,这里给出其中一个updateOomAdjLocked(ProcessRecord)函数的调用点统计。如图(见书6-23),这也说明AMS非常关注应用进程的状况。
6.6.4 AMS进程管理总结
本节首先向读者介绍了Linux平台中和进程调度、OOM管理相关的API,然后介绍了AMS如何利用这些API完成Android平台中进程管理方面的工作,从中可以发现,AMS设置的检查点比较密集,也就是说经常会进行进程调度方面的操作。
6.7 APP的Crash处理
在Android平台中,应用进程fork出来后会为虚拟机设置一个未截获异常处理器,即在程序运行时,如果有任何一个线程抛出了未被截获的异常,那么该异常最终会抛给未截获异常处理器去处理。设置未截获异常处理器的代码如下:RuntimeInit.java::commonInit
应用程序有问题是再平常不过的事情了,不过,当抛出的异常没有被截获时,系统又会做什么处理呢?来看UncaughtHandler的代码。
6.7.1 应用进程的Crash处理
UncaughtHandler的代码如下:RuntimeInit.java::UncaughtHandler
6.7.2 AMS的handleApplicationCrach分析
AMS handleApplicationCrash函数的代码如下:ActivityManagerService.java::handleApplicationCrash
上述代码中的crashApplication函数的代码如下:ActivityManagerService.java::crashApplication
以上代码中还有一个关键函数makeAppCrashingLocked,其代码如下:ActivityManagerService.java::makeAppCrashingLocked
当App的Crash处理完后,事情并未就此结束,因为该应用进程退出后,之前AMS为它设置的讣告接收对象被唤醒。接下来介绍AppDeathRecipient binderDied的处理流程。
6.7.3 AppDeathRecipient binderDied分析
1、binderDied函数分析
binderDied函数的代码如下:ActivityManagerService.java::AppDeathRecipient binderDied
最终的处理函数是appDiedLocked,其中所传递的3个参数保存了对应死亡进程的信息。
下面分析appDiedLocked的代码 ActivityManagerService.java::appDiedLocked
以上代码中有一个关键函数handleAppDiedLocked,下面来看它的处理过程。
2、handleAppDiedLocked函数分析
该函数的代码如下:ActivityManagerService.java::handleAppDiedLocked
重点看上边代码中的cleanUpApplicationRecordLocked函数,该函数的主要功能就是处理Service、ContentProvicer及BroadcastReceiver相关的收尾工作。先来看Service方面的工作。
(1)cleanUpApplicationRecordLocked之处理Service
cleanUpApplicationRecordLocked函数首先处理几个对话框(dialog),然后调用killServiceLocked函数做相关处理。作为Service流程的一部分,读者需要深入研究。
(2)cleanUpApplicationRecordLocked之处理ContentProvider
再来看cleanUpApplicationRecordLocked下一阶段的工作,这一阶段的工作主要和ContentProvicer有关。
从以上的描述可知,ContentProvider所在进程和其客户端进程实际上有着非常紧密而隐晦(之所以说其隐晦,是因为SDK中没有任何说明)的关系。在目前软件开发追求模块间尽量保持松耦合关系的大趋势下,Android中的ContentProvider和其客户端这种紧耦合的设计思路似乎不够明智。
(3)cleanUpApplicationRecordLocked之处理BroadcastReceiver
在这段代码中,除了处理BroadcastReceiver方面的工作外,还包括其他方面的收尾工作。最后,如果要重启该应用,则需要调用startProcessLocked函数进行处理。
6.7.4 App的Crash处理总结
分析完整个处理流程,有些读者或许会咋舌。应用进程的诞生是一件很麻烦的事情,没想到应用进程的善后工作居然也很费事,希望各个应用进程能“活得”更稳健点。
图(见书6-24)326页,展示了应用进程Crash后的处理的流程。
6.8 本章学习指导
本章内容较为复杂,即使用了这么长的篇幅来讲解AMS,依然只能覆盖其中一部分内容。读者在阅读本章时,一定要注意文中的分析脉络,以搞清楚流程为主旨。以下是本章思路总结:
# 首先搞清楚AMS初创时期的一系列流程,这对理解Android运行环境和系统启动流程等很有帮助。
# 搞清楚一个最简单的情形下Activity启动所历经的"磨难"。这部分流程最复杂,建议读者在搞清书中所阐述内容的前提下结合具体问题进行分析。
# BoradcastReceiver的处理流程相对简单。读者务必理解AMS发送广播的处理流程,这对实际工作非常有帮助。例如最后在处理一个广播时,发现静态注册的广播接收者收到广播的时间较长,研究了AMS广播发送的流程后,将其改成了动态注册,结果响应速度就快了很多。
# 关于Service的处理流程希望读者根据流程图自行分析和研究。
# AMS中的进程管理这一部分内容最难看懂。此处有多方面的原因,笔者觉得和缺乏相关说明有重要关系。建议读者只了解AMS进程管理的大概面貌即可。另外,建议读者不要试图通过修改这部分代码来优化Android的运行效率。进程调度规则向来比较复杂,只有在大量实验的基础上才能得到一个合适的模型。
# AMS在处理应用进程的Crash及死亡的工作上也是不遗余力的。这部分工作相对比较简单,相信读者能轻松掌握。
6.9 本章小结
本章对AMS进行了有针对性的分析:
# 分析了AMS的创建及初始化过程。
# 以启动一个Activity为例,分析了Activity的启动,应用进程的创建等一系列比较复杂的处理流程。
# 介绍了广播接收者及广播发送的处理流程。Service的处理流程读者可根据流程图并结合代码自行开展研究。
# 还介绍了AMS中的进程管理及相关的知识。重点是在AMS中对LRU、oom_adj及scheduleGroup的调整。
# 最后介绍了应用进程Crash及死亡后,AMS的处理流程。
第7章 深入理解ContentProvider
本章主要内容:
# 深入分析ContentProvider的创建和启动,以及SQLit相关的知识点。
# 深入分析Cursor query和close函数的实现。
# 深入分析ContentResolver openAssetFileDescriptor函数的分析
本章所涉及的源代码文件名及位置:(见书329页)
MediaStore.java(base/core/java/android/provider/MediaStore.java)
ContentResolver.java(base/core/java/android/content/ContentResolver.java)
ContentProvider.java(base/core/java/android/content/ContentProvider.java)
MediaProvider.java(package/providers/MediaProvider/src/java/com/android/MediaProvider/MediaProvider.java)
7.1 概述
ContentProvider简称(CP)、ContentResolver简称(CR)
本章重点分析ContentProvider(CP)、SQLite、Cursor query、close函数的实现及ContentResolver-openAssetFileDescriptor函数。为了帮助读者进一步理解本章的知识点,笔者特意挑选了4条分析路线。
第一条:以客户端进程通过MediaStore.Image.Media类的静态函数query来查询MediaProvider中Image相关信息为入口点,分析系统如何创建和启动MediaProvider。此分析路线着重关注客户端进程、ActivityManagerService及MediaProvider所在进程间的交互。
第二条:沿袭第一条分析路径,但是将关注焦点转换到SQLiteDatabase如何创建数据库的分析上。另外,本条路线还将对SQLite进行相关介绍。
第三条:重点研究Cursor query和close函数的实现细节。
第四条:分析ContentResolver openAssetFileDescriptor函数的实现。
7.2 MediaProvider的启动及创建
第一、二、三条分析路线都将以下面这段示例为参考。
MediaProvider客户端示例。
先介绍一下这段示例的情况:客户端(即运行本示例的进程)查询(query)的目标ContentProvider是MediaProvider,它运行于进程android.process.media中。假设目标进程此时还未启动。
本节的关注点集中在:
# MediaProvider所在进程是如何创建的?MediaProvider又是如何创建的?
# 客户端通过什么和位于目标进程中的MediaProvider交互的?
先来看第一个关键函数getContentResolver。
7.2.1 Context的getContentResolver函数分析 ContextImpl.java::getContentResolver
根据第6章对Context的介绍,Context的getContentResolver最终会调用它所代理的ContextImpl对象的getContentResolver函数,此处直接看ContextImpl的代码。
该函数直接返回mContentResolver,此变量在ContextImpl初始化时创建.
由以上代码可知,mContentResolver的真实类型是ApplicationContentResolver,它是ContextImpl定义的内部类并继承了ContentResolver.
7.2.2 MediaStore.Image.Media的query函数分析
第二个关键函数是在MediaProvider客户端示例中所调用的MediaStore.Image.Media的query函数。MediaStore是多媒体开发中常用的类,其中内部定义了专门针对Image、Audio、Video等不同多媒体信息的内部类来帮助客户端开发人员更好地和MediaProvider交互。这些类及相互之间的关系如图(见书332)7-1所示。
由图可知,MediaStore定义了较多的内部类,我们重点展示作为内部类之一的Image的情况,其中:
# MediaColumns定义了所有与媒体相关的数据库表都会用到的数据库字段,而ImageColumns定义了单独针对Image的数据库字段。
# Image类定义了一个名为Media的内部类用于查询和Image相关的信息,同时Image类还定义了一个名为Thumbnails的内部类用于查询和Image相关的缩略图的信息(在Android平台上,缩略图的来源有两种,一种是Image,另一种是Video,故Image定义了名为Thumbnails的内部类,而Video也定义了一个名为Thumbnails的内部类)。
下面来看Image.Media的query函数。MediaStore.java::Media.query
Image.Media的query函数直接调用CR的query函数,虽然CR的真实类型是ApplicationContentResolver,但是此函数却由其基类CR实现。
1、ContentResolver的query函数分析 ContentResolver.java::query
CR的query将调用acquireProvider,该函数定义在CR类中
如上所述,acquireProvider由CR的子类实现,在本例中该函数由ApplicationContentResolver定义。ContextImpl.java::acquireProvider
如以上代码所示,最终ActivityThread的acquireProvider函数将被调用,希望它不要再被层层转包了。
2、ActivityThread的acquireProvider函数分析 ActivityThread.java::acquireProvider
在acquireProvider内部调用getProvider得到一个IContentProvider类型对象,该函数非常重要。ActivityThread.java::getProvider
以上代码中让人比较头疼的是其中新出现的几种数据类型,如IContentProvicer、ContentProviderHolder.先来分析AMS的getContentProvider.
3、AMS的getContentProvider函数分析
getContentProvider的功能主要由getContentProviderImpl函数实现,故此处可直接对它进行分析。
(1)getContentProviderImpl启动目标进程 ActivityManagerService.java::getContentProviderImpl
getContentProviderImpl函数较长,可分段来看
以上代码主要是为目标CP(即MediaProvider)创建一个ContentProviderRecord对象。结合第6章的知识,AMS为四大组件都设计了对应的数据结构,如ActivityRecord、BroadcastRecord等。
接着看getContentProviderImpl,其下一步的工作就是启动目标进程,
通过对以上代码的分析发现,getContentProviderImpl将等待一个事件,想必读者也能明白,此处一定是在等待目标进程启动并创建好MediaProvide。目标进程的这部分工作用专业词语来表达就是发布(publish)目标ContentProvider(即本例的MediaProvider).
(2)MediaProvider的创建
根据第6章的介绍,目标进程启动后要做的第一件大事就是调用AMS的attachApplication函数,该函数的主要功能由attachApplicationLocked完成。代码:ActivityManagerService.java::attachApplicationLocked
再来看目标进程bindApplication的实现,其内部最终会通过handleBindApplication函数处理,代码:ActivityThread.java::handleBindApplication
AMS传递过来的ProviderInfo列表将由目标进程的installContentProviders处理,代码:ActivityThread.java::installContentProviders
以上代码列出两个关键点,分别是:
# 调用installProvider得到一个IContentProvider类型的对象。
# 调用AMS的publishContentProviders发布本进程所运行的CP。
在继续分析之前,笔者要特别强调installProvider,该函数既在客户端进程中被调用,又在目标进程中被调用。与客户端进程的调用相比,只在一处有明显的不同:
# 客户端进程调用installProvider函数时,该函数的第二个参数不为null.
# 目标进程调用installProvider函数时,该函数的第二个参数硬编码为null.
不面来看installProvider函数,ActivityThread.java::installProvider
由以上代码可知,installProvider最终返回的是一个IContentProvider类型的对象。对于目标进程而言,该对象是通过调用CP的实例对象的(本例就是MediaProvider)getIContentProvider函数得到的。而对于客户端进程而言,该对象是由installProvider第二个参数传递进来的,那么,这个IContentProvider到底是什么?
(3)IContentProvider的真面目
要说清楚IContentProvider,就要先来看ContentProvider家族的类图,如图(见书340)所示。
(4)AMS pulishContentProvider分析
要把目标进程的CP信息发布出去,需要借助AMS的pulishContentProvider函数,其代码:ActivityManagerService.java:;publishContentProviders
至此,客户端进程将从getContentProvider中返回,并调用installProvider函数。根据前面的分析,客户端进程调用installProvider时,其第二个参数不为null,即客户端进程已经从AMS中得到了能直接和目标进程交互的IContentProvider Bp端对象。此后,客户端就可直接使用该对象向目标进程发起请求。
7.2.3 MediaProvider的启动及创建
回顾一下整个MediaProvider的启动和创建流程,如图7-3(见书342)
整个流程相对简单。读者在分析时只要注意installProvider这个函数在目标进程和客户端进程中被调用时的区别即可。这里再次强调:
# 目标进程调用installProvider时,传递的第二个参数为null,使内部通过java反射机制真正创建目标CP实例。
# 客户端调用installProvider时,其第二个参数已经通过查询AMS得到。该函数真正的工作只不过是引用计数控制和设置讣告接收对象罢了。
到此,客户端进程和目标进程通信的通道IContentProvider已经登场。除此之外,客户端进程和目标CP还建立了非常紧密的关系,这种关系造成的后果就是一旦目标CP进程死亡,AMS会杀死与之有关的客户端进程。回顾一下与之有关的知识点:
# 该关系的建立是在AMS getContentProviderImpl函数中调用incProviderCount完成的,关系的确立以ContentProviderRecorder保存客户端进程的ProcessRecord信息为标志。
# 一旦CP进程死亡,AMS能根据该ContentProviderRecorder中保存的客户端信息找到使用该CP的所有客户端进程,然后再杀死它们。
客户端能否撤销这种紧密关系呢?答案是肯定的,但这和Cursor是否关闭有关。这里先简单描述一下流程:
# 当Cursor关闭时,ContentImpl的releaseProvider会被调用。根据前面的介绍,它最终会调用ActivityThread的releaseProvider函数。
# ActivityThread的releaseProvider函数会导致completeRemoveProvider被调用,在其内部根据该CP的引用计数判断是否需要调用AMS的removeContentProvicer.
# 通过AMS的removeContentProvider将删除对应ContentProviderRecord中此客户端进程的信息,这样一来,客户端进程和目标CP进程的紧密关系就荡然无存了。
至此,本章第一条分析路线就介绍完毕了。
7.3 SQLite创建数据库分析
作为Android多媒体系统中媒体信息的仓库,MediaProvider使用了SQLite数据库来管理系统中多媒体相关的数据信息。作为第二条分析路线,本节的目标是分析MediaProvider如何利用SQLite创建数据库,同时还将介绍和SQLite相关的一些知识点.
下面先来看大名鼎鼎的SQLite及java层的SQLiteDatabase家族。
7.3.1 SQLite及java层的SQLiteDatabase家族
1、SQLite轻装上阵
SQLite是一个轻量级的数据库,它和笔者之产接触的SQLServer或Oracle DB比起来,犹如蚂蚁和大象的区别。它“轻”到什么程序呢?笔者总结了SQLite具有的两个特点:
# 从代码上看,SQLite所有的功能都实现在Sqlite3.c中,而头文件Sqlite3.h(这里的3是SQLite的版本号)定义了它所支持的API。其中,Sqlite3.c文件包含12万行左右的代码,相当于一个中等偏小规模的程序。
# 从使用者角度的来说,SQLite编译完成后将生成一个libsqlite.so,大小仅为300KB左右。
SQLite API的使用主要集中在以下几点上:
# 创建代表指定数据库的sqlite3实例。
# 创建代表一条SQL语句的sqlite3_stmt实例,在使用过程中首先要调用sqlite3_prepare将其和一条代表SQL语句的字符串绑定。如该字符串包含有通配符"?",后续就需要通过sqlite3_bind_xxx函数为通配符绑定特定的值以生成一条完整的SQL语句。最终调用sqlite3_step执行这条语句。
# 如果是查询(即SELECT命令)命令,则需调用sqlite3_step函数遍历结果集,并通过sqlite3_column_xx等函数取出结果集中某一行指定列的值。
# 最后需调用sqlite3_finalize和sqlite3_close来释放sqlite3_stmt实例及sqlite3实例所占据的资源。
SQLite API的使用非常简单明了。不过很可惜,这份简单明了所带来的快捷,只供那些Native层程序开发者独享。对于java程序员,他们只能使用Android在SQLite API之上所封装的SQLiteDatabase家族提供的类和相关API.(参阅frameworks/base/core/java/android/database 目录中的文件),包括庞大而复杂的SQLiteDatabase家族,其成员有61个之多。
2、SQLiteDatabase家族介绍
图(见书347)7-4展示了SQLiteDatabase家族中的几位重要成员。
相关类的说明如下:
# SQLiteOpenHelper是一个帮助(Utility)类,用于方便开发者创建和管理数据库。
# SQLiteQueryBuilder是一个帮助类,用于帮助开发者创建SQL语句。
# SQLiteDatabase代表SQLite数据库,它内部封装了一个Nativie层的sqlite3实例。
# Android提供了3个类,即SQLiteProgram、SQLiteQuery和SQLiteStatement用于描述和SQL语句相关的信息。从图7-4可知,SQLiteProgram是基类,它提供了一些API用于参数绑定。SQLiteQuery主要用于query查询操作,而SQLiteStatement用于query之外的一些操作(根据SDK的说明,如果SQLiteStatement用于query查询,其返回的结果集只能是1行X1列的)。注意,在这3个类中,基类SQLiteProgram将保存一个指向Native层的sqlite3_stmt实例的变量,但是这个成员变量的赋值却和另外一个对开发者隐藏的类SQLiteCompiledSql有关。从这个角度看,可以认为Native层sqlite3_stmt实例的封装是由SQLiteCompiledSql完成的。这方面的知识在后文进行分析时即能见到。
# SQLiteClosable用于控制SQLiteDatabase家族中一些类的实例的生命周期,例如SQLiteDatabase实例和SQLiteQuery实例。每次使用这些实例对象前都需要调用acquireReference以增加引用计数,使用完毕后都需要调用releaseReference以减少引用计数。
下面来看MediaProvider是如何使用SQLiteDatabase的,重点关注SQLite数据库是如何创建的。
7.3.2 MediaProvider创建数据库分析
在MediaProvider中触发数据库的是attach函数,其代码如下:MediaProvicer::attach
以上代码中列出了两个关键点,分别是:
# 构造一个DatabaseHelper对象
# 调用DatabaseHelper对象的getWritableDatabase函数得到一个代表SQLite数据库的SQLiteDatabase对象。
1、DatabaseHelper分析
DatabaseHelper是MediaProvider的内部类,它从SQLiteOpenHelper派生。
(1)DatabaseHelper构造函数分析
DatabaseHelper构造函数的代码如下:MediaProvicer.java::DatabaseHelper
SQLiteOpenHelper作为DatabaseHelper的基类,其构造函数的代码如下:SQLiteOpenHelper.java::SQLiteOpenHelper
上面这些函数都比较简单,其中却蕴含一个较为深刻的设计理念,具体如下:从SQLiteOpenHelper的构造函数中可知,MediaProvider对应的数据库对象(即SQLiteDatabase实例)并不在该函数中创建。那么,代表数据库的SQLiteDatabase实例是何时创建呢?此处使用了所谓的延迟创建(lazy creation)的方法,即SQLiteDatabase实例真正创建的时间是在第一次使用它的时候。也就是第二个关键函数getWritableDatabase.
(2)getWritableDatabase函数分析,代码如下:SQLiteDatabase.java::getWritableDatabase
由以上代码可知,代表数据库的SQLiteDatabase对象是由Context openOrCreateDatabase创建的,下面分析此函数
2、ContextImpl openOrCreateDatabase分析
(1)openOrCreateDatabase函数分析 ContextImpl.java::openOrCreateDatabase
openDatabase将真正创建一个SQLiteDatabase实例 SqiteDatabase.java::openDatabase
其实openDatabase主要就干了两件事情,即创建一个SQLiteDatabase实例,然后调用该实例的dbopen函数。
(2)SQLiteDatabase的构造函数及dbopen函数分析
前面说过,java层的SQLiteDatabase对象会和一个Native层sqlite3实例绑定,从以上代码中可发现,绑定的工作并未在构造函数中进行。实际上,该工作是由dbopen函数完成的:android_database_SQLiteDatabase.cpp:dbopen
从上述代码可知,使用dbopen函数其实就是为了得到Native层的一个sqlite3实例。另外,Android对SQLite还设置了一些与平台相关的函数,这部分后文分析。
3、SQLiteCompiledSql介绍
4、Android SQLite自定义函数介绍
本节将介绍Android在SQLite上自定义的一些函数。一切还得从SQL的触发器说起。
(1)触发器介绍
触发器(Trigger)是数据库开发技术中一个常见的术语。其本质非常简单,就是在指定表上发生特定事情时,数据库需要执行的某些操作。
# 如果没有定义名为images_cleanup的触发器,就创建一个名为images_cleanup的触发器。
# 设置该触发器的触发条件。显然,当对images表执行delete操作时,该触发器将被触发。
# 触发器执行的操作:1、删除表中对应的信息。
(2)register_android_functions介绍
7.3.3 SQLiteDatabase创建数据库的分析总结
本节以MediaProvider创建数据库为入口,对SQLite及java层的SQLiteDatabase进行了介绍。其中,应重点阅读SQLiteTest中的示例代码,通过它可以掌握SQLite API的用法。在此基础了,还介绍了SQLiteDatabase家族并分析了MediaProvider中数据库创建的相关代码。
7.4 Cursor的query函数实现分析
本节将分析CP中另一个比较复杂的知识点,即query函数的实现。下面从CR的query函数开始分析,其代码: ContentResolver.java::query
上面代码揭示了CR的query函数的工作流程:
# 调用远程CP的query函数,返回一个CursorWrapperInner类型的对象。
# 该函数最终给客户端的是一个CursorWrapperInner类型的对象。
Cursor和CursorWrapperInner这两个新出现的数据类型严重干扰了我们的思考。
7.4.1 提取query关键点
1、客户端query关键点
按照以前的分析习惯,碰到Binder调用时会马上转入服务端(即Bn端)去分析,但是这个思想在query函数中行不通,为什么?来看IContentProvider Bp端的query函数,它定义在ContentProviderProxy中,代码如下: ContentProiderNative.java::ContentProviderProxy.query
2、服务端query关键点
根据第2章对Binder的介绍,客户端发来的请求先在Bn端的onTransact中得到处理。代码如下:ContentProviderNative.java::onTransact
和客户端对应,服务端的query处理也比较复杂,在扫清这此复杂之前,应先把客户端和服务端query调用的关键点总结一下。
3、提取query关键点总结
我们提取query两端的调用关键点,如图(见书361)7-5所示。
再来总结一下前面提到的几个“拦路虎”,它们分别是:
# 客户端创建的BulkCursorToCursorAdapter,以及从服务端query后得到的IBulkCursor.
# 服务端创建的CursorToCursorAdapter,以及从子类query函数返回的Cursor.
从名字上看,这几个类都和Cursor有关,所以有必要先搞清MediaProvider-query返回的Cursor到底是什么。
7.4.2 MediaProvider的query分析
本节将分析MediaProvider的query函数。此次分析的焦点不是MediaProvider本身的业务逻辑,而是要搞清query函数返回的Cursor到底是什么,其代码:MediaProvider.java::query
上边代码列出的两个关键点分别是:
# 调用SQLiteQueryBuilder的query函数得到一个Cursor类型的对象
# 调用Cursor类型对象的setNotificationUri函数。从名字上看,该函数是为Cursor对象设置通知URI。和ContentObserver有关的内容留到第8章分析。
下面来看SQLiteQueryBuilder的query函数
1、SQLiteQueryBuilder的query函数分析 代码如下:SQLiteQueryBuilder.java::query
以上代码中又出现一个新类型,即SQLiteCursorDriver,cursor变量是其query函数的返回值。
(1)SQLiteCursorDriver的query函数分析:代码:SQLiteCursorDriver.java::query
SQLiteCursorDriver的query函数的主要功能就是创建一个SQLiteQuery实例和一个SQLiteCursor实例。至此,我们终于搞清了MediaProvider的query返回的游标对象其真实类型是SQLiteCursor.
下面来看SQLiteQuery和SQLiteCursor为何方神圣。
(2)SQLiteQuery介绍
在图7-4中曾介绍过SQLiteQuery,它保存了和查询(即SQL的select命令)命令相关的信息,代码如下:SQLiteQuery.java::构造函数
SQLiteQuery的基类是SQLiteProgram 代码如下:SQLiteProgram.java::构造函数
来看compileAndbindAllArgs函数,其代码是:SQLiteProgram.java::compileAndbindAllArgs
conpileSql函数将绑定java层SQLiteQuery对象到一个Native的sqlite3_stmt实例。根据前文的分析,这个绑定是通过SQLiteCompileSql对象实施的,代码如下:SQLiteProgram.java::compileSql
通过以上分析可以发现,SQLiteQuery将和一个代表select命令的sqlite3_stmt实例绑定。
(3)SQLiteCursor分析 函数的代码如下:SQLiteCursor.java::构造函数
2、Cursor分析
至此,我们已经知道了MediaProvider query返回的游标对象的真实类型了,现在,终于可以请Cursor家族登台亮相了,如图7-6所示。(见书367)
图7-6中元素较多,包含的知识点较为复杂,因此见书。
7.4.3 query关键点分析
本节将按如下顺序分析query函数中的关键点:
# 介绍服务端的CursorToBulkCursorAdapter及其count函数。
# 跨进程共享数据的关键类CursorWindow。
# 客户端的BulkCursorToCursorAdapter及其initialize函数,以及返回给客户端使用的CursorWrapperInner类。
1、CursorToBulkCursorAdapter函数分析
(1)构造函数分析 代码:CursorToBulkCursorAdapter
构造函数简单,看下一个重要函数,即CursorToBulkCursorAdapter的count.该函数返回本次查询结果集所包含的行数。
(2)count函数分析
count最终调用SQLiteCursor的getCount函数,代码:SQLiteCursor.java::getCount
getCount函数将调用一个非常重要的函数,即fillWindow。顾名思义,读者可以猜测到它的功能:将结果数据保存到CursorWindow的那块共享内存中。
2、CursorWindow分析
CursorWindow的创建源于前边代码中对fillWindow的调用。fillWindow的代码如下:SQLiteCursor.java::fillWindow
(1)clearOrCreateLocalWindow函数分析 代码:SQLiteCursor.java::clearOrCreateLocalWindow
调用CursorWindow的构造函数 代码如下:CursorWindow.java::CursorWindow
nativeCreate是一个native函数,其真正实现在android_database_CursorWindow.cpp中,代码:android_database_CursorWindow.cpp::nativeCreate
再来看看CursorWindow的create函数,代码:CursorWindow.cpp::create
由以上代码可知,CursorWindow的create函数将构造一个Native的CursorWindow对象。最终,java层的CursorWindow对象会和此Native的CursorWindow对象绑定。
到此,用于承载数据的共享内存已创建完毕,但我们还没有执行SQL的select语句。这个工作由SQLiteQuery的fillWindow函数完成。
(2)SQLiteQuery fillWindow分析
前面曾说过,SQLiteQuery保存了一个Native层的sqlite3_stmt实例,那么它的fillWindow函数是否就是执行SQL语句后将结果信息填充到CursorWindow中了呢?可以通过以下代码来验证。SQLiteQuery.java::fillWindow
来看nativeFillWindow的实现函数,其代码是:android_database_SQLiteQuery.cpp::nativeFillWindow
通过以上代码可确认,fillWindow函数实现的就是将SQL语句的执行结果填充到了CursorWindow的共享内存中。
(3)CursorWindwo分析总结
本节向读者介绍了CursorWindow相关的知识点。其实,CursorWindow就是对一块共享内存的封装。另外我们也看到了如何将执行Select语句后得到的结果集填充到这块内存中。但是这块内存现在还属于服务端进程,只有客户端进程得到这块内存后,客户端才能真正获取执行select后的结果。那么,客户端是何时得到这块内存的呢?让我们回到客户端进程。
3、(客户端)BulkCursorToCursorAdaptor和CursorWrapperInner分析
客户端的工作是先创建BulkCursorToCursorAdaptor,然后根据远端查询(query)结果调用BulkCursorToCursorAdaptor的intialize函数。代码:BulkCursorToCursorAdaptor.java
由以上代码可知,BulkCursorToCursorAdaptor仅简单保存了来自远端的信息,并没有什么特殊操作。看来客户端进程没有在上面代码的执行过程中共享内存。
客户端通过Image.Media.query函数,将得到一个CursorWrapperInner类型的游标对象。当然,客户端并不知道这么重要的细节,它只知道自己用的接口类Cursor.根据前面的分析,此时客户端通过空上游标对象可与服务端的CursorToBulkCursorAdaptor交互,即进程间Binder通信的通道已经打通。但是此时客户端还未拿到那块至关重要的共享内存,即进程间的数据通道还没打通。那么,数据通道是何时打通的呢?
数据通道打通的时间和lazy creation(延迟创建)有关,即只在使用它时才打通。
4、moveToFirst函数分析
根据前文的分析,客户端从Image.Media.query函数得到的游标对象,其真实类型是CursorWrapperInner。游标对象的使用有一个特点,即必须先调用它的move家族的函数。这家族包括moveToFirst、moveToLast等函数。为什么一定要调用它们呢?来分析最常见的moveTOFirst函数,该函数实际上由CursorWrapperInner的基类CursorWrapper来实现,代码如下:CursorWrapper.java::moveToFirst
mCursor成员变量的真实类型是BulkCursorToCursorAdaptor,但其moveToFirst函数却是该类的“老祖宗”AbstractCursor实现的。代码如下:AbstractCursor.java::moveToFirst
在上边代码中moveToPosition将调用子类实现的onMove函数。在本例中,子类就是BulkCursorToCursorAdaptor,接下来看它的onMove函数。
(1)BulkCursorToCursorAdaptor的onMove函数分析 代码如下:BulkCursorToCursorAdaptor.java::onMove
建立数据通道的关键函数是lBulkCursor的getWindow。对于客户端而言,lBulkCursor-Bp端对象的类型是BulkCursorProxy,下面介绍它的getWindow函数。
(2)BulkCursorProxy的getWindow函数分析 代码如下:BulkCursorNative.java::BulkCursorProxy::getWindow
再来看lBulkCursor-Bn端的getWindow函数,此Bn端对象的真实类型是CursorToBulkCursorAdaptor.
(3)CursorToBulkCursorAdaptor的getWindow函数分析 代码如下:CursorToBulkCursorAdaptor.java::getWindow
服务端返回的CursorWindow对象正是之前在count函数中创建的那个CursorWindow对象,其内部已经包含了执行本次query的查询结果。
另外,在将服务端的CursorWindow传递到客户端之前,系统会调用CursorWindow的writeToParcel函数进行序列化工作。
(4)SQLiteCursor的moveTOPostion函数分析
该函数由SQLiteCursor的基类AbstractCursor实现。前面已经看过它的代码了,其内部的主要工作就是调用AbstractCursor子类(此处就是SQLiteCursor自己)实现onMove函数,因此可直接看SQLiteCursor的onMove函数。代码如下:SQLiteCursor.java::onMove
以上代码中的if判断很重要,具体解释如下:
# 当mWindow为空,即服务端未创建CursorWindow时(当然,就本例而言,CursorWindow早已在query时就创建好了),需要调用fillWindow.该函数内部将调用clearOrCreateLocalWindow。如果CursorWindow不存在,则创建一个CursorWindow对象。如果已经存在,则清空CursorWindow对象的信息。
# 当newPostion小于上一次查询得到的CursorWindow的起始位置,或者newPosition大于上一次查询得到的CursorWindow的最大行位置,也需要调用fillWindow。由于此时CursorWindow已经存在,则clearOrCreateLocalWindow会调用它的clear函数以清空之前保存的信息。
# 调用fillWindow后将执行SQL语句,以获得正确的结果集。例如,假设上次执行query时设置了查询是从第10行开始的90条记录(即10-100行的记录),那么,当新的query若指定了从0行开始或从101行开始时,就需要重新调用fillWindow,即将新的结果填充到CursorWindow中。如果新query查询的行数位于10-100之前,则无需再次调用fillWindow了。
这是服务端针对query做的一些优化处理,即当CursorWindow已经包含了所要求的数据时,就没有必要再次查询了。按理说,客户端也应该做类似的判断,以避免发起不必要的Binder请求。我们回头来看客户端BulkCursorToCursorAdaptor的onMove函数。代码如下:BulkCursorToCursorAdaptor.java::onMove
(5)moveToFirst函数分析总结
moveToFrist及其相关的兄弟函数(如moveToLast和move等)的目的是移动游标位置到指定行。通过上面的代码分析,我们发现它的工作其实远不止移动游标位置这么简单。对于还未拥有CursorWindow的客户端来说,moveTOFirst将导航客户端反序列化来自服务端的CursorWindow信息,从而使客户端和服务端之间的数据通道真正建立起来。
7.4.4 Cursor query实现分析总结
本节的知识点是除ActivityManagerService之外难度最大的,当我们回头来看这一路分析旅程时,可能会有如下感知:
AMS的难度体现在它的游戏规则上;而query的难度体在它所做的层层封装上(包括其创建的类和它们之间的派生关系)。从本质上说,query要做的工作其实很简单,就是将数据复制到共享内存。这份工作的内空谈不上很难,因为它既没有复杂的游戏规则,也没较高的技术门槛。
7.5 Cursor close函数实现分析
如果写代码时不重视资源回收,等最后出问题进再来查找泄露点的做,会极大地增加了软件开发的成本。虽然java提供了垃圾回收机制,但希望读者不要把资源回收的意识丢失。
7.5.1 客户端Close的分析
客户端拿到的游标对象的真实类型是CursorWrapperInner,其close函数的代码如下:ContentResolver.java::CursorWrapperInner.close
下面来看CursorWrapperInner的基类CursorWrapper的close函数。这里必须提醒读者,后文对函数分析会频繁从基类转到子类,又从子类转到基类。造成这种局面是因为对类封装太厉害。
对于Cursor close函数来说,笔者更关注其中所包含的CursorWindow资源是如何释放的。根据以上代码中的注释可知,BulkCursorToCursorAdaptor的close调用的基类close函数会释放CursorWindow.
接下来来看super.close这个函数。这个close由BulkCursorToCursorAdaptor的父类AbstractWindowedCursor的父类AbstractCursor实现,代码如下:AbstractCursor.java::close
onDeactivateOrClose由AbstractCursor的子类AbstractWindowedCursor实现,代码如下:AbstractWindowedCursor.java::onDeactivateOrClose
close函数涉及的调用居然在派生树中如此反复出现,这让人很无奈!AbstractWindowedCursor.java::close
CursorWindow从SQLiteClosable派生,根据前面的介绍,释放SQLiteClosable代表的资源会利用引用计数来控制,这是如何实现的呢?代码如下:CursorWindow.java::close
releaseReference由CursorWindwo的基类SQLiteClosable实现,代码如下:SQLiteClosable.java::releaseReference
资源释放的工作由子类实现的onAllReferencesReleased完成,对CursorWindow来说,该函数的代码如下:CursorWindow.java::onAllReferencesReleased
至此,客户端游标对象的Close函数已分析完毕。
7.5.2 服务端close的分析
服务端close函数的触发是因为客户端通过IBulkCursor.close函数发送的Binder请求。IBulkCursor的Bn端就是目标CP进程的CursorToBulkCursorAdaptor,其close函数的代码如下:CursorToBulkCursorAdaptor.java::close
调用CursorToBulkCursorAdaptor.java::disposeLocked
调用SQLiteCursor的close函数
至此,服务端的close函数就分析完毕,现在来回答本节最开始提出的问题,如果没显式调用游标对象的close函数,那么该对象被垃圾回收时是否会调用close函数呢?我们将在下一节用代码来回答这个问题。
7.5.3 finalize函数分析
游标对象被回收前,其finalize函数将被调用。来看CursorWrapperInner的finalize函数,代码如下:ContentResolver.java::CursorWrapperInner.finalize
很可惜,我们寄予厚望的super.finalize函数也不会做出什么特殊的处理。难道CursorWindow资源就没地方处理了?这个问题的答案如下:
# 客户端所持有的CursorWindow资源会在该对象执行finalize时被回收。读者可查看CursorWindow的finalize函数。
# 前面分析过,服务端的close函数由BulkCursorToCursorAdaptor调用IBulkCursorClose函数触发。但BulkCursorToCursorAdaptor却没有实现finalize函数,故BulkCursorToCursorAdaptor被回收时,并不会触发服务端的Cursor释放。所以,如客户端不显式调用close,将导致服务端进程的资源无法释放。
7.5.4 Cursor close函数总结
Cursor close函数并未涉及什么较难的知识点。希望通过这一节介绍的知识点帮助读者建立及时回收资源的意识。
7.6 ContentResolver openAssetFileDescriptor函数分析
通过对Cursor query的分析可知,客户端进程可像查询本地数据库那样,从目标CP进程获取信息。不过,这种方法也有其局限性:
# 客户端只有按照结果集的组织方式来获取数据,而结果集的组织方式是行列式的,即客户端须移动游标到指定行,才能获取自己感兴趣的列的值。在实际生活中,不是所有的信息都能组织成行列的格式。
# query查询得到的数据的数据量很有限。通过分析可知,用于承载数据的共享内存只有2MB大小。对于较大数据量的数据,通过query方式来获取显然不合适。
考虑到query的局限性,ContentProvider还支持另外一种更直接的数据传输方式,笔者称之为“文件流方式”。因为通过这种方式客户端将得到一个类似文件描述符的对象,然后在其上创建对应的输入或输出流对象。这样,客户端就可通过它们和CP进程交互数据了。
不面来分析这个功能是如何实现的。先向读者介绍客户端使用的函数,即CR的openAssetFileDescriptor.
7.6.1 openAssetFileDescriptor之客户端调用分析,代码如下:ContentResolver.java::openAssetFileDescriptor
如上代码所述,openAssetFileDescriptor是一个通用函数,它支持3种scheme(方案)类型的URI.
# Scheme_Android_Resource:字符串表达为android.resource.通过它可以读取apk包(其实就是一个压缩文件)中封装的资源。假设在应用进程的res/raw目录下存放一个test.ogg文件,最终生成的资源ID由R.raw.test来表达,那么如果应用进程想要读取这个资源,创建的URI就是android.resource://com.package.name/R.raw.test。读者可试试。
# Scheme_File:字符串表达为file.通过它可以读取普通文件。
# 除上述两种scheme之外的URI:这种资源背后到底对应的是什么数据需要由目标CP来解释。
接下来分析在第3种scheme类型下调用的openAssetFileDescriptor函数。
1、openAssetFileDescriptor函数分析,代码如下:ContentResolver.java::openTypedAssetFileDescriptor
以上代码中,阻碍我们思维的依然是新出现的类,先解决它们,再分析代码中关键调用。
2、FileDescriptot家族介绍
本节涉及的类家族图谱如图7-7所示。(见书388)
解释:
# FileDescriptot类是java的标准类,它是对文件描述符的封装。进程打开的每一个文件都有一个对应的文件描述符。在Native语言开发中,它用一个int型变量来表示。
# 文件描述符作为进程的本地资源,如想超过进程边界将其传递给其他进程,则需要借助进程间共享技术。在Android平台上,设计都封装了一个ParcelFileDescriptor类。此类实现了Parcel接口,自然就支持了序列化和反序列化的功能。从图7-7可知,一个ParcelFileDescriptor通过mFileDescriptor指向一个文件描述符。
# AssetFileDescriptor也实现了Parcel接口,其内部通过mFd成员变量指向一个ParcelFileDescriptor对象。从这里可看出,AssetFileDescriptor是对ParcelFileDescriptor类的进一步封装和扩展。实际上,根据SDK文档中对AssetFileDescriptor的描述可知,其作用在于从AssetManager(后续分析资源管理的时候会介绍它)中读取指定的资源数据。
提示:简单向读者介绍一下与AssetFileDescriptor相关的知识。它用于读取APK包中指定的资源数据。以前面提到的test.ogg为例。如果通过AssetFileDescriptor读取它,那么其mFd成员则指向一个ParcelFileDescriptor对象。
下面来看ContentProvicer的openTypedAssetFile函数
7.6.2 ContentProvider的openTypedAssetFile函数分析 代码如下:ContentProvider.java::openTypedAssetFile
调用ContentProvider.java::openAssetFile
下面分析MediaProvider实现的openFile函数
1、MediaProvider openFile分析 代码如下:MediaProvider.java::openFile
在以上代码中,MediaProvider将首先通过客户端指定的音乐文件的_id去查询它的专辑信息。此处给读者一个示例,如图7-8所示(见书391)
2、ContentProvider openFileHelper函数分析 代码如下:ContentProvider.java::openFileHelper
至此,服务端已经打开指定文件了。那么,这个服端的文件描述符是如何传递到客户端的呢?我们单起一节来回答这个问题。
7.6.3 跨进程传递文件描述符的探讨
在回答上一节的问题之前,不知读者是否思考过下面的问题:实现文件描述符跨进程传递的目的是什么?
以上节读取音乐专辑的缩略图为例,问题的答案就是,让客户端能够读取专辑的缩略图文件。为什么客户端不先获得对应专辑缩略图的文件存储路径,然后直接打开这个文件,却要如此大费周章呢?原因有二:
# 出于安全的考虑,MediaProvider不希望客户端绕过它去直接读取存储设备上的文件。另外,客户端须额外声明相关的存储设备读写权限,然后才能直接读取其上面的文件。
# 虽然本例针对的是一个实际文件,但是从可扩展性角度看,我们希望客户端使用一个更通用的接口,通过这个接口可读取实际文件的数据,也可读取来自网络的数据,而作为该接口的使用者无须关心数据到底从何而来。
1、序列化ParcelFileDescriptor
先来看ParcelFileDescriptor的序列化函数writeToParcel,代码如下:ParcelFileDescriptor.java::writeToParcel。
Parcel的writeFileDescriptro是一个native函数,代码如下:android_util_Binder.cpp::android_os_Parcel_writeFileDescriptor
Native Parcel类的writeDupFileDescriptro函数的代码如下:Parcel.cpp::writeDupFileDescriptor
由上边的代码可知,ParcelFileDescriptor的序列化过程就是将其内部对应文件的文件描述符取出,并存储到一个由Binder驱动flat_binder_object对象中。该对象最终会发送给Binder驱动。
2、反序列化ParcelFileDescriptor
假设客户端进程收到了来自服务端的回复,客户端要做的就是根据服务端的回复包构造一个新的ParcelFileDescriptor.我们重点关注文件描述符的反序列化,其中调用的函数是Parcel的readFileDescriptor,代码如下:ParcelFileDescriptor.java::readFileDescriptor
internalReadFileDescriptor是一个native函数,其实现代码如下:android_util_Binder_cpp::android_os_Parcel_readFileDescriptor
来看Parcel的readFileDescriptor函数,代码如下:Parcel.cpp::readFileDescriptor
笔者在以上代码中提到了一个较深刻的问题:此fd是彼fd吗?这个问题的真实含义是:
# 服务端打开了一个文件,得到一个fd。注意,fd是一个整型。在服务端上,这个fd确实对应了一个已经打开的文件。
# 客户端得到的也是一个整型值,它对应的是一个文件吗?
如果说客户端得到一个整型值,就认为它得到了一文件,这种说法未免有些草率。在以上代码中,我们发现客户端确实根据收到的那个整型值创建了一个FileDescriptor对象。那么,怎样才可知道这个整型值在客户端中一定代表一个文件呢?
这个问题的终极解答在Binder驱动的代码中。来看它的binder_transaction函数。
3、文件描述符传递之Binder驱动的处理.代码如下:binder.c::binder_transaction
一切真相大白!原来,Binder驱动代替客户端打开了对应的文件,所以现在可以肯定,客户端收到的整型值确确实实代表一个文件。
4、深入讨论
Android 3.0以后为CP提供了管道支持,相关函数:ContentProvider.java::openPipeHelper
由以上代码可知, 采用管道这种方式的开销确实比客户端直接获取文件描述符的开销大。
7.6.4 openAssetFileDescriptor函数分析总结
本节讨论了CP提供的第二种数据共享方式,即文件流方式。通过种方式,客户端可从目标CP中获取想要的数据。
本节所涉及的内容较杂,从java层一直到驱动层。
7.7 本章学习指导
本章围绕CP进行了较为深入的讨论。相比而言,CP的启动和创建的难度及重要性要小,而SQLite的使用、进程间的数据共享和传递等就复杂得多。建议读者阅读完本章后,能在此基础上,对下列问题做进一步的深入研究:
# 客户端进程是如何撤销它和目标CP进程之间的紧密关系的。
# 尝试自行封装一个轻量级的、面向对象的SQLite类库。
# 针对序列化和反序列化相关的知识,最好能编写一些简单的例子,用于实践。
# java程序员应阅读《高质量java程序设计》一书,树立良好的资源管理和回收意识。
7.8 本章小结
本意围绕CP进行了较为深入细致的讨论。首先介绍了目标CP进程是如何启动并创建CP实例的;接着又介绍了SQLite及SQLiteDatabase家族;最后三节分别围绕Cursor的query、close函数的实现及文件描述符跨进程传递的内容进行了专题讨论。
第8章 深入理解ContentService和AccountManagerService(见书400)
本章主要内容:
# 介绍ContentService
# 介绍AccountManagerService
本章所涉及的源代码文件名及位置:
# StsemServer.java(frameworks/base/services/java/com/android/server/SystemServer)
# ContentService.java(frameworks/base/core/java/android/content/ContentService)
# UsbSettinngs.java(packages/apps/Settings/src/com/android/settings/deviceinfo/UsbSettins.java)
# Development.java(packages/apps/Settings/src/com/android/settings/deviceinfo/DevelopmentSettings.java)
# UsbDeviceManager.java(frameworks/base/services/java/com/android/server/usb/UsbDeviceManager.java)
# AccountAuthcnticatorCache.java(frameworks/base/core/java/android/accounts/AccountAuthcnticatorCache.java)
8.1 概述
本章第一个要分析的是ContentService.ContentService包含以下两个主要功能:
# 它是Android平台中数据更新通知的执行者。数据更新通知与第7章分析Cursorquery函数实现时提到的ContentObserver有关。这部分内容将在8.2节中介绍。
# 它是Android平台中数据同步服务的管理中枢。当用户通过Android手机中的Contacts应用将联系人信息同步到远端服务器时,就需要和ContentService交互。这部分内容是本章的难点,将在8.4节中介绍。
本章第二个要分析的是AccountManagerService,它负责管理Android手机中用户的账户,这些账户是用户的online账户,例如用户在Google、Facebook上注册的账户。
本章将先分析ContentService中数据通知机制的实现,然后分析AccountManagerService,最后再介绍ContentService中的数据同步服务。
8.2 数据更新通知机制分析
何为数据更新通知?先来看日常生活中的一个例子。
笔者所在公司采用BugZilla来管理Bug 。在日常工作中,笔者和同事们的一部分工作就是登录BugZilla查询各自名下的Bug并修改它们。如何跟踪自己的Bug呢?其实,以上描述中已择提到了一种方法,即登录BugZilla并查询。除此之外, BugZilla还支持另一种方法,即为每个Bug设置一个关系人列表,一旦该Bug的状态发生变化,BugZilla就会给该Bug关系人列表中的人发送邮件。
上例中提到的第二种方法就是本节要分析的数据更新通知机制。
现在回到Android平台,如果程序需要监控某数据项的变化,可以采用一个类似while循环的语句不断查询它以判断其值是否发生丁变化。显而易见,这种方式的效率很低。有了通知机制以后,程序只需注册一个ContentObserver 实例即可。一且该项数据发生变化,系统就会通过ContentObserver的onChange函数来通知我们。与前面所述的轮询相比,此处的通知方式明显更高效。
通过上面的描述可以知道,通知机制的实施包括两个步骤:第一步, 注册观察者;第二步:通知观察者。在Android平台中,这两步都离不开ContentService,下面来认识一下它。
提示:在设计模式中,通知机制对应的模式是Observer模式,即观察者模式。
8.2.1 初始ContentService
SystemServer创建ContentService的代码非常简单,如下:SystemServer.java::ServerThread.run
以上代码中直接调用了ContentService的main函数。在一般情况下,该函数第二个参数为false.此main函数的代码如下:ContentService.java::main
ContentService构造函数代码:ContentService.java::ContentService
读取同步服务管理对象,代码:ContentService.java::getSyncManager
看完以上代码读者可能会觉得ContentService比较简单。其实,ContentService中最难实现的功能是数据同步服务,不过该功能的实现都封装在SyncManager及相关类中了,所以在分析通知机制时不会和数据同步服务有太多瓜葛。
下面来分析通知机制实施的第一步,即注册ContentObserver.该步骤由ContentResovler提供的registerContentObserver函数来实现。
8.2.2 ContentResolver的registerContentObserver分析 代码如下:ContentResolver.java::registerContentObserver
registerContentObserver最终将调用ContentService的registerContentObserver函数,其中第三个参数是ContentObserver getContentObserver的返回值。这个返回值是什么呢?要搞明白这个问题,需请出ContentObserver家族成员。
1、ContentObserver介绍
ContentObserver家族成员如图8-1(见书403)
图8-1中的ContentObserver类和第7章中介绍的ContentProvider非常类似,内部都定义了一个Transprot类参与Binder通信。Transprot类从IContentObserver.Stub派生。从Binder通信角度来看,客户端进程中的Transprot将是Bn端。如此,通过registerContentObserver传递到ContentService所在进程的就是Bp端。IContentObserver Bp端对象的真实类型是IContentObserver.Stub.Proxy.
注意:IContentObserver.java由aidl处理IcontentObserver.aild生成。
2、registerContentObserver函数分析,代码如下:ContentService.java::registerContentObserver
mRootNode是ContentService的成员变量,其类型为ObserverNode.ObserverNode的组织形式是数据结构中的树,其叶子节点的类型为ObserverEntry,它保存了uri和对应的IContentObserver对象。
至此,客户端已经为某些数据项设置了ContentObserver.再来看通知机制实施的第二步,即通知观察者。
8.2.3 ContentResolver的notifyChange分析
数据更新的通知由ContentResolver的notifyChange函数触发。看MediaProvider的update函数的代码如上:MediaProvider.java::update
由以上代码可知,MediaProvider update函数更新完数据后,将通过notifiyChange函数来通知观察者。notifiyChanage函数的代码如下:ContentResolver.java::notifiyChange
由以上代码可知,ContentService的notifiyChange函数将被调用,其代码如下:ContentService.java::notifiyChange
8.2.4 数据更新通知机制总结和深入探讨
总结上面所描述的数据更新通知机制的流程,如图8-2(见书406)
从前面的代码介绍和图8-2所示的流程来看,Android平台中的数据更新通知机制较为简单。不过此处尚有几问题想和读者探讨。(见书)
问题1:
问题2:假设服务端有一项功能,需要客户端通过某种方式来控制它的开闭(即禁止或使用该功能),考虑一下有几种方式能实现这个控制机制?
Android平台上至少有3种方法可吧实现这个控制机制。
第一种:服务端实现一个API函数,客户端直接调用这个函数来控制。
第二种:客户端发送指定的广播,而服务端注册该广播的接收者,然后在这个广播接收者的onReceive函数中处理。
第三种:服务端输出一个ContentProvider,并为这个功能输出一个uri地址,然后注册一个ContentObserver.客户端可通过更新数据的方式来触发服务端ContentObserver的onChange函数,服务端在该函数中做对应处理即可。
在Android代码中,这三种方法都有使用。
8.3 AccountManagerService分析
本节将分析AccountManagerService.如前所述,AccountManagerService负责管理手机中用户的online账户,主要工作涉及账户的添加和删除、AuthToken(全称为authenticationtoken.有了它,客户端无须每次操作都向服务器发送密码了)的获取和更新等。关于AccountManagerService更详细的功能,可阅读SDK文档中AccountManager的说明。
8.3.1 初识AccountManagerService
AccountManagerService创建时的代码如下:SystemServer.java::ServerThread.run
其构造函数的代码如下:AccountManagerService.java::AccountManagerService
在AccountManagerService构造函数中创建了一个AccountAuthenticatorCache对象,它是什么?来看下方。
1、AccountAuthenticatorCache分析
AccountAuthenticatorCache是Android平台中账户验证服务(Account Authenticator Service,AAS)的管理中心。AAS由应用程序通过在AndroidManifest.xml中输出符合指定要求的Service信息而来。稍后读者将看到这些要求的具体格式。
先来看AccountAuthenticatorCache的派生关系,如图8-3(见书409)
# AccountAuthenticatorCache从RegisteredServicesCache<AuthenticatorDescription>派生。RegisteredServicesCache是一个模板类,专门用于管理系统中指定Service的信息收集和更新,而具体是哪些Service,则由RegisteredServicesCache构造时的参数指定。AccountAuthedticatorCache对外输出是由RegisteredServiceCache模板参数指定的类的实例。在图8-3中就是AuthenticatorDescription.
# AuthenticatorDescription继承了Parcelable接口,这代表它可以跨Binder传递。该类描述了AAS相关的信息。
# AccountAuthenticatorCache实现了IAccountAuthenticatorCache接口。这个接口供外部调用都使用以获取AAS的信息。
下面看AccountAuthenticatorCache的创建,相关代码如下:AccountAuthenticatorCache.java::AccountAuthenticatorCache
AccountAuthenticatorCache调用基类RegisteredServicesCache的构造函数时,传递了3个字符串参数,这3个参数用于控制RegisteredServicesCache从PackageManagerService获取哪些Service的信息。
(1)RegisteredServicesCache分析 RegisteredServicesCache.java::RegisteredServicesCache
由以上代码可知:
# 成员变量mPersistentServicesFile指向/data/system/registercd_service目录下的一个文件,该文件保存了以前获取的对应Service的信息。就AccountAuthenticator而言,mPersistentServicesFiel指向该目录的android.accounts.AccountAuthenticator.xml文件。
# 由于RegisteredServicesCache管理的是系统中指定Service的信息,当系统中有Package安装、卸载或更新时,RegisteredServicesCache也需要对应更新自己的信息,因为有些Service可能会随着APK被删除而不复存在。
generateServiceMap函数将获取指定的Service信息,其代码如下:RegisteredServicesCache.java::generateServicesMap
接下来解析Service的parseServiceInfo函数。
(2)parseServiceInfo函数分析:代码如下:RegisteredServicesCache.java::parseServiceInfo
parseServiceInfo将解析Service中的MetaData信息,然后调用子类实现的parseServiceAttributes函数,以获取特定类型Service的信息。
下面通过实例向读者展示最终的解析结果。
(3)AccountAuthenticatorCache总结
在Email应用的AndroidManifest.xml中定义一个AAS,如图8-4所示(见书413)
这个XML文件中的内容是有严格要求的,其中:
# accountType标签用于指定账户类型(账户类型和具体应用有关,Android并未规定账户的类型).
# icon、smallIcon、label和accountPreferences等用于界面显示。例如,当需要用户输入账户信息时,系统会弹出一个Activity,上述几个标签就用于界面显示,详细说明可阅读SDK文档AbstractAccountAuthenticator的说明。android.accounts.AccountAuthenticator.xml的内部
下面来看AccountManagerService的构造函数。
2、AccountManagerService构造函数分析 代码如下:AccountManagerService.java::AccountManagerService
8.3.2 AccountManager.addAccount分析
这一节将分析AccountManagerService中的一个重要的函数,即addAccount,其功能是为某项目账户添加一个用户。下面以前面提及到的Email为例来认识AAS的处理流程。
AccountManagerService是一个运行在SystemServer中的服务,客户端进程必须借助AccountManager提供的API来使用AccountManagerService服务,所以本例需要从AccountManager的addAccount函数讲起。
1、AccountManager的addAccount发起请求 代码如下:AccountManager.java::addAcount
在以上代码中,AccountManager的addAcount函数将返回一个匿名类对象,该匿名类继承自AmsTask类,那么,AmsTask又是什么呢?
(1)AmsTask介绍
先来看AmsTask的继承关系,如图8-7所示(见书417)
(2)AmsTask匿名类处理分析
AccountManager的addCount最终返回给客户端的是一个AmsTask的子类,首先来了解它的构造函数,其代码如下:AccountManager.java::AmsTask
下一步调用的是这个匿名类的Start函数,代码如下:AccountManager.java::AmsTask.start
调用doWork函数即下面这个函数,代码如下:AccountManager.java::addAcount返回的匿名类
AccountManager的addAccount函数的实现比较新奇,它内部使用了java的concurrent(并发)类。
下面转到AccountManagerService中去分析addAccount的实现。
2、AccountManagerService.addAccount转发请求,代码如下:AccountManagerService.java::addAccount
由以上代码可知,AccountManagerService的addAccount函数最后也创建了一个匿名类对象,该匿名类派生自Session。addAccount最后还调用了这个对象的bind函数。其中重要的内部就是Session。那么,Session又是什么呢?
(1)Session介绍
Session家族成员如图8-8所示(见书419)
(2)Session匿名类处理分析
首先调用Session的构造函数,代码为:AccountManagerService.java::Session
获得匿名类对象后,addAccount将调用其bind函数,该函数由Session实现,代码如下:AccountManagerService.java::Session::bind
由以上代码可知,Session的bind函数将启动指定类型的Service,这是通过bindService函数完成的。如果服务启动成功,Session的onServiceConnected函数将被调用,这部分代码如下:AccountManagerService.java::Session.onServiceConnected
匿名类实现的run函数非常简单,代码如下:AccountManagerService.java::addAccount返回的匿名类
由以上代码可知,AccountManagerService在addAccount最终将调用AAS实现的addAccount函数。
不面来看本例中满足“com.android.email”类型的服务是如何处理addAccount的请求的。该服务就是Email应用中的EasAuthenticatorService,下面来分析它。
3、EasAuthenticatorService处理请求
EasAuthenticatorService的创建是AccountManagerService调用了bindService完成的,该函数会触发EasAuthenticatorService的onBind函数的调用,这部分代码如下:EasAuthenticatorService.java::onBind
下面来分析EasAuthenticator。
(1)EasAuthenticator介绍
EasAuthenticator是EasAuthenticatorService定义的内部类,其家族关系如图8-9所示(见书422)
(2)EasAuthenticator的addAccount函数
根据上文的描述可知,Email进程中首先被触发的是IAccountAuthenticator Bn端的addAccount函数,其代码如下:AbstractAccountAuthenticator.java::Transprot::addAccount
本例中AbstractAccountAuthenticator子类(即EasAuthenticator)实现的addAccount函数,代码如下:EasAuthenticatorService.java::EasAuthenticator.addAccount
4、返回值的处理流程
5、AccountManager的addAccount分析总结
AccountManger的addAccount流程分析起来会给人一种行云流水般的感觉。该流程涉及3个模块,分别是客户端、AccountManagerService和AAS。
总结起来addAccount相关流程如图8-10所示(见书427)
8.3.3 AccountManagerService的分析总结
本节对AccountManagerService进行分析,从技术上说,本节涉及和java concurrent类相关的知识。另外,对并发编程来说,架构设计是最重要的,因此读者务必阅读脚注中提到的参考书籍《Pattern.Oriented.Software.Architecture.Volume2》。
8.4 数据同步管理SyncManager分析
本节将分析ContentService中负责数据同步管理的SyncManager。SyncManager和AccountManagerService之间的关系比较紧密。同时,由于数据同步涉及手机中重要数据(例如联系人信息、Email、日历等)的传输,因此它的控制逻辑非常严谨,知识点也比较多,难度相对比AccountManagerService大。
先来认识数据同步管理的核心SyncManager.
8.4.1 初识SyncManager
SyncManager的构造函数的代码较长,可分段来看。下面先来介绍第一段的代码。
1、SyncManager介绍 代码如下:SyncManager.java::SyncManager
在以上代码中,首先见到的是SyncManager的几位重要成员,它们之间的关系如图8-11所示(见书429)
下面来认识一下SyncManager家族中的几位主要成员,首先是SyncStorageEngine.
2、SyncStorageEngine介绍
SyncStorageEngine负责整个同步系统中信息管理方面的工作。先看其init函数代码:SyncStorageEngine.java::init
3、SyncAdaptersCache介绍
再看SyncAdaptersCache,其构造函数代码如下:SyncAdaptersCache.java::SyncAdaptersCache
SyncAdaptersCache的基类是RegisteredServicesCache。8.3.1节已经分析过RegisteredServicesCache了,此处不再赘述。
再看SyncManager家族中最后一位成员SyncQueue.
4、SyncQueue介绍
SyncQueue用于管理同步操作对象SyncOperation。SyncQueue的构造函数代码为:SyncQueue.java::SyncQueue
SyncQueue比较简单,其中一个比较难理解的概念是backoff,后文对此作解释。
至此,SyncManager及相关家族成员已介绍完毕。下面将通过实例分析同步服务的工作流程。在本例中,将同步Email数据,目标同步服务为EmailSyncAdapterService。
8.4.2 ContentResolver的requestSync分析
ContentResolver提供了一个requestSync函数,用于发起一次数据同步请求。在本例中,该函数的调用方法如下:
1、客户端发起请求
ContentResolver的requestSync的代码如下:ContentResolver.java::requestSync
与添加账户(addAccount)相比,客户端发起一次同步请求所要做的工作就太简单了。下面转战ContentService去看它的requestSync函数
2、ContentService的requestSync函数分析,代码如下:ContentService.java::requestSync
ContentService将工作转交给SyncManager来完成,其调用的函数是scheduleSync。
(1)SyncManager的scheduleSync函数分析
先行介绍scheduleSync函数非常重要,其调用代码如下:SyncManager.java::scheduleSync
scheduleSync函数较复杂,难点在于其策略控制。建议读者反复阅读这部分内容。
scheduleSync最后将构造一个SyncOperation对象,并调用scheduleSyncOperation处理它。scheduleSyncOperation内部将这个SyncOperatio对象保存到时mSyncQueue中,然后发送Message_check_alarms消息让mSyncHandler处理。由于scheduleSyncOperation函数比较简单,因此下面将直接去mSyncHandler的handMessage函数中分析Message_check_alarms的处理流程。
(2)处理Message_check_alarms消息
SyncHandler的handleMessage代码如下:SyncManager.java::SyncHandler:handleMessage
如上代码所述,Message_check_alarms消息的处理就是调用maybeStartNextSyncLocked函数。这个函数比较烦琐。它主要做了以下几项工作。
ActiveSyncContext是SyncManager和同步服务交互的关键类,其家族图谱如图8-16所示(见书444)
(3)ActiviSyncContext派发请求,代码如下:SyncManager.java::ActiveSyncContext.bindToSyncAdapter
3、EmailSyncAdapterService处理请求
在本例中,目标同步服务位于EmailSyncAdapterService中,先看它通过onBind函数返回给ActiveSyncContext的是什么。
(1)onBind分析,代码如下:EmailSyncAdapterService.java::onBind
(2)startSync分析
在SyncService中,首先被调用的函数是ISyncAdapterImpl的startSync函数,其代码为:AbstractThreadSyncAdapter.java::ISyncAdapterImpl.startSync
执行完onPerformSync函数后,ISyncAdapterImpl.run返回前会调用mSyncContextonFinished函数,通知位于SyncManager中的ActiveSyncContext同步操作的结果。
4、ContetnResolver reqeustSync分析总结
总结requestSync的工作流程,如图8-18(见书449)
由图8-18可知,requestSync涉及的对象及调用流程比较烦琐。但从技术上看,则没有什么需要特别注意的地方。
8.4.3 数据同步管理SyncManager分析总结
本节对ContentService中第二个主要功能即数据同步管理SyncManager进行了研究,这部分内容主要包括3个方面:
# SyncManager及相关成员的作用。
# 通过requestSync展示了SyncManager各个模块的作用及交互过程。
# 穿插于上述两方面之中的是数据同步的一些处理策略和规则。笔者未对这部分内容展开更细致的讨论。感兴趣的读者可自行学习。
8.5 本章学习指导
本章对ContentService和AccountManagerService进行了研究,在学习过程中,需要注意以下几个方面。
# ContentService包括两个不同的部分,一个是数据更新通知机制,另一个是同步服务管理。在这两部分中,ContentService承担了数据更新通知机制的工作,同步服务管理的工作则委托给SyncManager来完成。基于这种分工,本章先分析了ContentService的数据更新通知机制。这部分内容非常简单,读者应能轻松掌握。不过,作为深入学习的入口,笔者有意留下几个问题,旨在让读者仔细思考。
# 由于同步服务管理和AccountManagerService关系密切,因此本章先分析了AccountManagerService。在这部分代码中,读者需要了解RegisteredServicescCache的作用。另外,感兴趣的读者也可以学习如何编写AuthedticatorService,相关文档在SDK关于AbstractAccountAuthenticator类的说明中。
# SyncManager是本章理解难度最大的知识点。笔者觉得,其难度主要体现在同步策略上。另外,缺乏相关文档资料的参与也是导致学习SyncManager难度较大的原因之一。读者在把本部分内容搞清楚的基础上,可自行研究本章没有提及的内容。另外,读者如想学习如何编写同步服务,可参与SDK文档中关于AbstractThreadSyncAdapter的说明。
除上述内容处,AccountManager的addAcount函数实现中所使用的java concurrent类,也是读者必须学习并掌握的基础知识。
8.6 本章小结
本章对ContentService和AccountManagerService进行了较为深入的探讨,其中:
# 8.2节和8.4节分别分析了ContentService中数据更新通知机制的实现及同步服务管理方面的工作。
# 8.3节分析了AccountManagerService及addAccount的实现。
本章留出以下问题供读者自行研究,这些问题包括:
# 8.2节最后提到的开放性问题和Cursor query中ContentObserver的使用分析。
# 8.4节涉及SyncManager的同步请求处理的策略和maybeStartNextSyncLocked函数分析等。另外,读者如果有兴趣,不妨研究下SyncStorageEngine中同步信息的统计等内容。
相关推荐
《深入理解Android:卷2》是“深入理解Android”系列的第2本,第1本书上市后获得广大读者高度评价,在Android开发者社群内口口相传。本书不仅继承了第1本书的优点并改正了其在细微处存在的一些不足,而且还在写作的...
### 深入理解Android卷II相关知识点 #### 前言 《深入理解Android卷II》这本书聚焦于Android系统的内部实现与高级特性,通过详细分析Android的核心组件和服务,帮助读者深入了解Android系统的运行机制。 #### 第1...
《深入理解Android 卷 II》是由邓凡平编著的一本深入探讨Android系统技术的专著,这本书旨在帮助读者从底层原理到上层应用全面掌握Android开发的核心技术。邓凡平作为一位资深的Android专家,以其丰富的经验和深入的...
资源名称:深入理解ANDROID 卷3内容简介:深入理解Android(卷3)》是Android经典畅销书系(对Android系统源代码的分析最为系统和细致)“深入理解Android”系列Framework卷的第III卷,从源代码的角度,对Android...
对Android的源代码进行深入分析。内容广泛,以对Framework层的分析为主,分析系统服务源码,如ActivityManagerService、PackageManagerservice等。
### 深入理解Android卷II 第7章:ContentProvider #### 一、ContentProvider概述与原理 在Android系统中,ContentProvider作为一种重要的组件之一,主要用于处理应用间的数据共享问题。它提供了一套标准接口来访问...
Android 深入理解 卷II 清晰 无删减
Android 深入理解 卷II 清晰 无删减
Android 深入理解 卷II 清晰 无删减
Android 深入理解 卷II 清晰 无删减
Android 深入理解 卷II 清晰 无删减
Android 深入理解 卷II 清晰 无删减
Android 深入理解 卷II 清晰 无删减
Android 深入理解 卷II 清晰 无删减
Android 深入理解 卷II 清晰 无删减
经典畅销书系“深入理解Android”系列Framework卷完结篇,数十万Android开发工程师翘首以盼 从源代码层面全面、详细剖析了Android 框架UI系统的实现原理和工作机制,以及优秀代码的设计思想,填补市场空白 要想...