导读:对于Android开发者来说,成系列的技术文章对他们的技术成长帮助最大。如下是我们向您强烈推荐的主题为Android开发的第一个系列文章。
《Android核心分析》整理如下:
1. 方法论探讨之设计意图
为什么要研究Android,是因为它够庞大,它够复杂,他激起了我作为一个程序员的内心的渴望,渴望理解这种复杂性。我研究的对象是作为手机开发平台的Android软件系统部分,而不是Dalvik虚拟机本身。
作为一个从其他平台装接过来的程序员,要从事Andoid平台系统开发,我的关于手机平台上积累的知识已经不能满足需要了,Android为我们带来了大量的新名词,Activity,Manifest,INTENT,Service,Binder,Dalvik虚拟机,Framework,Linux,Navtive ,JNI.....。通过在源代码,在开发社区,在开发博客,甚至在招聘过程中,我不断的寻求Android是什么。经过一定时间的沉淀,我慢慢的理解到Android不仅仅是一类手机的总称,不仅仅是一个手机开发平台,不仅仅是一个虚拟java操作系统,不仅仅是一个开发社区,一个开发标准,不仅仅是一堆代码,Android已经成了一个新的潮流。
代码多,系统复杂,纵观社区中Android的研究者,一开始从源代码分析Android就走向迷途,不断的跋山涉水,向纵深冲刺,最终脑袋堆栈不够用,迷失在开始的旅程,或者挂在半途中,鲜有通达者。我感觉到大部分的研究者总是忘记站在高山上向下望一望设计者的意图,一味的随着代码的控制流走入繁杂的谜团,陷入到复杂性的深渊。
我的研究分析是从设计者的意图出发,从抽象的甚至从哲学的高度,从最简单的系统原型开始,从设计猜想开始,而不是一开始就从代码分析展开。首先理解Android大的运行框架,主干流程,系统原型,之后再用源代码分析充实之。当然我这里的设计者意图并不是真正的Android设计者意图,而是我以为的Android设计者意图。
要理解设计者意图,就需要抽象。我们需要在哲学意义空间中去考虑系统的描述,即系统在本质上要表达什么。在逻辑空间上去考虑系统基本构成和动态结构。从现实到虚拟对象的映射去理解系统对象的组成,在从数据流的角度分析数据的产生者和消费者之间作用关系,从控制流的角度去分析对象之间的交互关系,从函数调用去分析具体的层次关系。
在系统设计上,原型是最能表达哲学空间和逻辑空间中系统本质的东西,原型是事物本质的第一层体现。我以为任何复杂的系统都一个简洁的系统原型,都有它简洁的意义。系统原型是设计者意图的第一体现,所以我们需要从几个方向上去提炼系统原型:
(1)从系统本质和基本原理出发
(2)从分析系统数据流和控制流分析出发。
从设计者意图出发,得出系统原型,提取到大的逻辑结构和系统构成是第一步。之后我们可以从设计者的角度考虑系统猜想系统设计,为什么要这样设计,为什么要有这些构成。这样的基本原型是什么?系统的限制是什么,应用场景有哪些,有些设计的引进还是系统收敛性而为之呢。我们还可以从代码痕迹上去分析,这些概念是如何的得来的?从一定的抽象和高度去理解这些问题,遵循系统原型出发之原则,在深入分析代码的时候,就不容易陷入细节中。我们就可以随时跳出来想,这些代码在整体上载表达一个什么概念,在描绘一个什么逻辑,他要构成一个虚拟层吗?他是在管理这个硬件吗?他在 虚拟这个对象吗?他在构建管理机构?还是在构建一个对象管理?空间管理,为了快速引入了什么样的复杂算法,实际上的原型算法应该是什么样的?
只有深入到这个抽象层次,我们才能很好的把握住系统的每一条线,每一个对象的意义。只用从原型出发,我们才能把握住这个系统的实质所在,在干什么?他要表达什么?设计者为什么要这样想?最终极的想法是什么?这样,代码分析就变得简单明了,读代码就变成了是在印证猜想,修正方向。
2. 方法论探讨之概念空间篇
我们潜意识就不想用计算机的方式来思考问题,我们有自己的思维描述方式,越是接近我们思维描述方式,我们越容易接受和使用。各种计算机语言,建模工具,不外乎就是建立一个更接近人的思维方式的概念空间,再使用工具从该概念空间向另外一个概念空间映射,我称之为人性思维空间向01序列描述空间的一个映射。实现方面来看,系统就是一个翻译器,将机器性更加人性化的一种机制。大学计算机经典课“计算机体系结构”,其他的可以忘记,但是下面这个图不能忘记:
这个就是概念空间最本质的原型体现:作为观测者看到了什么?设计者给了观察者什么?给出的答案是外部特性。
(1)提供给观察者的概念空间是什么?
(2)内部特性的概念空间是什么?
概念空间所表达的东西带有两个方面的缠绕:一面是人性自由,一面是物性制约(实时响应,系统资源的限制)。所以程序实现的概念空间是人性自由与特定计算机系统物性之间有一个折中,并且根据实际系统而采取某种动态的平衡。而这种平衡将会影响到系统架构,以及设计的思想。特别在手机这样的嵌入式系统中,这种矛盾和平衡无处不在,这种折中无处不在。而对系统的选取和采用,也就接受了某个方面的折中或某中即在的,也许是看不见的标准,及这样的标准有隐式和显式的。正因为如此,不管是工具的产生,新的平台的产生, 都是计算机的物性向人性靠近的一个小台阶。一个新的思想的形成随即带来的新工具,新系统框架,新的体系结构。
如果设计者站的高度足够高,那么设计者一开始就会考虑到“我该给他们一个什么样的概念空间,甚至一个什么样的理念,让他们这个概念空间去建立自己的产品”,于是设计者就会开始主动的去建立概念空间,这个概念空间要表达的实际意义,概念空间应该有哪些内容构成,考虑概念空间的完备性和封闭性,考虑概念空间的边界,考虑从哪个基础上建立这个概念空间,考虑如何与概念空间外的实体进行交互,考虑系统的资源限制条件,考虑功能性构建的合理性,考虑机器系统与人的平衡问题。
我们在学习新系统时,首先映入眼帘的就是新概念。新名词,就如现在我们面临的Android大量的新名词,在程序员的世界都是从代码实践开始的,是从写应用开始去涉及。SDK给了我们一个概念,我们就在这个概念框架下,使用SDK给我提供的函数接口,数据结构,初始化过程等,我们最初的接触到原型就是“HelloWorld”之类的DEMO程序,我们在Hello world上去使用各种不同的接口函数,对于应用程序员来讲,他说看到的系统就是系统调用接口,及其编程开发流程。实际上只要一使用这些接口,就不得不接受一系列的概念,只有在这种概念系统下,我们才能工作。但是,实际上我们却忽略了这样的概念系统的理解,只是在编程接口的这个狭窄的空间去理解系统.我们理解系统在形成理解概念的空间只是微小的一角,很少有资料来介绍这种概念系统的形成和理解,编程接口只是这个概念空间一个,对外部的一个表征。我们可以抽象起来,以接口,协议和行为,来描述系统的情况。SDK API的实质向上层提供了一个语义接口,从而在层间实现了一个转义过程,同时又成为一个功能的集合体。但是我们很少这样跳出来看,我们到底是处于一种什么样的概念空间,SDK除了调用接口外,还给了我们怎样一种整体概念?目标系统的基本构架在本质上的东西就是一个概念系统到另一个概念系统的映射。让我们大脑理解的概念系统映射到计算机能实现的概念域的一个映射。我们假定这个概念域E,机器能够理解的概念域为M,我们的软件工程要做的事情实质就是:EàM领域的一个映射过程。
为什么要在宏观上把握这些概念呢,显然有我的目的,理解概念空间是理解设计者意图的一个重要途径。设计者要想给开发者提供什么,设计者想要提供给最终用户什么。我们需要站在高处看待系统明白设计者意图。
Android的实质还是一套管理手机硬件系统的软件,这个话讲起来没有多大意义,计算机操作系统本质都是如此,Android是Google云计算计划的一部分,我们修正成:Android建立的本质就是让计算机成为我的云接入移动智能终端。作为硬件管理软件,Android提供概念空间内涵实质上泛操作系统内涵,我们的理解可以从泛操作系统概念空间映射到Android系统中去。而作为云计算的一部分的内容,我们可以云计算的概念入手去研究Andoird。
3. 手机之硬件形态
本节可能与Android无关,但是Android系统现在这个阶段更多的是移动终端形态的开发平台,本节给出了Android背后的工作-Android管理的硬件是什么,Android的本质就是要管理好这些硬件部分,为用户提供一个体验更好,速度更快的智能移动终端。对手机硬件形态的认识是要让我们对手机硬件组成有个感性的认识,让程序员知道系统中的代码是管理那一部分的,即我们堆砖头的目的是什么,让思维有一个伸展。
为了对手机这类嵌入式系统有一个较为深入的了解,我制作了如下的手机硬件结构思维导图,在这张图上我们可以看到组成手机硬件的有哪些,初步了解到手机管理平台为什么要那么多的管理框架和层次,从最底层理解Android设计者的设计意图,这个思维导图其实只是示意图。
我们知道手机这种嵌入式系统,硬件架构最简单描述的描述为:
应用处理器+Modem+射频
对于应用处理器而言,对设计者最为本质的描述为输入输出,而对于移动终端设备电源管理,连接机制,多媒体又是很重要的考虑环节,而这些环节都会在软件平台上有所体现。
4. 手机的软件形态
上节我给出了手机的硬件树,本节将给出手机软件形态树。主要突出手机软件涵盖的内容。通过该思维导图,我们可以看到手机软件所涉及到的方方面面,Android所涉及到的内容也不会超过下面所示太多,这个也是Andoid系统外特性空间所要展示的,这个也是Android设计者需要考虑管理的大部分内容,通过下面的整理,我们可以让我们的思维更加贴近Android设计意图,从而更深入的了解Android中各种组成的由来,这个就是前面讲到的分析思想之一从退到源头出发,从思考最终极的问题开始。
5. Android基本空间划分
Google给了我们一张系统架构图,在这张图上我们可以看到Android的大体框架组成。
从上图可以看到:Android Applications,Application Framework,Dalvik Virtual Machine,Linux。如果将Android泛化,我们可以将系统划分成两部分:
但是为了研究的方便我们先看最为本质的三层,上面是Android,中间叫Dalvik虚拟机,下面叫Linux。
虽然上两层都包含在Android中,但是为了理解的方便或者从实用主义出发,我还是将虚拟机这次给分开出来,因为我研究的对象是Android的手机系统相关部分,对于虚拟机我们不做太深入的研究。
e: pre;"> 从上面我们可以看到这个系统静态的划分成这样的三层。但是从动态运行逻辑上不是这样划分的,所以空间的划分是一个有趣的概念。我们从操作系统的角度看,Android就是一堆Linux应用的集合。从Linux角度看到的空间划分:进程空间和内核空间。从Android的应用对应着Linux的一个个进程。
Andoid中包含一个Java虚拟机,虚拟机是运行在Linux之上的,Android构建在JVM之上,从Android动态运行逻辑上我们需要将Android划分成Android空间和非Android空间。在Andoid系统中我们面对的是Andoid概念空间,而不是Linux进程了,在Andoid概念空间中已经没有了Lliux进程的概念,而是Service,proxy,Activity,provider等。
至于虚拟机JVM,我们只需要知道JVM是Dalvik VM(虚拟机)这是一个专为嵌入式设备打造的JAVA虚拟机,是一个有着自己的code-byte和格式的可以在嵌入式设备上高效运行的Java虚拟机。
为了研究的深入,我们还是需要涉及到JNI Native部分。在这个分类中我将JVM分为JVM空间和C++空间。
Android应用的开发者是工作在Android外特性概念空间的,这里没有了Linux的一点气息,Android构建的外特性空间概念包含了:Activity,Provider,Interface,Events,Provider,Service等。至于JVM空间和C++空间的划分是为了研究Android核心的描述而提出的,我们在做Android系统开发时,常常需要修改到JNI的Native部分。后面我将用较多的篇幅来深入阐述这个部分。
6. IPC框架分析(Binder,Service,Service manager)
我首先从宏观的角度观察Binder,Service,Service Manager,并阐述各自的概念。从Linux的概念空间中,Android的设计Activity托管在不同的的进程,Service也都是托管在不同的进程,不同进程间的Activity,Service之间要交换数据属于IPC。Binder就是为了Activity通讯而设计的一个轻量级的IPC框架。
在代码分析中,我发现Android中只是把Binder理解成进程间通讯的实现,有点狭隘,而是应该站在公共对象请求代理这个高度来理解Binder,Service的概念,这样我们就会看到不一样的格局,从这个高度来理解设计意图,我们才会对Android中的一些天才想法感到惊奇。从Android的外特性概念空间中,我们看不到进程的概念,而是Activity,Service,AIDL,INTENT。一般的如果我作为设计者,在我们的根深蒂固的想法中,这些都是如下的C/S架构,客户端和服务端直接通过Binder交互数据,打开Binder写入数据,通过Binder读取数据,通讯就可以完成了。
该注意到Android的概念中,Binder是一个很低层的概念,上面一层根本都看不到Binder,而是Activity跟一个Service的对象直接通过方法调用,获取服务。
这个就是Android提供给我们的外特性:在Android中,要完成某个操作,所需要做的就是请求某个有能力的服务对象去完成动作,而无需知道这个通讯是怎样工作的,以及服务在哪里。所以Andoid的IPC在本质上属于对象请求代理架构,Android的设计者用CORBA的概念将自己包装了一下,实现了一个微型的轻量级CORBA架构,这就是Andoid的IPC设计的意图所在,它并不是仅仅解决通讯,而是给出了一个架构,一种设计理念,这就是Android的闪光的地方。Android的Binder更多考虑了数据交换的便捷,并且只是解决本机的进程间的通讯,所以不像CORBA那样复杂,所以叫做轻量级。
所以要理解Android的IPC架构,就需要了解CORBA的架构。而CORBA的架构在本质上可以使用下面图来表示:
在服务端,多了一个代理器,更为抽象一点我们可以下图来表示。
分析和CORBA的大体理论架构,我给出下面的Android的对象代理结构。
在结构图中,我们可以较为清楚的把握Android的IPC包含了如下的概念:
设备上下文什(ContextObject)
设备上下文包含关于客服端,环境或者请求中没有作为参数传递个操作的上下文信息,应用程序开发者用ContextObject接口上定义的操作来创建和操作上下文。
Android代理:这个是指代理对象
Binder Linux内核提供的Binder通讯机制
Android的外特性空间是不需要知道服务在那里,只要通过代理对象完成请求,但是我们要探究Android是如何实现这个架构,首先要问的是在Client端要完成云服务端的通讯,首先应该知道服务在哪里?我们首先来看看Service Manger管理了那些数据。Service Manager提供了add service,check service两个重要的方法,并且维护了一个服务列表记录登记的服务名称和句柄。
Service manager service使用0来标识自己。并且在初始化的时候,通过binder设备使用BINDER_SET_CONTEXT_MGR ioctl将自己变成了CONTEXT_MGR。Svclist中存储了服务的名字和Handle,这个Handle作为Client端发起请求时的目标地址。服务通过add_service方法将自己的名字和Binder标识handle登记在svclist中。而服务请求者,通过check_service方法,通过服务名字在service list中获取到service 相关联的Binder的标识handle,通过这个Handle作为请求包的目标地址发起请求。
我们理解了Service Manager的工作就是登记功能,现在再回到IPC上,客服端如何建立连接的?我们首先回到通讯的本质:IPC。从一般的概念来讲,Android设计者在Linux内核中设计了一个叫做Binder的设备文件,专门用来进行Android的数据交换。所有从数据流来看Java对象从Java的VM空间进入到C++空间进行了一次转换,并利用C++空间的函数将转换过的对象通过driver/binder设备传递到服务进程,从而完成进程间的IPC。这个过程可以用下图来表示。
这里数据流有几层转换过程。
(1) 从JVM空间传到c++空间,这个是靠JNI使用ENV来完成对象的映射过程。
(2) 从c++空间传入内核Binder设备,使用ProcessState类完成工作。
(3) Service从内核中Binder设备读取数据。
Android设计者需要利用面向对象的技术设计一个框架来屏蔽掉这个过程。要让上层概念空间中没有这些细节。Android设计者是怎样做的呢?我们通过c++空间代码分析,看到有如下空间概念包装(ProcessState@(ProcessState.cpp)
在ProcessState类中包含了通讯细节,利用open_binder打开Linux设备dev/binder,通过ioctrl建立的基本的通讯框架。利用上层传递下来的servicehandle来确定请求发送到那个Service。通过分析我终于明白了Bnbinder,BpBinder的命名含义,Bn-代表Native,而Bp代表Proxy。一旦理解到这个层次,ProcessState就容易弄明白了。
下面我们看JVM概念空间中对这些概念的包装。为了通篇理解设备上下文,我们需要将Android VM概念空间中的设备上下文和C++空间总的设备上下文连接起来进行研究。
为了在上层使用统一的接口,在JVM层面有两个东西。在Android中,为了简化管理框架,引入了ServiceManger这个服务。所有的服务都是从ServiceManager开始的,只用通过Service Manager获取到某个特定的服务标识构建代理IBinder。在Android的设计中利用Service Manager是默认的Handle为0,只要设置请求包的目标句柄为0,就是发给Service Manager这个Service的。在做服务请求时,Android建立一个新的Service Manager Proxy。Service Manager Proxy使用ContexObject作为Binder和Service Manager Service(服务端)进行通讯。
我们看到Android代码一般的获取Service建立本地代理的用法如下:
IXXX mIxxx=IXXXInterface.Stub.asInterface(ServiceManager.getService("xxx"));
例如:使用输入法服务:
IInputMethodManager mImm=
IInputMethodManager.Stub.asInterface(ServiceManager.getService("input_method"));
这些服务代理获取过程分解如下:
(1) 通过调用GetContextObject调用获取设备上下对象。注意在AndroidJVM概念空间的ContextObject只是 与Service Manger Service通讯的代理Binder有对应关系。这个跟c++概念空间的GetContextObject意义是不一样的。
注意看看关键的代码
BinderInternal.getContextObject() @BinderInteral.java
NATIVE JNI:getContextObject() @android_util_Binder.cpp
Android_util_getConextObject @android_util_Binder.cpp
ProcessState::self()->getCotextObject(0) @processState.cpp
getStrongProxyForHandle(0) @
NEW BpBinder(0)
注意ProcessState::self()->getCotextObject(0) @processtate.cpp,就是该函数在进程空间建立 了ProcessState对象,打开了Binder设备dev/binder,并且传递了参数0,这个0代表了与Service Manager这个服务绑定。
(2) 通过调用ServiceManager.asInterface(ContextObject)建立一个代理ServiceManger。
mRemote= ContextObject(Binder)
这样就建立起来ServiceManagerProxy通讯框架。
(3)客户端通过调用ServiceManager的getService的方法建立一个相关的代理Binder。
ServiceMangerProxy.remote.transact(GET_SERVICE)
IBinder=ret.ReadStrongBinder() -》这个就是JVM空间的代理Binder
JNI Navite: android_os_Parcel_readStrongBinder() @android_util_binder.cpp
Parcel->readStrongBinder() @pacel.cpp
unflatten_binder @pacel.cpp
getStrongProxyForHandle(flat_handle)
NEW BpBinder(flat_handle)-》这个就是底层c++空间新建的代理Binder。
整个建立过程可以使用如下的示意图来表示:
Activity为了建立一个IPC,需要建立两个连接:访问Servicemanager Service的连接,IXXX具体XXX Service的代理对象与XXXService的连接。这两个连接对应c++空间ProcessState中BpBinder。对IXXX的操作最后就是对BpBinder的操作。由于我们在写一个Service时,在一个Package中写了Service Native部分和Service Proxy部分,而Native和Proxy都实现相同的接口:IXXX Interface,但是一个在服务端,一个在客服端。客户端调用的方式是使用remote->transact方法向Service发出请求,而在服务端的OnTransact中则是处理这些请求。所以在Android Client空间就看到这个效果:只需要调用代理对象方法就达到了对远程服务的调用目的,实际上这个调用路径好长好长。
我们其实还一部分没有研究,就是同一个进程之间的对象传递与远程传递是区别的。同一个进程间专递服务地和对象,就没有代理BpBinder产生,而只是对象的直接应用了。应用程序并不知道数据是在同一进程间传递还是不同进程间传递,这个只有内核中的Binder知道,所以内核Binder驱动可以将Binder对象数据类型从BINDER_TYPE_BINDER修改为BINDER_TYPE_HANDLE或者BINDER_TYPE_WEAK_HANDLE作为引用传递。
7. Service详解
上一章我们分析了Android IPC架构,知道了Android服务构建的一些基本理念和原理,本章我们将深入分析Android的服务。Android体系架构中三种意义上服务:
Native服务
Android服务
Init空间的服务,主要是属性设置,这个IPC是利用Socket来完成的,这个我将在另外一章来讨论。
Navite服务,实际上就是指完全在C++空间完成的服务,主要是指系统一开始初始化,通过Init.rc脚本起来的服务,例如Service Manger service,Zygote service,Media service , ril_demon service等。
Android服务是指在JVM空间完成的服务,虽然也要使用Navite上的框架,但是服务主体存在于Android空间。Android是二阶段初始(Init2)初始化时建立的服务。
1 Service本质结构
我们还是从Service的根本意义分析入手,服务的本质就是响应客户端请求。要提供服务,就必须建立接收请求,处理请求,应答客服端的框架。我想在Android Service设计者也会无时不刻把这个服务本质框图挂在脑海中。从程序的角度,服务一定要存在一个闭合循环框架和请求处理框架
分析清楚服务框就必须弄清楚以下的机制及其构成。
(1)闭合循环结构放置在哪里?
(2)处理请求是如何分发和管理?
(3)处理框架是如何建立的?
(4)概念框架是如何建立的?
2 Service基本框架分析
Android设计中,Native Service和Android Service采用了同一个闭合循环框架。这个闭合循环框架放置在Native的C++空间中,,[email protected] 和[email protected]两个类完成了全部工作。
在服务框架中,ProcessState是公用的部分,这个公用部分最主要的框架就是闭合循环框架和接收到从Binder来的请求后的处理框架。我们将服务框架用ProcessSate来表示,简言之:
(1) addservice
(2) 建立闭合循环处理框架。
int main(int argc, char** argv)
{
sp
addService(String16("xxx0"), new xxx0Service());
addService(String16("xxx1"), new xxx1Service());
…
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();//闭合循环框架
}
2.1 Native Service
Native Service是在系统Init阶段通过Init.rc脚本建立的服务。
首先来看看一个例子mediaserver@main_mediaserver.cpp的建立过程。
int main(int argc, char** argv)
{
sp
sp
LOGI("ServiceManager: %p", sm.get());
AudioFlinger::instantiate();
MediaPlayerService::instantiate();
CameraService::instantiate();
AudioPolicyService::instantiate();
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}
我们将代码向下展开了一层,更能看到事物的本质。
int main(int argc, char** argv)
{
sp
sp
defaultServiceManager()->addService(String16("media.audio_flinger"), new AudioFlinger());
…
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}
(1)服务进程建立了ProcessState对象,并将给对象登记在进程的上下文中。
(2)建立一个新AudioFlinger对象,并将对象登记Service Manager Service中。
(3)开始就收请求,处理请求,应答这个循环闭合框架。
2.2 Android Service
Androids service是系统二阶段(Init2)初始化时建立的服务。
Android的所有服务循环框架都是建立SystemServer@(SystemServer.java)上。在SystemServer.java中看不到循环结构,只是可以看到建立了init2的实现函数,建立了一大堆服务,并AddService到service Manager。
main() @ com/android/server/SystemServer
{
init1();
}
Init1()是在Native空间实现的(com_andoird_server_systemServer.cpp)。我们一看这个函数就知道了,原来这个闭合循环处理框架在这里:
init1->system_init() @System_init.cpp
在system_init()我们看到了这个久违的循环闭合管理框架。
{
Call "com/android/server/SystemServer", "init2"
…..
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}
Init2()@SystemServer.java中建立了Android中所有要用到的服务:
Entropy Service
Power Manager
Activity Manager
Telephony Registry
Package Manager
Account Manager
Content Manager
System Content Providers
Battery Service
Hardware Service
Alarm Manager
Init Watchdog
Sensor Service
Window Manager
Bluetooth Service
statusbar
Clipboard Service
Input Method Service
NetStat Service
Connectivity Service
Accessibility Manager
Notification Manager
Mount Service
Device Storage Monitor
Location Manager
Search Service
Checkin Service
Wallpaper Service
Audio Service
Headset Observer
Backup Service
AppWidget Service
3 ProcessState和IPCThreadState
从宏观来讲,PocessState及其IPCThreadState处于IPC与内核打交道包装层。前面的章节已经提到,下面我将更详细的分析。有关IPC的c++空间的实现都是从ProcessState这个对象完成的。
我们可以得出如下的结论:不管JVM的Binder做了多么复杂的操作,最终还是需要利用ProcessState 这个c++空间的对象把数据传递给Binder Driver,接收数据也是通过ProcessState这个对象完成,ProcessState是所有Binder IPC必经的通道。
ProcessState放置在全局变量gProcess中,每个进程只有一个ProcessState对象,负责打开Binder设备驱动,建立线程池等。而IPCThreadState每个线程有一个,IPCThreadState实例登记在Linux线程程的上下文附属数据中,主要负责Binder数据读取,写入和请求处理框架。IPCThreadSate在构造的时候,获取进程的ProcessSate并记录在自己的成员变量mProcess中,通过mProcess可以获取到Binder的句柄。
3.1 ProcessState的生命周期
既然ProcessState是Binder通讯的基础,那么Process必须在Binder通讯之前建立。客户端,服务端都必须建立。由于现在重点讨论服务端,所以重心放置在服务端。在Android体系中有c++空间的服务,JVM空间的服务,这两类服务在本质上相同的,只是形式上不同,由于他们都是建立在ProcessState这个基础上,所以在形式上不同就仅仅表现在对OnTransact的回调处理的不同。
Native Service
我们直接可以看到使用sp
Android Service
建立Android Service服务system_init @System_init.cpp中我们可以看到相同的结构。有一点不同的是所有的Android Service都运行在一个进程中:systemsever进程。
3.2 Binder Driver包装 @IPCThreadState
ProcessSate构造的时候,使用open_binder打开/driver/binder,并将句柄记录在mDriverFD,在ProcessState中并不使用这个句柄,真正使用这个Binder设备句柄的是IPCThreadState,所有关于Binder的操作放置在IPCThreadState中:
(1)读取/写入:talkWithDriver()@IPCThreadState对ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)进行包装。
(2)请求处理:executeCommand(...)@ IPCThreadState
(3)循环结构:joinThreadPool()
joinThreadPool()
{
While(1){
talkWithDriver(...)
...
executeCommand(...)
}
}
8. Android启动过程详解
Android从Linux系统启动有4个步骤;
(1) init进程启动
(2) Native服务启动
(3) System Server,Android服务启动
(4) Home启动
总体启动框架图如:
第一步:initial进程(system/core/init)
init进程,它是一个由内核启动的用户级进程。内核自行启动(已经被载入内存,开始运行,并已初始化所有的设备驱动程序和数据结构等)之后,就通过启动一个用户级程序init的方式,完成引导进程。init始终是第一个进程.
Init.rc
Init.marvell.rc
Init进程一起来就根据init.rc和init.xxx.rc脚本文件建立了几个基本的服务:
servicemanamger
zygote
。。。
最后Init并不退出,而是担当起property service的功能。
1.1脚本文件
init@System/Core/Init
Init.c: parse_config_file(Init.rc)
@parse_config_file(Init.marvel.rc)
解析脚本文件:Init.rc和Init.xxxx.rc(硬件平台相关)
Init.rc是Android自己规定的初始化脚本(Android Init Language, System/Core/Init/readme.txt)
该脚本包含四个类型的声明:
Actions
Commands
Services
Options.
1.2 服务启动机制
我们来看看Init是这样解析.rc文件开启服务的。
(1)打开.rc文件,解析文件内容@ system/core/init/init.c
将service信息放置到service_list中。@ system/core/init parser.c
(2)restart_service()@ system/core/init/init.c
service_start
execve(…).建立service进程。
第二步 Zygote
Servicemanager和zygote进程就奠定了Android的基础。Zygote这个进程起来才会建立起真正的Android运行空间,初始化建立的Service都是Navtive service.在.rc脚本文件中zygote的描述:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
所以Zygote从main(…)@frameworks/base/cmds/app_main.cpp开始。
(1) main(…)@frameworks/base/cmds/app_main.cpp
建立Java Runtime
runtime.start("com.android.internal.os.ZygoteInit", startSystemServer);
建立虚拟机
运行:com.android.internal.os.ZygoteInit:main函数。
(3)main()@com.android.internal.os.ZygoteInit//正真的Zygote。
registerZygoteSocket();//登记Listen端口
startSystemServer();
进入Zygote服务框架。
经过这几个步骤,Zygote就建立好了,利用Socket通讯,接收ActivityManangerService的请求,Fork应用程序。
第三步 System Server
[email protected]在Zygote上fork了一个进程: com.android.server.SystemServer.于是SystemServer@(SystemServer.java)就建立了。Android的所有服务循环框架都是建立SystemServer@(SystemServer.java)上。在SystemServer.java中看不到循环结构,只是可以看到建立了init2的实现函数,建立了一大堆服务,并AddService到service Manager。
main() @ com/android/server/SystemServer
{
init1();
}
Init1()是在Native空间实现的(com_andoird_server_systemServer.cpp)。我们一看这个函数就知道了,init1->system_init() @System_init.cpp
在system_init()我们看到了循环闭合管理框架。
{
Call "com/android/server/SystemServer", "init2"
…..
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}
init2()@SystemServer.java中建立了Android中所有要用到的服务。
这个init2()建立了一个线程,来New Service和AddService来建立服务
第三步 Home启动
在[email protected]后半段,我们可以看到系统在启动完所有的Android服务后,做了这样一些动作:
(1) 使用xxx.systemReady()通知各个服务,系统已经就绪。
(2) 特别对于ActivityManagerService.systemReady(回调)
Widget.wallpaper,imm(输入法)等ready通知。
Home就是在ActivityManagerService.systemReady()通知的过程中建立的。下面是ActivityManagerService.systemReady()的伪代码:
systemReady()@ActivityManagerService.java
resumeTopActivityLocked()
startHomeActivityLocked();//如果是第一个则启动HomeActivity。
startActivityLocked(。。。)CATEGORY_HOME
9. Zygote Service详解
在本章我们会接触到这两个单词:
Zygote [生物] 受精卵, 接合子, 接合体
Spawn:产卵
通过这两个单词,我们就可以大体知道Zygote是干什么的了,就是叫老母鸡下蛋。通过“Zygote”产出不同的子“Zygote”。从大的架构上讲,Zygote是一个简单的典型C/S结构。其他进程作为一个客服端向Zygote发出”孵化”请求,Zygote接收到命令就“孵化”出一个Activity进程来。
Zygote系统代码组成及其调用结构:
Zygote.java
提供访问Dalvik “zygote”的接口。主要是包装Linux系统的Fork,以建立一个新的VM实例进程。
ZygoteConnection.java
Zygote的套接口连接管理及其参数解析。其他Actvitiy建立进程请求是通过套接口发送命令参数给Zygote。
ZygoteInit.java
Zygote的main函数入口。
Zygote系统代码层次调用
main()
startSystemServer()…
runSelectLoopMode()
Accept socket connection
Conntecion.RunOnce()
Read argument
folkAndSpecialize
folkAndSpecialize使用Native函数Dalvik_dalvik_system_Zygote_forkAndSpecialize
//native 的获取
dalvik/vm/native
//dalvik_system_Zygote.c
const DalvikNativeMethod dvm_dalvik_system_Zygote[] = {
{ "fork", "()I",
Dalvik_dalvik_system_Zygote_fork },
{ "forkAndSpecialize", "(II[II[[I)I",
Dalvik_dalvik_system_Zygote_forkAndSpecialize },
{ "forkSystemServer", "(II[II[[I)I",
Dalvik_dalvik_system_Zygote_forkSystemServer },
{ NULL, NULL, NULL },
};
在这里我们就有了Zygote服务的全貌理解,也在Code中印证了。【应yk_hu0621修正】{由于Android中没有具体应用程序的入口,都是通过启动Actvity来启动相关的Android应用,而这个 Android应用则对应着Linux进程,Activity便Host在这个应用程序上。}
{原文:Activity在本质上是个什么东西,就是一个Linux进程}
从分析中我们可以看到,Android使用了Linux的fork机制。在Linux中Fork是很高效的。
一个Android的应用实际上一个Linux进程,所谓进程具备下面几个要素,
a.要有一段程序供该进程运行,程序是可以被多个进程共享的。
b..进程专用的系统堆栈空间。
c.进程控制块,在linux中具体实现是task_struct
d.有独立的存储空间。
fork 创造的子进程复制了父亲进程的资源,包括内存的内容task_struct内容,在复制过程中,子进程复制了父进程的task_struct,系统堆栈空间和页面表,而当子进程改变了父进程的变量时候,会通过copy_on_write的手段为所涉及的页面建立一个新的副本。所以只有子进程有改变变量时,子进程才新建了一个页面复制原来页面的内容,基本资源的复制是必须的,整体看上去就像是父进程的独立存储空间也复制了一遍。
再看看下面Google在讲解Dalvik虚拟机的图片,我们就大体有了Android系统中Actvitiy的实际映射状态有了基本的认识。
10.Android GWES基本原理篇
我这里的GWES这个术语实际上从Microsoft 的Window上移植过来的,用GWES来表示Android的窗口事件系统不是那么准确,在Android中Window是个弱化了的概念,更多的表现在View这个概念上。在很大程度上,Android的View的概念可以代替Microsoft Window这个概念,有点和Microsof暗中较劲的意味,你用过的概念我就偏不用,这个也是我以为的设计者意图。
原始GUI基本框架
首先我们从Android的SDK外特性空间开始,在编写Actvitiy时,我们都是面对的处理函数:OnXXXX(),例如有按键按下就是OnKeyDown等,在这个过程中系统做了怎样的处理?要详细的理解这个过程,我们就需要理解Andoid的View管理,窗口系统,消息系统和输入系统。我们还是从最本质的地方开始,Android作为一种嵌入式的图形用户界面系统,它的基本原理与一般GUI的原理是相同的,同时也是遵循GWES(图形窗口事件系统)的一般规律,总体上Android就是管理用户输入和系统屏幕输出的一个系统。其实GWES这个名称更能体现GUI的基本实质要素:图形、窗口、事件。
1. 一般GUI的基本组成
GUI的实现就是对上面提到的三个基本要素的管理,根据这这三个要素的特性及其涉及的范围,GUI在总体上可以分为三部分:
事件管理器
窗口管理器
GDI(绘制与GDI逻辑对象管理)
(1) 事件管理器
收集系统消息,转换并分发系统消息和用户消息给各个窗口对象。
消息队列管理
(2)窗口管理器:
管理窗口的创建,销毁
窗口的绘制
活动窗口,输入焦点的切换
窗口间关系的管理
控件,菜单实现
(3)GDI
上下文设备管理
上下文设备对象管理:字体,画笔等
图形绘制:点、线,填充等
图象操作:位传送、位图操作
2 系统体系构架及其数据流的大体走向
在本质上GUI就是管理用户输入和屏幕输出,我们从上面的体系结构可以看到GUI的这两大数据流的基本流向,这也决定了Android GWES设计的最基本的着眼点。
Android弱化了窗口的概念,着重使用View的概念。所以Android的基本组成可以从上面的图修改成如下的组成:
11.Android GWES消息系统篇
我们要理解Android的消息系统,Looper,Handle,View等概念还是需要从消息系统的基本原理及其构造这个源头开始。从这个源头,我们才能很清楚的看到Android设计者设计消息系统之意图及其设计的技术路线。
消息系统的基本原理
从一般的系统设计来讲,一个消息循环系统的建立需要有以下几个要素:
消息队列
发送消息
消息读取
消息分发
消息循环线程
首先来研究一下消息驱动的基本模型,我使用如下的图形来表示一个消息系统最基本构成:
上面的模型代表应用程序一直查询自己的消息队列,如果有有消息进来,应用消息处理函数中根据消息类型及其参数来作相应的处理。
消息系统要运作起来,必定有消息的产生和消费。我们可以从下图看到消息生产和消费的一个基本的链条,这是一个最基本的,最简单的消息系统。
生产线程将消息发送到消息队列,消息消费者线程从消息队列取出消息进行相应的处理。但是这样简单的模型对实际运行的系统来说是不够的,例如对系统资源的消耗等不能很好的处理,我们就需要一个有旗语的消息系统模型,在上面的消息系统模型中加入了一个旗语,让消息消费者线程在没有消息队列为空时,等待旗语,进入到挂起状态,而有消息到达时,才被唤醒继续运行。当然生产者同时也可以是消费者。
2 Android的消息模型
Android要建立一个消息系统使用了Looper,MessageQueue,Handler等概念,从上节的原理我们可以知道这些都是概念包装,本质的东西就是消息队列中消息的分发路径的和消息分发处理方式的设计。Android巧妙的利用了对象抽象技术抽象出了Looper和Handler的概念。在Looper和Handler两个概念的基础上,通过View的处理函数框架,Android十分完美的达到消息分发的目的。
参照基本消息系统描述模型,我给出了Android消息系统整体框架,表示如下:
Android消息系统消息分发框架
3 Looper,Handler详解
Looper只是产生一个消息循环框架,首先Looper创建了消息队列并把它挂接在Linux的线程上下文中,进入到取消息,并分发消息的循环当中。Handler对象在同一个线程上下文中取得消息队列,对消息队列进行封装操作,最主要的就是SendMessage和担当起dispatchMessage这个实际工作。外部系统需要向某个Android线程发送消息,必须通过属于该AndroidThread的Handler这个对象进行。
Handler属于某个线程,取决Handlerd对象在哪个线程中建立。Handler在构建时做了如下的默认动作:
从线程上下文取得Looper。
通过Looper获取到消息队列并记录在自己的成员mQueue变量中
Handler使用消息队列进行对象封装,提供如下的成员函数:
通过 post(Runnable r)发送。Runnable是消息处理的回调函数,通过该消息的发送,引起Runable 的回调运行,Post消息放置消息队列的前面。Message.callback=Runable.
通过 sendMessage发送。放置在所有的Post消息之后,sendMessage发送消息.
dispatchMessage分发消息。消息带有回调函数,则执行消息回调函数,如何没有则使用默认处理函数:handleMessage。而handleMessage往往被重载成某个继承Handler对象的新的特定的handleMessage。
几乎所有的Message发送时,都指定了target。Message.target=(this).
Looper运行在Activity何处?我们现在可以从代码堆栈中纵观一下Looper的位置。
NaiveStart.main()
ZygoteInit.main
ZygoteInit$MethodAndArgsCall.run
Method.Invoke
method.invokeNative
ActivityThread.main()
Looper.loop()
ViewRoot$RootHandler().dispatch()
handleMessage
....
这样我们就更清楚的了解到Looper的运行位置。
=============================================================================================================
《Android核心分析》整理如下:
12.Android核心分析之Android GEWS窗口管理基本架构篇
Android的窗口管理是C/S模式的。Android中的Window是表示Top Level等顶级窗口的概念。DecorView是Window的Top-Level View,这个View我称之为主View,DecorView会缺省的attach到Activity的主窗口中。主View被加入到WindowManager中,WM使用WindowState与这个主View对应。
Activity建立一个主窗口后,在将主窗口添加到WindowManager时,首先要建立WindowManager代理对象,并打开一个会话(实现IWindowSession AIDL接口),并维持该会话。Activity将通过该会话与WindowManager建立联系,这个Session是C/S体系的基础,Client通过WindowSession将window加入到Window Manager中。一个完整的窗口概念横跨了View,ViewRoot,WindowManager Service。Window,DecorView,View,IWindow ,ISession,WindowState之间的关系如下:
客户端的Activity通过Session会话与WindowManager建立对话,而WindowManager则通过IWindow接口访问Client,将消息传递到Client端,通过消息分发渠道,将消息传递到处理函数OnXXX。
后面我们将通过Client,WM Service分别加以分析。
13.Android GWES窗口管理详解
Android的窗口管理是C/S模式的。Android中的Window是表示Top Level等顶级窗口的概念。DecorView是Window的Top-Level View,这个View我称之为主View,DecorView会缺省的attach到Activity的主窗口中。主View被加入到WindowManager中,WM使用WindowState与这个主View对应。
Activity建立一个主窗口后,在将主窗口添加到WindowManager时,首先要建立WindowManager代理对象,并打开一个会话(实现IWindowSession AIDL接口),并维持该会话。Activity将通过该会话与WindowManager建立联系,这个Session是C/S体系的基础,Client通过WindowSession将window加入到Window Manager中。
一个完整的窗口概念横跨了View,ViewRoot,WindowManager Service。Window,DecorView,View,IWindow ,ISession,WindowState之间的关系如下
Client端的Activity通过Session会话与WindowManager建立对话,而WindowManager则通过IWindow接口访问Client,将消息传递到Client端,通过消息分发渠道,将消息传递到处理函数OnXXX。
后面我们将通过Client,WM Service分别加以分析。
2 Client端
我一致认为在Android中Window的概念并不是个很重要的概念。他的Window类,只是在PhoneWindow和MidWindow中使用。而PhoneWindow只是做了一个具体跟手机功能相关的公用事件的处理,所以在Android中PhoneWindow并不是一个抽象的纯正概念,而是一个跟手机系统相关的一个特别窗口概念,例如按键的默认动作处理,按键音的发出等等。
2.1 View
在Activity中真正重要的概念是View,以下是Google官方对View的定义:
This class represents the basic building block for user interface components. A View occupies a rectangular area on the screen and is responsible for drawing and event handling. View is the base class for widgets, which are used to create interactive UI components (buttons, text fields, etc.). The {@link android.view.ViewGroup} subclass is the base class for layouts, which are invisible containers that hold other Views (or other ViewGroups) and define their layout properties.
我对View不做翻译,翻译成视图好像不太佳,View在Android中,View比视图具有广的外延。View包含了用户交互,包含了显示,视图在中文中仅仅表示了静态的显示。对于View的理解应该从最容易的理解开始。我们使用过编辑器,在Android中这个编辑器就是一个View,这个编辑器需要显示文字,需要接收用户的键盘输入和鼠标选择,但是一个屏幕上有多个编辑器,如何管理,如何切换焦点编辑器,这些都是需要管理的。
客户端的组成:(Window,View,ViewRoot,WindowManager Proxy)
在Activity在performLaunchActivity时,会使用Activity.attach()建立一个PhoneWindow主窗口。这个主窗口的建立并不是一个重点。handleResumeActivity真正要启动一个Activity时候,将主窗口加入到WindowManager,当然并不是将主窗口本身,而是将主窗口的DecorView加入到WindowManager中。
真正Window核心的抽象概念存在于View,ViewRoot,WindowManger中的WindowState。为了描述概念的方便性,我特别提出主View这个概念,这个主View就是Top-Level View of the window. 主View与View想对,突出主View是attatch到主窗口上的。而一般的View则是存在于主View中的。主窗口这个概念,我讲的主窗口实际上就是Android提到的Top Level Window。
我们所提到的概念:View,GroupView,DecorView,ViewRoot都是存在于Client端,只有WindowState这个概念存在于Window Manager Service端。
DecorView实际上是一个ViewGroup。在依存关系上来讲,对看个主窗口来讲,DecorView是Top-Level View.View并不是关注的重点,重要的是我们如何需要知道分发路径是建立在什么关系上的。View的成员变量mParent用来管理View上级关系的。而ViewGroup顾名思义就是一组View的管理,于是在ViewGroup构建了焦点管理和子View节点数组。这样通过View的mParent和ViewGroup的mChildren构建了Android中View直接的关系网。
2.2 Focus Path
所谓的Foucs Path就是我们的KeyEvent传递的路线。一般的我们的KeyEvent在主循环中主View通过View的焦点记录关系传递到焦点View上。例如下图,View22是焦点,我们从最顶层的View通过mFcous的关系链找到最后所形成的路径就是Focus Path。
2.3 ViewRoot,Window Manager Proxy
ViewRoot与Window Manager的核心是IWindowSession和IWindow。ViewRoot通过IWindowSession添加窗口到Window Manager。而IWindow这是Window Manager分发消息给Client ViewRoot的渠道。利用AIDL接口进行进程间通信。
ViewRoot实际是一个Handler,ViewRoot建立主View与WindowsManger通讯的桥梁。ViewRoot在本质上一个Handler。我们知道Handler的基本功能就是处理回调,发送消息。
Activity在使用getSystemService获取WindowManagerImpl ,建立了一个WindowManagerImpl实例,即Window Manager服务的代理:
wm=(WindowManagerImpl)context.getSystemService(Context.WINDOW_SERVICE);并调用wm.addview添加窗口到WMService中。
这个过程在客户端建立了什么样的管理框架,并如何这个会话?在Window Manager Proxy中建立了View,Layout ,ViewRoot三者的对应关系表。构造一个ViewRoot就会打开一个session,并利用IWindowSession建立会话上下文。
4 Window Manager Service
本次对于Window Manager Service的研究仅限于FocusWindow,消息系统。其他的部分将在后面的专门章节讨论。
Window Manager管理的窗口是应用程序的Top-level窗口,我这里参照Window的概念叫主窗口。主窗口为什么要放在在Service这边来管理呢?为什么不放在Client那边?主窗口放置在一起管理是为了计算Z-order序列,根据应用程序的状态来显隐应用程序的窗口。我想Android设计者在考虑设计窗口系统的时候,一定首先考虑:
窗口z-order序的管理
活动窗口的计算,及其变化通知
窗口归属(属于哪个应用)
输入法管理
Window Service大体上实现了如下的功能:,
(1)Z-ordered的维护函数
(2)输入法管理
(3)AddWindow/RemoveWindow
(4)Layerout
(5)Token管理,AppToken
(6)活动窗口管理(FocusWindow)
(7)活动应用管理(FocusAPP)
(8)转场动画
(9)系统消息收集线程
(11)系统消息分发线程
在服务端的窗口对象叫做WindowState。在Service维护了一个mWindow数组,这个mWindow就是Window的Z-order序数组。mWindowMap用于记录
WindowState有一个叫做mClient成员变量来记录客户端IWindow实例,通过IWindow接口实例,Service可以访问客户端的信息,说以IWindow是Service连接View桥梁。
(1) FocusWindow活动窗口如何计算?
基本原理就是查找前景应用(FousActivity),并同Z-Order序中找出属于该FousActivity(AppToken)的主窗口,这个窗口就是计算出来的Focus Window。
(2)为什么要提出Token这个概念呢?
一个应用程序要管理自己的窗口,那么如何来标识该窗口是属于某个Activity,Andoid设计者提出了AppToken这个概念。AppToken在本质上的描述:
(3)系统消息收集与处理
我们下面重点研究Service中的系统消息收集模式及其分发模式。Service使用KeyQ作为专门的消息队列。
KeyEvent
TouchEvent
TrackballEvent
系统有两个线程:
KeyQ线程,通过Navite函数readEvent轮询设备,将读取的结果放置在KeyQ队列中。
系统dispatcher 等待在KeyQ消息队列上,一旦从消息队列中获取到消息,就通过分发函数通过mClient传递到Client端。
14.Android GWES输入系统篇
依照惯例,在研究Android输入系统之前给出输入系统的本质描述:从哲学的观点来看,输入系统就是解决从哪里来又将到哪里去问题。输入的本质上的工作就是收集用户输入信息并放置到目标位置。
Android在源代码分类上,并没有输入系统分类。本章的输入系统研究是一个综合的分析,前面的GWES的分析,特别是View的Focus Path以及Window Manager Proxy是本章分析的基础,如果没有理解,请参阅前面的窗口管理的相关章节。
Android输入系统的组成
输入系统由如下几部分组成:
1)后台窗口管理服务
2)Focus Activity
3)Focus Window
4)Focus View:用来接收键盘消息
从输入系统这个角度去看Android的Window Manager服务解决了用户信息输入收集,而FocusActvitiy,Focus Window、Focus View这些概念的设计是为了解决用户输入应该放到哪里去这个问题。在整个Android系统中,同时只有一个一个Focus Window,而属于该Window的Focus View才是真正的Focus View。
在Android系统中,在设计上要求多个Actvitiy同时存在运行。在实现中,每次把Actvitiy变成Focused Actvitiy时([email protected])激活程序的时候,就把该Activity的主窗口设置成前景窗口,即系统中的顶层窗口,AppToken概念的引进就是为了解决窗口对象的归属问题。在这个过程中,在逻辑上看,我们挑选了一个Activity作为了Focus Activity来接收系统的消息,实质上这个Focus Activity的Focus窗口就是前景窗口。
Focus窗口的改变将改变焦点View,前景窗口的改变也将引起焦点View的变化。焦点和光标的概念用于管理输入设备和输入事件的传送。光标是一个绘制在屏幕之上的小位图,指示当前的输入位置。键盘输入有类似的输入焦点和键盘输入插入符的概念。只有具有输入焦点的窗口才能获取键盘事件。改变窗口的焦点通常由特殊的按键组合或者TouchEvent事件完成。具有输入焦点的窗口通常绘制有一个键盘插入符。该插入符的存在、形式、位置,以及该插入符的控制完全是由窗口的事件处理例程完成的。
现在站在更宏观的位置来看Actvitiy的输入系统,可以从Linux Driver开始到输入框结束的整个链条,我这里给出大输入系统的概念,Android大输入系统包含:Linux driver, Window Manager, Message System, View Focus Path,Focus View。
Android输入系统架构图
现在从Android的代码分析的角度,来看看输入系统的组成。这个过程从代码中分析处理:
在Window Manager Service端
readEvent@com_android_server_KeyInputQueue.cpp
在Client端
KeyInputQ在WindowMangerService中建立一个独立的线程InputDeviceReader,使用Native函数readEvent来读取Linux Driver的数据构建RawEvent,并放入到KeyQ消息队列中。
InputDispatcherThread从KeyQ中读取Events,找到Window Manager中的Focus Window,通过Focus Window记录的mClient接口,将Events专递到Client端。Client端在根据自己的Focus Path传递事件,直到事件被处理。
15.Android GWES输入系统之输入路径详解
1 输入路径的一般原理
按键,鼠标消息从收集到最终将发送到焦点窗口,要经历怎样的路径,是Android GWES设计方案中需要详细考虑的问题。按键,鼠标等用户消息消息的处理可分为不同的情况进行判定:
(1)用户输入根据系统状况是否应该派送。如在ScreenOff的情况下,在按键属于特殊按键的情况下等
(2)是否有拦截Listener
(3)对按键事件来讲,是否存在输入法
(4)是否是焦点终点
(5)是否为焦点切换按相关键
这些情况都是设计输入路径需要考虑的基本条件。
1.1一般的输入路径设计
该输入路径实际上是指的按键消息(MSG_KEYDOWN,MSG_KEYUP, MSG_LongPress)的输入路径,即从活动主窗口到焦点窗口所经历的路程。
将信息输入路径分为两步:
Step 1)窗口管理器将信息发送到活动窗口
Step 2)活动窗口通过缺省处理函数将该消息一层层的传递到焦点。
这样应用程序可以在活动View的处理函数中来预先处理用户输入信息,从而增强应用对用户信息的控制力。
传递路径是通过View的缺省处理函数Onxxx来完成。通过ActiveView ->focus->focus->focus的链条关系,一级一级的将按键消息MSG_KEYDOWN,MSG_KEYUP, MSG_CHAR等传递到focus窗口。
此时用户按键输入先发送到输入法窗口,经过输入法管理器处理,过滤后将输入法产生的结果放置到焦点View。
1.3输入系统整体流程
下面示意图是Android输入系统的数据流途径,通过WM的输入系统线程收集消息,分发到Focus Activity消息队列,然后通过消息系统派发。
2 Android输入路径详细描述
2.1 第一步:用户数据收集及其初步判定
KeyInputQ在WindowMangerService中建立一个独立的线程InputDeviceReader,使用Native函数readEvent来读取Linux Driver的数据构建RawEvent,放入到KeyQ消息队列中。
preProcessEvent()@[email protected]这个是在输入系统中的第一个拦截函数原型。KeyQ重载了preProcessEvent()@WindowManagerService.java。在该成员函数中进行如下动作:
(1) 根据PowerManager获取的Screen on,Screen off状态来判定用户输入的是否WakeUPScreen。
(2) 如果按键式应用程序切换按键,则切换应用程序。
(3) 根据WindowManagerPolicy觉得该用户输入是否投递。
2.2 第二步 消息分发第一层面
InputDispatcherThread从KeyQ中读取Events,找到Window Manager中的Focus Window,通过Focus Window记录的mClient接口,将Events专递到Client端。
如何将KeyEvent对象传到Client端:
在前面的章节(窗口管理ViewRoot,Window Manager Proxy)我们已经知道:在客户端建立Window Manager Proxy后,添加窗口到Window Manager service时,带了一个跟客户ViewRoot相关的IWindow接口实例过去,记录在WindowState中的mClient成员变量中。通过IWindow这个AIDL接口实例,Service可以访问客户端的信息,IWindow是Service连接View桥梁。
看看在Client ViewRootKeyEvent的分发过程
IWindow:dispatchKey(event)
dispatchKey(event)@W@[email protected]
ViewRoot.dispatchKey(event)@ViewRoot.java
message>
sendMessageAtTime(msg)@[email protected]
至此我们通过前面的Looper,Handler详解章节的分析结论,我们可以知道Key Message已经放入到应用程序的消息队列。
2.3第三步:应用消息队列分发
消息的分发,在Looper,Handler详解章节我们分析了Looper.loop()在最后后面调用了handleMesage.
…
ActivityThread.main()
Looper.loop()
ViewRoot$RootHandler().dispatch()
handleMessage
....
注意到在分发的调用msg.target.dispatch(),而这个target在第二层将消息sendMessageAtTime到消息队列时填入了mag.target=this即为msg.target=ViewRoot实例。所有此时handleMessage就是ViewRoot重载的handleMessage函数。
handlerMessage@[email protected]
deliverkeyEvent
如果输入法存在,dispatchKey到输入法服务。
在这里需要强调的是,输入法的KeyEvent的拦截并没有放入到Window Manager Service中,而是放入到了客户端的RootView中来处理。
2.4第四步:向焦点进发,完成焦点路径的遍历。
分发函数调用栈
mView.dispatchKeyEvent:mView是与ViewRoot相对应的Top-Level View.如果mView是一个ViewGroup则分发消息到他的mFocus。
mView.dispatchKeyEvent @ViewGroup (ViewRoot@root)
Event.dispatch
mFocus.dispatchKeyEevnet
如果此时的mFocu还是一个ViewGroup,这回将事件专递到下一层的焦点,直到mFocus为一个View。通过这轮调用,就遍历了焦点Path,至此,用户事件传递完成一个段落。
2.5第五步 缺省处理
如果事件在上述Focus View没有处理掉,并且为方向键之类的焦点转换相关按键,则转移焦点到下一个View。
16.Android电话系统-概述篇
首先抛开Android的一切概念来研究一下电话系统的最基本的描述。我们的手机首先用来打电话的,随后是需要一个电话本,随后是PIM,随后是网络应用,随后是云计算,随后是想我们的手机无所不能,替代PC。但是作为一个电话的基本功能如下:
0)拨叫电话,接听电话,挂断电话,发送短信,网络连接,PIM管理
1)由于电话运营商为我们提供了呼叫等待,电话会议等补充业务,所以我们的手机需要管理多路通话,如何管理?
2)来电时,我们要播出来电铃声,接通时我们需要切换语音通道,这个又跟多媒体系统打上了交道,例如有耳机插上了,有蓝牙耳机连上了,系统该做如何的管理和切换?
3)上网的网络通路建立(例如GSM GPRS),如何PPP连接并连接到LinuxSocket通道上的?系统如何管理数据连接?
4)AP跟Modem通讯时通过AT指令的,如何将AT指令变成一个个具体的操作函数,如何管理Modem发给我们的回应,AT命令通道,数据通道如何管理?
5)sim卡的电话本如何管理?
上面的关于手机的基本问题,Android电话系统设计者必须要解答的问题。该设计如何的管理框架,提出什么概念来表达?所以要分析Android的电话部分,还是需要理解电话实现的背景知识,通讯协议,大体框架。
我们回到电话系统基本构成上,先从整体上去把握一下电话模块的大体框架,先从空中俯瞰。我给出的图是一般的智能手机的框架图,该框架基本能够概括所有手机电话模块的构成,当然也包括Android的电话系统构成。
智能机架构一般是应用处理器+Modem。应用处理器与Modem的连接使用串口或者USB。在一个硬件串口通路上实现为了要同时实现数据传输并同时实现控制Modem,就需要实现多路复用协议(GSM TS07.10),在底层我们在多路复用的基础上虚拟了两个串口,一个用于CMD通道,一个用于DATA通道。电话的所有控制通路都是在这连个通道上。
RIL,Radio Interface Layer。本层为一个协议转换层,手机框架需要适应多类型的Modem接入到系统中,而对于不同的Modem有不同的特性,AT指令的格式或者回应有所不同,但是这种特性在设计应用时不可能完全考虑和兼容。所以设计者在设计电话系统时,建立了一个虚拟电话系统,为该虚拟电话系统规定了标准的功能,上层的电话管理都是建立在这些标准的功能基础之上。而RIL则是将虚拟电话系统的标准功能转换成实际的所使用的Modem的AT指令。
Android设计者将电话系统设计成了三部分。
Andoird的Phone Service其实是PhoneApp。GSMPhone(CDMAPhone)是Phone Service核心的对象,他包含了如下的相关对象。
我们的分析任务就是要把这些对象的相互关系,及其对象间数据传递关系弄清楚。首先我们给出以下的Android电话系统的框架,以便对Android电话系统有个概要的认识,然后从数据流的角度,以及对象的引用关系来分析系统。下面是android电话系统整体框架图。
==========================================================================================================
17.电话系统之rilD
Rild是Init进程启动的一个本地服务,这个本地服务并没有使用Binder之类的通讯手段,而是采用了socket通讯这种方式。RIL(Radio Interface Layer)
Android给出了一个ril实现框架。由于Android开发者使用的Modem是不一样的,各种指令格式,初始化序列都可能不一样,GSM和CDMA就差别更大了,所以为了消除这些差别,Android设计者将ril做了一个抽象,使用一个虚拟电话的概念。这个虚拟电话对象就是GSMPhone(CDMAPhone),Phon对象所提供的功能协议,以及要求下层的支撑环境都有一个统一的描述,这个底层描述的实现就是靠RIL来完成适配。
Andoid将RIL层分为两个代码空间:RILD管理框架,AT相关的xxxril.so动态链接库。将RIL独立成一个动态链接库的好处就是Android系统适应不同的Modem,不同的Mode可以有一个独立的Ril与之对应。从这个层面上看,Rild更多是一个管理框架。
而ril是具体的AT指令合成者和应答解析者。从最基本的功能来讲,ril建立了一个侦听Socket,等待客户端的连接,然后从该连接上读取RIL-Java成传递来的命令并转化成AT指令发送到Modem。并等待Modem的回应,然后将结果通过套接口传回到Ril-Java层。下图是Ril-D的基本框架:
下面的数据流传递描述图表描述了RIL-JAVA层发出一个电话指令的5 步曲。
在AT通讯的过程中有两类响应:一种是请求后给出应答,一种是通知类,即为不请自来的,例如短信通知达到,我们称该类通知为URC。在Rild中URC和一般的Response是分开处理的,概念上URC由[email protected]处理,而Response由handleFinalResponse来处理。
1 Event Loop
Rild管理的真正精髓在ril.cpp,ril_event.cpp中,在研究的过程中,可以看到设计者在抽象上所下的功夫,设计得很优美。Event Loop的基本工作就是等待在事件端口(串口,Socket),一旦有数据到达就根据登记的Event回调函数进行处理。现在来看Ril设计者是如何建立一套管理框架来完成这些工作的?
1.1 Event对象
Event对象构成:(fd,index,persist,func,param)
fd 事件相关设备句柄。例如对于串口数据事件,fd就是相关串口的设备句柄
index
persist 如果是保持的,则不从watch_list中删除。
func 回调事件处理函数
param 回调时参数
为了统一管理事件,Android使用了三个队列:watch_list,timer_list,pending_list,并使用了一个设备句柄池readFDS。
readFDS:是Linux的fd_set,readFDS保存了Rild中所有的设备文件句柄,以便利用select函数统一的完成事件的侦听。
watch_list:监测时间队列。需要检测的事件都放入到该队列中。
timer_list:timer队列
pending_list:待处理事件队列,事件已经触发,需要所回调处理的事件。
事件队列队列的操作:ril_event_add,ril_event_del, ril_timer_add
在添加操作中,有两个动作:
(1) 加入到watch_list
(2) 将句柄加入到readFDS事件句柄池。
1.2 ril_event_loop()
我们知道对于Linux设备来讲,我们可以使用select函数等待在FDS上,只要FDS中记录的设备有数据到来,select就会设置相应的标志位并返回。readFDS记录了所有的事件相关设备句柄。readFDS中句柄是在在AddEvent加入的。所有的事件侦听都是建立在linux的select readFDS基础上。
ril_event_loop 利用select等待在readFDS(fd_set)上,当select设备有数据时,ril_event_loop会从select返回,在watch_list中相应的Event放置到pend_list,如果Event是持久性的则不从watch_list中删除。然后ril_event_loop遍历pengding_list处理Event事件,发起事件回调函数。
1.3 几个重要的Event
上面分析了ril-d的框架,在该框架上跑的事件有什么
(1)s_listen_event- (s_fdListen,listenCallback)
listenCallback处理函数,
接收客户端连接:s_fdCommand=accepte(..)
添加s_commands_event()
重新建立s_listen_event,等待下一次连接
(2) s_command_event(s_fdCommand,ProcessCommandsCallback)
从fdCommand Socket连接中读取StreamRecord
使用ProcessCommandBufer处理数据
s_listen_event在大的功能上处理客户端连接(Ril-JAVA层发起的connect),并建立s_commands_event去处理Socket连接发来的Ril命令。ProcessCommandBufer实际上包含了Ril指令的下行过程。
1.4 下行命令翻译及其组织@ProcessCommandBuffer
RIL_JAVA传递的命令格式:Parcel ,由命令号,令牌,内容组成。RIL_JAVA到达RIL_C时转为构建本地RequestInfo,并将被翻译成具体的AT指令。由于每条AT命令的参数是不同的,所以对不同的AT指令,有不同的转换函数,在此Android设计在这里做了一个抽象,做了一个分发框架,通过命令号,利用sCommand数组,获得该命令的处理函数。
sComand[]={
<...>
}
sComand 存在于Ril_command.h中。
&sComand[]=
<
{RIL_REQUEST_GET_IMEI, dispatchVoid, responseString},
{RIL_REQUEST_DIAL, dispatchDial, responseVoid},
{….}
>
dispatchXxx函数一般都放在在Reference-ril.c中,Reference-ril.c这个就是我们需要根据不同的Modem来修改的文件。
1.5 send_at_command框架
send_at_command是同步的,命令发送后,send_at_command将等待在s_commandcond,直到有sp_response->finalResponse。
2 read [email protected]
Read loop是解决的问题是:解析从Modem发过来的回应。如果遇到URC则通过handleUnsolicited上报的RIL_JAVA。如果是命令的应答,则通过handleFinalResponse通知send_at_command有应答结果。
对于URC,Rild同样使用一个抽象数组@Ril.CPP.
static UnsolResponseInfo s_unsolResponses[] = {
#include "ril_unsol_commands.h"
};
并利用RIL_onUnsolicitedResponse将URC向上层发送。
3 Ril-d的整体数据流及其控制流示意图
18.Android电话系统之RIL-Java
RIL-Java在本质上就是一个RIL代理,起到一个转发的作用,是Android Java概念空间中的电话系统的起点。在RIL-D的分析中,我们知道RILD建立了一个侦听套接口,等待RIL-Java的连接。一旦连接成功,RIL-JAVA就可发起一个请求,并等待应答,并将结构发送到目标处理对象。在RIL-Java中,这个请求称为RILRequest。为了直观起见,我还是不厌其烦的给出RIL-Java的框架图。
RIL-Java的大框架包含了四个方面:
Receiver,Sender,CommandInterface,异步通知机制
(1) Command Interface
在ril.java源代码中,我们可以看到RIL-JAVA对象提供了如下的Command Interface:
…
getlccCardStatus
getCurrrentCalls
dial
acceptCall
rejectCall
sendDTMF
sendSMS
setupDataCall
setRadioPower
…
为什么要定义这些接口呢?这函数接口不是凭空捏造出来的,这些都是电话的基本功能的描述,是对Modem AT指令的提炼抽象。大多数Modem都是根据通讯协议提供接口,我们如果不熟悉通讯协议,请参阅3GPP的相关文档,以及自己使用的Modem的SPEC说明。
V.25ter AT Commands
3GPP 07.07 AT Comamnds-General commands
3GPP 07.07 AT Comamnds-Call Control commans
3GPP 07.07 AT Comamnds-Network Service related commands
3GPP 07.07 AT Comamnds-MT control and status command
3GPP 07.07 AT Comamnds-GPRS Commands
3GPP 07.07 Mobile Termination Errors
3GPP 07.05 SMS AT Commands
(2)Receiver
Receiver连接到RILD的服务套接口,接收读取RILD传递过来的Response Parcel。Response分为两种类型,一种是URC,一种是命令应答。对于URC将会直接分发到通知注册表中的Handler。而命令应答则通过Receiver的异步通知机制传递到命令的发送者进行相应处理。
(3)Sender
Sender应该分为两部分架构,
上层函数调用Command Interface将请求消息发送到Sender的架构。
Sender接收到EVENT_SEND消息后,将请求发送到RILD的架构。
(4)异步应答框架
对于异步应答来讲,命令的发起者发送后,并不等待应答就返回,应答的回应是异步的,处理结果通过消息的方式返回。站在设计者的角度思考如何设计合适的框架来完成异步通讯的功能呢?对于异步系统我们首先应该考虑的是如何标识命令和结果,让命令和结果有一个对应关系,还有命令没有响应了,如何管理命令超时?让我们来看看Android设计者如何完成这些工作。
Android设计者利用了Result Message 和RILRequest对象来完成Request和Result的对应对于关系。在上层做调用的时候生成Result Message对象传递到ril_java,并在Modem有应答后,通过Result Message对象带回结果。如何保证该应答是该RILRequest的呢?Android设计者还提供了一个Token(令牌)的概念。在源代码中RILRequest的mSerail就用作了Token。Token用来唯一标识每次发送的请求,并且Token将被传递到RILD,RILD在组装应答是将Token写入,并传回到ril-java,ril-java根据该Token找到相应的Request对象。
(4.1)RIL命令的发送模式
协议的真正实现是在rild中,RIL-JAVA更多的是一个抽象和代理,我们在研究源代码的过程中就会体会到到RIL-JAVA中的命令函数都有一个共同的框架。
SendXxxCmd(传入参数Data,传出参数result){
组合RILRequest(请求号,result,mSerail)
Data->RR
send(RILRequest): Message
}
1)RILRequest
请求号:
request将传递到RILD用以标识命令,request代表某个功能。例如拨叫的request号为:RIL_REQUEST_DIAL。在libs/telephony/ril_commands.h有定义。RILRequest.obtain@RILRequest根据命令请求号,传入参数Result Message,mSerail构造了一个RILRequest。Result Message将带回应答信息回到命令的发起者。
mSerail:
Android使用了一个RILRequest对象池来管理Andoird RILRequest。mSerail是一个递增的变量,用来唯一标识一个RILRequest。在发送时正是用了该变量为Token,在rild层看到的token就是该mSerail。
EVENT_END:
EVENT_END@handleMessage@[email protected]
2)发送步骤:
第一步:
生成RILRequest,此时将生成m_Serial(请求的Token)并将请求号,数据,及其Result Message 对象填入到RILRequest中
第二步:
使用send将RILRequest打包到EVENT_SEND消息中发送到到RIL Sender Handler,
第三步:
RilSender 接收到EVENT_SEND消息,将RILRequest通过套接口发送到RILD,同时将RILRequest保存在mRequest中以便应答消息的返回。
(4.2) 接收模式
第一步:分析接收到的Parcel,根据类型不同进行处理。
第二步:根据数据中的Token(mSerail),反查mRequest,找到对应的请求信息。
第三步:将是数据转换成结果数据。
第四步:将结果放在RequestMessage中发回到请求的发起者。
4.3)详细的GSMCallTracker,RIL-Java函数对照
19.电话系统之GSMCallTacker
通话连接管理
GSMCallTracker在本质上是一个Handler。
GSMCallTracker是Android的通话管理层。GSMCallTracker建立了ConnectionList来管理现行的通话连接,并向上层提供电话调用接口。
在GSMCallTracker中维护着通话列表:connections。顺序记录了正连接上的通话,这些通话包括:ACTIVE,DIALING,ALERTING,HOLDING,INCOMING,WAITING等状态的连接。GSMCallTracker将这些连接分为了三类别进行管理:
RingingCall: INCOMING ,WAITING
ForegourndCall: ACTIVE, DIALING ,ALERTING
BackgroundCall: HOLDING
上层函数通过getRingCall(),getForegrouandCall()等来获得电话系统中特定通话连接。
为了管理电话状态,GSMCallTracker在构造时就将自己登记到了电话状态变化通知表中。RIL-Java一收到电话状态变化的通知,就会使用EVENT_CALL_STATE_CHANGE通知到GSMCallTacker
在一般的实现中,我们的通话Call Table是通过AT+CLCC查询到的,CPI可以通知到电话的改变,但是CPI在各个Modem的实现中差别比较大,所以参考设计都没有用到CPI这样的电话连接改变通知,而是使用最为传统的CLCC查询CALL TABLE。在GSMTracker中使用connections来管理Android电话系统中的通话连接。每次电话状态发生变化是GSMTracker就会使用CLCC查询来更新connections内容,如果内容有发生变化,则向上层发起电话状态改变的通知。
1 RIL-JAVA中发起电话连接列表操作
在RIL-JAVA中涉及到CurrentCallList查询的有以下几个操作:
(1)hangup
(2)dial
(3)acceptCall
(4)rejectCall
在GSMcallTracker在发起这些调用的时候都有一个共同的ResultMessage构造函数:obtainCompleteMessage()。obtainCompleteMessage()实际上是调用:
obtainCompleteMessage(EVENT_OPERATION_COMPLETE)
这就意味着在这些电话操作后,GSMCallTracker会收到EVENT_OPERATION_COMPLETE消息,于是我们将目光转移到handleMessage()@GSMCallTracker的EVENT_OPERATION_COMPLETE事件处理:operationComplete@GSMCallTracker。
operationComplete()操作会使用cm.getCurrentCalls(lastRelevantPoll)调用,向RILD发起RIL_REQUEST_GET_CURRENT_CALLS调用,这个最终就是向Modem发起AT+CLCC,获取到真正的电话列表。
2 在RILD中,引起getCurrentCalls调用
(1)在RILD中,收到URC消息:
+CRING
RING
NO CARRIER
+CCWA
将会使用RIL_onUnsolicitedResponse( RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED),主动向ril-java上报RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED消息。
(2) 在处理requestCurrentCalls时,使用CLCC查询通话连接(CALL TABLE)后,如何发现有call Table不为空则开启一个定时器,主动上报RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED消息,直到没有电话连接为止。
在RIL-Java层收到RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED这个URC,并利用mCallStateRegistrants.notifyRegistrants(new AsyncResult(null, null, null))来通知电话状态的变化,此时GSMTracker会接收到EVENT_CALL_STATE_CHANGE消息,并使用
pollCallsWhenSafe()-> cm.getCurrentCalls(lastRelevantPoll);
来发起查询,并更新JAVA层的电话列表。
3 handlePollCalls电话列表刷新
首先我们来看看是什么引起了handlePollCalls的调用。
上面的1,2分析了,Android电话系统中所有引起电话连接列表更新的条件及其处理。他们共同的调用了cm.getCurrentCalls(lastRelevantPoll) 来完成电话列表的获取。
lastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT)
我们这里就从可以看到获取到的电话列表Result使用handlePollCalls进行了处理。Result实际上是一个DriverCall列表,handlePollCalls的工作就是将当前电话列表与RIL-Java的电话列表对比,使用DriverCall列表更新CallTracker的电话列表connections,并向上传递电话状态改变的通知。
20.Android应用程序框架之无边界设计意图
Android的应用框架的外特性空间的描述在SDK文档(http://androidappdocs.appspot.com/guide/topics/fundamentals.html#acttask)有十分清楚的描述,Android应用的基本概念,组件生命周期等等有详细的描述。在外特性空间中,Android提供了Activity,Service,Broadcast receivers,Content Provider,Intent,task等概念,我在这里不讨论这些概念定义,因为SDK文档已经讲得够详细。
在阅读SDK文档和研究Activity这个概念时,我感觉到了在Android中若隐若现的Android自由无边界这个设计意图。Android的应用只是一个虚的概念,并没有实际的入口,这个不像Window平台上的应用程序的概念,Android更多的是提供组件(Components)的概念。为什么要虚化应用的概念?我想这个虚化就是自由无边界设计意图的直接体现。突出请求和服务,突出组件个体,弱化边界,系统的各个组件可以自由的无边界的交流,服务请求者直接发出请求,不论这个对象在何处和属于谁的,组件是自由独立的个体,一个应用程序可以直接请求使用其他的应用的的组件,这个是Android应用框架设计的核心理念,其他的一切都是在为这个核心理念服务。
让程序员忽略应用的概念,甚至彻底的抛弃进程这样的概念,程序员看到的就是一个一个的组件,应用程序员利用这些组件来架构成一个所谓的应用,那么设计者首先要考虑的是什么呢?我想应该是一个抽象的应用模型,在这个模型下产生概念和接口。
我们知道MicroSoft提出了Application,Windows的概念,有前景应用(Foreground Application)概念,MicroSoft的应用模型中用户交互则完全交给了Window,各种界面的呈现都是属于这个应用的是孤立的,应用程序之间的各个构成对象不能相互访问,最多提供一个进程间通讯机制,那个也是应用程序层面的。虽然Microsoft后来也提出了组件,分布式组件等概念,但是这些不是根植在Windows系统中,而Android则是彻底的组件化思想构建,一开始的应用程序概念就是Activity,Service,Broadcast receivers,Content Provider,Intent,Task。这些概念体现了一个人机交互的模型本质:
Activity是Android应用的核心概念,简而言之Activity为用户交互管理者,有一个可视界面呈现,而Service跟Activity的区别是他在后台运行,没有界面呈现。而Intent的意义是意图,他在Android的概念空间中,代表消息,这个消息代表了请求的意图。
Activity可以到处存在,提供服务,消除空间差别,Activity是一个独立的个体,更能表现面向对象的实质。这个个体需要接受另外的个体的消息,可以随时发起对另外一个个体的请求。个体是自由的,Android中你可以开始一个Activity,但是没有权利消灭一个Activity,这是个体权利的体现,个体的消灭是由系统决定的,这个就是Android中Activity蕴含的人文意义。
21.Android应用框架之AndroidApplication
Android提供给开发程序员的概念空间中Application只是一个松散的表征概念,没有多少实质上的表征。在Android实际空间中看不到实际意义上的应用程序的概念,即使有一个叫Application的类,这个也就是个应用程序上下文状态,是一个极度弱化的概念。Application只是一个空间范畴的概念,Application就是Activity,Service之类的组件上下文描述。Application并不是Android的核心概念,而Activity才是Android的核心概念。
从Android的SDK文档中,我们知道一般情况Android应用程序是由以下四种组件构造而成的:Activity,Broadcast Intent Receiver,服务(Service),内容提供器(Content Provider)。我们可以使用下面的图来表示一下Android的概念空间。这些组件依附于应用程序中,应用程序并不会一开始就建立起来,而是在这些组件建立起来后,需要运行时,才开始建立应用程序对象。
2.1应用进程名称
为什么要从应用进程名称开始?作为内核研究,我们还是回到问题的最本质处:不管Activity,Service等组件如何设计和运行,它要提供服务,就必须要依附在Linux的进程上,建立消息循环,组件才能够真正的运作。Activity实例是如何Hosting在Linux进程上的?这个是我们首先想要弄明白的。
我们在的项目中看到android:process="string"这个定义。
allowClearUserData=["true" | "false"]
android:allowTaskReparenting=["true" | "false"]
android:backupAgent="string"
… android:label="string resource"
android:manageSpaceActivity="string"
android:name="string"
android:permission="string"
android:persistent=["true" | "false"]
android:process="string"
android:restoreAnyVersion=["true" | "false"]
android:taskAffinity="string"
android:theme="resource or theme" >
. . .
在SDK用已经描述的很清楚到了。
android:process
The name of a process where all components of the application should run. Each component can override this default by setting its own process attribute.
By default, Android creates a process for an application when the first of its components needs to run. All components then run in that process. The name of the default process matches the package name set by the element.
By setting this attribute to a process name that's shared with another application, you can arrange for components of both applications to run in the same process — but only if the two applications also share a user ID and be signed with the same certificate.
为什么要提出这么一个定义?android:process名称。
默认状态下,Activity Manager Service在应用程序的第一个组件需要运行时将会为应用程序建立一个进程,而这个进程的名字就是android:process=”string”所指定,缺省的是应用程序包的名字。该进程一旦建立,后面的该应用的组件都将运行在该进程中,他们绑定的根据就是这个Android:Process指定的名称,因为在他们都在同一个应用程序包里,也就具有了同样的进程名字,于是他们都托管在了同一进程中。组件将通过ClassLoader从Package中获取到应用程序的信息。
在建立Actvitiy时,如果在应用进程端没有应用对象,系统在该过程中利用makeApplication建立一个Application对象,实例化"android.app.Application",建立一个应用程序上下文完成例如资源,package等信息管理。
2.2 ActivityThread运行框架
在分析中,我们可以看到真正对应应用进程的不是Application而是ActivityThread。我们从实际的应用堆栈可以看到:
NaiveStart.main()
ZygoteInit.main
ZygoteInit$MethodAndArgsCall.run
Method.Invoke
method.invokeNative
ActivityThread.main()
Looper.loop()
....
每个应用程序都以ActivityThread.main()为入口进入到消息循环处理。对于一个进程来讲,我们需要这个闭合的处理框架。
ActivitiyThread是应用程序概念空间的重要概念,他建立了应用进程运行的框架,并提供了一个IActivityThread接口作为与Activity Manager Service的通讯接口.通过该接口AMS可以将Activity的状态变化传递到客户端的Activity对象。
2.3 ActivitiyThread的建立
为了叙述的方便我将Actvitiy Manager Service简写成AMS。
在AMS中关于应用程序的概念是ProcessRecord,请求都是从Activity,Service…等开始的,在Activity需要Resume时,此时如果与Activity相关的应用进程没有起来,AM则启动应用进程。
AMS与应用进程的绑定分为两个部分,第一部分就是AM建立应用进程,第二部分就是应用进程Attach到AM,与AM建立通讯通道。
1)创建建立进程:startProcessLocked(processName,Appinfo.uid)。该函数在StartSecificActivityLocked等调用。
(1)建立ProcessRecord对象app,并将该对象添加到mProcessNames中。应用对象在mProcessNames中使用应用名字和uid来标识自己。如果在同一个Package中的Activity,如果都使用默认设置,那么这些Activity都会托管在同一个进程中,这是因为他们在带的ApplicationInfo中的ProcessName都是一样的。
mPidsSelfLocked数组记录了PID,这个将会在应用进程跑起来后,将自己Attach到AM时,根据pid找到自己的前世:ProcessRecord.
2)android.app.ActivityThread进程启动
Android.app.ActivityThread进程建立后,将跳入到ActivityThread的main函数开始运行,进入消息循环。
应用进程使用thread.attach()发起AMS的AttachApplicationLocked调用,并传递 ActvitiyThread对象和CallingPid。AttachApplicationLocked将根据CallingPid在mPidsSelfLocked找到对应的ProcessRecord实例app,将ActvitiyThread放置app.thread中。这样应用进程和AMS建立起来双向连接。AM可以使用AIDL接口,通过app.thread可以访问应用进程的对象。
应用程序通过ActivityThread提供的框架,建立消息循环Looper和Handler。从前面的相关章节我们知道有Looper和Handler,整个系统就可以运作了。
为了更为系统的了解应用程序的建立时序及其涉及到数据操作,我给出了应用进程的建立过程示意图:
22.Android应用框架之Activity
3 Activity设计框架
3.1 外特性空间的Activity
我们先来看看,Android应用开发人员接触的外特性空间中的Activity,对于AMS来讲,这个Activity就是客服端的Activity。应用程序员在建立Android应用时,构建Activity的子类就是Andoid外特性空间展现的接口。我们可以从下面的简单的例子描述看看Activity,到底如何建立的。
DemoActivity extend Activity
{
onCreate
onResume
onPause
onStop
}
在Android的外特性空间(SDK)中,Android应用程序员根本不知道进程是什么时候起来的,系统消息是如何传递过来的。这个DemoActivity是如何实例化的呢?并且该Activity是托管在哪个进程的呢?本节的分析将给出答案。
我们从ActivityThread中可以看到在应用进程中的Activity都被放置在mActivities中。
这些ActivityRecord记录了应用进程中,程序员建立的Activity子类的实例,我们称之为外特性空间的Activity。这些Activity类实例是放在应用程序端进行实际交互的Activity,而为了管理这些Activity,AMS内核中还有一个影子Activity,被称为HistoryRecord。
3.2 Activity与HistoryRecord的关系
在整个系统中,Activity实际上有两个实体。一个在应用进程中跟应用程序员打交道的Activity,一个是在AMS的中具有管理功能的History Record。应用进程中的Activity都登记ActivityThread实例中的mActivity数组中,而在AM端,HistroytRecord实例放置在mHistroy栈中。mHistory栈是Android管理Activity的场所,放置在栈顶的就是User看到的处于活动状态的Activity。
Activity与HistrotyRecord的关系图可以表示如下:
Activity的内核实体是依靠在ProcessRecord的成员变量中,通过ProcessRecord我们可以访问到所有的属于该Process的Activity。而在ProcessRecord记录了与应用进程之间的联系:IActivtityThread接口。通过该接口,可以访问到所对应的Activity的方法。在Launch Activity时,AMS将对应的HistoryRecord作为token传递到客服端和客服端的Activity建立联系。在AMS中Activity状态变化时,将通过该联系找到客服端的Activity,从而将消息或者动作传递应用程序面对的接口:xxxActivity。
3.3 Actvity的Launch过程
1)发起请求startActivity(intent)
2)Activity Service Manager接收到请求执行StartActivity函数。
建立:HistoryRecord实例r.
将r 加入到mHistory顶。
(3)通过app.thread.scheduleLaunchActvity( app,r)@ActivityThread.java
(4)在App应用中建立新的ActivityRecord。
(5)建立新的Activity对象并放入到ActivityRecord中。
(6)将ActivityRecord加入到mActivites@ActivityThread
(7)发起Activity.onCreate(..),,该onCreate就是在你的应用程序XXXActivity中的onCreate。
3.4 Activity的Resume
(1)Activity什么时候被Resume
(2)Rusume的过程
通过该过程的研究我们会进一步的了解到AMS与应用进程的交互过程。
在AMS端,满足resume条件都会调用:Resume的核心函数:resumeTopActivityLocked@ActivityManagerService
XXX当前栈顶的HistroyRecord
1)窗口切换:隐藏前一个Activity的窗口,
2)更新LRUList,(LRUList是淘汰应用程序的依据之一)
3) XXX.app.thread.scheduleResumeActivity(XXX,
isNextTransitionForward());
4)completeResumeLocked
setFocusedActivityLocked
mFocusActivity=xxx //此时焦点Actvitiy切换了。
WM.setFocusedApp(xxx,
mWindowManager.executeAppTransition();
mNoAnimActivities.clear();
在应用程序端:
(5)scheduleResumeActivity
handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
ActivityRecord r = performResumeActivity(token, clearHide);
ActivityRecord r = mActivities.get(token);
r.activity.performResume()
performResume
整个Resume的过程如下:
23.Andoird GDI之基本原理及其总体框架
在Android中所涉及的概念和代码最多,最繁杂的就是GDI相关的代码了。但是本质从抽象上来讲,这么多的代码和框架就干了一件事情:对显示缓冲区的操作和管理。
GDI主要管理图形图像的输出,从整体方向上来看,GDI可以被认为是一个物理屏幕使用的管理器。因为在实际的产品中,我们需要在物理屏幕上输出不同的窗口,而每个窗口认为自己独占屏幕的使用,对所有窗口输出,应用程序不会关心物理屏幕是否被别的窗口占用,而只是关心自己在本窗口的输出,至于输出是否能在屏幕上看见,则需要GDI来管理。
从最上层到最底层的数据流的分析可以看到实际上GDI在上层为GUI提供一个抽象的概念,就好像操作系统中的文件系统所提供文件,目录等抽象概念一样,GDI输出抽象成了文本,画笔,位图操作等设备无关的操作,让应用程序员只需要面对逻辑的设备上下文进行输出操作,而不要涉及到具体输出设备,以及输出边界的管理。GDI负责将文本、线条、位图等概念对象映射到具体的物理设备,所以GDI的在大体方向上可以分为以下几大要素:
Android的GDI系统
Android的GDI系统所涉及到概念太多,加之使用了OpenGL使得Android的层次和代码很繁杂。但是我们对于Android的GDI系统需要了解的方面不是他的静态的代码关系,而是动态的对象关系,在逻辑运行的架构上理解GDI。我们首先还是需要从代码结构开始我们的理解。
Frameworks/Libs/Surfaceflinger
Frameworks/base/core/jni/android_view_Surface.cpp
Frameworks/base/core/java/android/view/surface.java
Frameworks/base/Graphics:绘图接口
Frameworks/Libs/Ui
External/Skia
其中External/Skia是一个C++的2D图形引擎库,Android的2D绘制系统都是建立在该基础之上.Skia完成了:文本输出,位图,点,线,图像解码等功能。
我在这里给出Android GDI的基本框架示意图。
对于上面的GDI架构图我们只是一个大概的了解,我们有太多的问题需要解决,有太多的疑问需要得到答案,我就一直在想,为什么设计者有提出如此众多的概念,这个概念的背景是什么?他要管理什么,他要抽象什么?从前面知道,Android的整个设计理念就是无边界化,他是如何穿透Linux进程这个鸿沟来达到无边界的?Surface,Canvas, Layer,LayerBase, NativeBuffer,SurfaceFlinger,SurfaceFlingerClient这些到底是一个什么东西?如何管理,传递的是什么?创建的是什么?这些都是抽象的概念,绘画的终极的缓冲区到底是如何管理的?缓冲区到底在哪里?
我们还是看看做终极的,最本质的设计概念,在从这些概念出发,来探讨这些概念的形成过程,是否有必要去生成写概念。SurfaceFlinger本质上干什么的?SurfaceFlinger的确就是这个意义:应用程序通过SurfaceFlinger将自己的“Surface”投掷到屏幕缓冲区。至于如何投掷的,我们将会在后面详细描述。
24.Android GDI之显示缓冲管理
Android GDI之屏幕设备管理-动态链接库
万丈高楼从地起,从最根源的硬件帧缓冲区开始。我们知道显示FrameBuffer在系统中就是一段内存,GDI的工作就是把需要输出的内容放入到该段内存的某个位置。我们从基本的点(像素点)和基本的缓冲区操作开始。
1 基本知识
1.1点的格式
对于不同的LCD来讲,FrameBuffer的二进制格式不一样,并且可以分为两部分:
1)点的格式:通常将Depth,即表示多少位表示一个点。
1位表示一个点
2位表示一个点
16位表示一个点
32位表示一个点(Alpha通道)
2) 点内格式:RGB分量分布表示。
例如对于我们常见的16位表示一个点
1.2.格式之间的转换
所以屏幕输出实际上是一个值映射的关系。我们可以有如下的点格式转换,
源格式可能来自单色位图和彩色位图,对于具体的目标机来讲,我们的目标格式可能就是一种,例如16位(5/6/5)格式。其实就只存在一种格式的转换,即从目标格式都是16位格式。
但是,在设计GDI时,基本要求有一个可移植性好,所以我们还是必须考虑对于不同点格式LCD之间的转换操作。所以在GDI的驱动程序中涉及到如下几类主要操作:
区域操作(Blit):我们在显示缓冲区上做的最多的操作就是区块搬运。由此,很多的应用处理器使用了硬件图形加速器来完成区域搬运:blit.从我们的主要操作的对象来看,可以分为两个方向:
1)内存区域到屏幕区域
2)屏幕区域到屏幕区域
3)屏幕区域到内存区域
4)内存区域到内存区域
在这里我们需要特别提出的是,由于在Linux不同进程之间的内存不能自由的访问,使得我们的每个Android应用对于内存区域和屏幕缓冲区的使用变得很复杂。在Android的设计中,在屏幕缓冲区和显示内存缓冲区的管理分类很多的层次,最上层的对象是可以在进程间自由传递,但是对于缓冲区内容则使用共享内存的机制。
基于以上的基础知识,我们可以知道:
(1)代码中Config及其Format的意义所在了。也就理解了兼容性的意义:采用同硬件相同的点的描述对象
(2)所有屏幕上图形的移动都是显示缓冲区搬运的结果。
1.2图形加速器
应用处理器都可能带有图形加速器,对于不同的应用处理器对其图形加速器可能有不同的处理方式,对于2D加速来讲,都可归结为Blit。多为数据的搬运,放大缩小,旋转等。
2 Android的缓冲区抽象定义
不同的硬件有不同的硬件图形加速设备和缓冲内存实现方法。Android Gralloc动态库抽象的任务就是消除不同的设备之间的差别,在上层看来都是同样的方法和对象。在Moudle层隐藏缓冲区操作细节。Android使用了动态链接库gralloc.xxx.so,来完成底层细节的封装。
2.1 本地定义@hardware/libhandware/modules/gralloc
每个动态链接库都是用相同名称的调用接口:
1)硬件图形加速器的抽象:BlitEngine,CopyBit的加速操作。
2)硬件FrameBuffer内存管理
3)共享缓存管理
从数据关系上我们来考察..动态链接库的抽象行为:在层次:Hardware.c@hardware/libhardware 中对动态链接库中的内容作了全新的包装。/system/lib/hw/gralloc.xxx.so动态库文件。从文件Gralloc.h(handware/libhardware/include/hardware)是抽象的结果:hw_get_module从gralloc.xxx.so提取了HAL_MODULE_INFO_SYM(SYM变量)
从展露在外部的数据结构,我们在@Gralloc.cpp看到到了这样的布局:
static struct hw_module_methods_t gralloc_module_methods = {
open: gralloc_device_open
};
struct private_module_t HAL_MODULE_INFO_SYM = {
base: {
common: {
tag: HARDWARE_MODULE_TAG,
…
id: GRALLOC_HARDWARE_MODULE_ID,
name: "Graphics Memory Allocator Module",
author: "The Android Open Source Project",
methods: &gralloc_module_methods
},
registerBuffer: gralloc_register_buffer,
unregisterBuffer: gralloc_unregister_buffer,
lock: gralloc_lock,
unlock: gralloc_unlock,
},
framebuffer: 0,
flags: 0,
numBuffers: 0,
bufferMask: 0,
…
};
我们建立了什么对象来支撑缓冲区的操作?
buffer_handle_t:外部接口。
methods.open,registerBuffer,unregisterBuffer,lock,unlock
下面是外部接口和内部对象的结构关系,该类型的结构充分利用C Struct的数据排列特性:基本结构体放置在最前面,本地私有放置在后面,满足了抽象的需要。
typedef const native_handle* buffer_handle_t;
private_module_t HAL_MODULE_INFO_SYM 向往暴露的动态链接库接口,通过该接口,我们直接可以使用该对象。
看不清楚上面图,可以偏一下头横着看:
几个接口函数的解释:
(1)fb_post
对于帧缓冲区实际地址并不需要向上层报告,所有的操作都是通过fb_post了完成。
fp_post的任务就是将一个Buffer的内容传递到硬件缓冲区。其实现方式有两种:
(方式1)无需拷贝动作,是把Framebuffer的后buffer切为前buffer,然后通过IOCTRL机制告诉FB驱动切换DMA源地地址。这个实现方式的前提是Linux内核必须分配至少两个缓冲区大小的物理内存和实现切换的ioctrol,这个实现快速切换。
(方式2)利用Copy的方式。不修改内核,则在适配层利用从拷贝的方式进行,但是这个是费时了。
(2)gralloc的主要功能是要完成:
1)打开屏幕设备 "/dev/fb0",,并映射硬件显示缓冲区。
2)提供分配共享显示缓存的接口
3)提供BiltEngine接口(完成硬件加速器的包装)
(3)gralloc_alloc输出buffer_handle_t句柄。
这个句柄是共享的基本依据,其基本原理在后面的章节有详细描述。
3 总结
总结一下,/system/lib/hw/gralloc.xxx.so是跟硬件体系相关的一个动态链接库,也可以叫做Android的硬件抽象层。他实现了Android的硬件抽象接口标准,提供显示内存的分配机制和CopyBit等的加速实现。而如何具体实现这些功能,则跟硬件平台的配备有关系,所以我们看到了对于与不同的硬件架构,有不同的配置关系。
25.Android GDI之共享缓冲区机制
1 native_handle_t对private_handle_t 的包裹
private_handle_t是gralloc.so使用的本地缓冲区私有的数据结构,而Native_handle_t是上层抽象的可以在进程间传递的数据结构。在客户端是如何还原所传递的数据结构呢?首先看看native_handle_t对private_handle_t的抽象包装。
numFds= sNumFds=1;
numInts= sNumInts=8;
这个是Parcel中描述句柄的抽象模式。实际上是指的Native_handle所指向句柄对象的具体内容:
numFds=1表示有一个文件句柄:fd/
numInts= 8表示后面跟了8个INT型的数据:magic,flags,size,offset,base,lockState,writeOwner,pid;
由于在上层系统不要关心buffer_handle_t中data的具体内容。在进程间传递buffer_handle_t(native_handle_t)句柄是其实是将这个句柄内容传递到Client端。在客户端通过Binder读取readNativeHandle @Parcel.cpp新生成一个native_handle。
native_handle* Parcel::readNativeHandle() const
{
…
native_handle* h = native_handle_create(numFds, numInts);
for (int i=0 ; err==NO_ERROR && i
h->data[i] = dup(readFileDescriptor());
if (h->data[i] < 0) err = BAD_VALUE;
}
err = read(h->data + numFds, sizeof(int)*numInts);
….
return h;
}
这里需要提到的是为在构造客户端的native_handle时,对于对方传递过来的文件句柄的处理。由于不是在同一个进程中,所以需要dup(…)一下为客户端使用。这样就将Native_handle句柄中的,客户端感兴趣的从服务端复制过来。这样就将Private_native_t的数据:magic,flags,size,offset,base,lockState,writeOwner,pid;复制到了客户端。
客户端利用这个新的Native_buffer被Mapper传回到gralloc.xxx.so中,获取到native_handle关联的共享缓冲区映射地址,从而获取到了该缓冲区的控制权,达到了客服端和Server间的内存共享。从SurfaceFlinger来看就是作图区域的共享。
2 Graphic Mapper是干什么的?
服务端(SurfaceFlinger)分配了一段内存作为Surface的作图缓冲,客户端怎样在这个作图缓冲区上工作呢?这个就是Mapper(GraphicBufferMapper)y要干的事情。两个进程间如何共享内存,如何获取到共享内存?Mapper就是干这个得。需要利用到两个信息:共享缓冲区设备句柄,分配时的偏移量。Mapper利用这样的原理:
客户端只有lock,unlock,实质上就是mmap和ummap的操作。对于同样一个共享缓冲区,偏移量才是总要的,起始地址不重要。实际上他们操作了同一物理地址的内存块。我们在上面讨论了native_handle_t对private_handle_t 的包裹过程,从中知道服务端给客户端传递了什么东西。
进程1在共享内存设备上预分配了8M的内存。以后所有的分配都是在这个8M的空间进行。对以该文件设备来讲,8M物理内存提交后,就实实在在的占用了8M内存。每个每个进程都可以同个该内存设备共享该8M的内存,他们使用的工具就会mmap。由于在mmap都是用0开始获取映射地址,所以所有的客户端进程都是有了同一个物理其实地址,所以此时偏移量和size就可以标识一段内存。而这个偏移量和size是个数值,从服务进程传递到客户端直接就可以使用。
3 GraphicBuffer(缓冲区代理对象)
typedef struct android_native_buffer_t
{
struct android_native_base_t common;
int width;
int height;
int stride;
int format;
int usage;
…
buffer_handle_t handle;
…
} android_native_buffer_t;
关系图表:
GraphicBuffer :EGLNativeBase :android_native_buffer_t
GraphicBuffer(parcel &)建立本地的GraphicBuffer的数据native_buffer_t。在通过接收对方的传递的native_buffer_t 构建GraphicBuffer。我们来看看在客户端Surface::lock获取操作缓冲区的函数调用:
Surface::lock(SurfaceInfo* other, Region* dirtyIn, bool blocking)
{int Surface::dequeueBuffer(android_native_buffer_t** buffer)(Surface)
{status_t Surface::getBufferLocked(int index, int usage)
{
sp buffer = s->requestBuffer(index, usage);
{
virtual sp requestBuffer(int bufferIdx, int usage)
{ remote()->transact(REQUEST_BUFFER, data, &reply);
sp buffer = new GraphicBuffer(reply);
Surface::Lock建立一个在Client端建立了一个新的GraphicBuffer 对象,该对象通过(1)描述的原理将SurfaceFlinger的buffer_handle_t相关数据构成新的客户端buffer_handle_t数据。在客户端的Surface对象就可以使用GraphicMapper对客户端buffer_handle_t进行mmap从而获取到共享缓冲区的开始地址了。
4 总结
Android在该节使用了共享内存的方式来管理与显示相关的缓冲区,他设计成了两层,上层是缓冲区管理的代理机构GraphicBuffer,及其相关的native_buffer_t,下层是具体的缓冲区的分配管理及其缓冲区本身。上层的对象是可以在经常间通过Binder传递的,而在进程间并不是传递缓冲区本身,而是使用mmap来获取指向共同物理内存的映射地址。
26.Android GDI之SurfaceFlinger
Android GDI之SurfaceFlinger
SurfaceFinger按英文翻译过来就是Surface投递者。SufaceFlinger的构成并不是太复杂,复杂的是他的客户端建构。SufaceFlinger主要功能是:
1) 将Layers (Surfaces) 内容的刷新到屏幕上
2) 维持Layer的Zorder序列,并对Layer最终输出做出裁剪计算。
3) 响应Client要求,创建Layer与客户端的Surface建立连接
4) 接收Client要求,修改Layer属性(输出大小,Alpha等设定)
但是作为投递者的实际意义,我们首先需要知道的是如何投递,投掷物,投递路线,投递目的地。
1 SurfaceFlinger的基本组成框架
SurfaceFlinger管理对象为:
mClientsMap:管理客户端与服务端的连接。
ISurface,IsurfaceComposer:AIDL调用接口实例
mLayerMap:服务端的Surface的管理对象。
mCurrentState.layersSortedByZ :以Surface的Z-order序列排列的Layer数组。
graphicPlane 缓冲区输出管理
OpenGL ES:图形计算,图像合成等图形库。
gralloc.xxx.so这是个跟平台相关的图形缓冲区管理器。
pmem Device:提供共享内存,在这里只是在gralloc.xxx.so可见,在上层被gralloc.xxx.so抽象了。
2 SurfaceFinger Client和服务端对象关系图
Client端与SurfaceFlinger连接图:
Client对象:一般的在客户端都是通过SurfaceComposerClient来跟SurfaceFlinger打交道。
3 主要对象说明
3.1 DisplayHardware &FrameBuffer
首先SurfaceFlinger需要操作到屏幕,需要建立一个屏幕硬件缓冲区管理框架。Android在设计支持时,考虑多个屏幕的情况,引入了graphicPlane的概念。在SurfaceFlinger上有一个graphicPlane数组,每一个graphicPlane对象都对应一个DisplayHardware.在当前的Android(2.1)版本的设计中,系统支持一个graphicPlane,所以也就支持一个DisplayHardware。
SurfaceFlinger,Hardware硬件缓冲区的数据结构关系图。
3.2 Layer
method:setBuffer 在SurfaceFlinger端建立显示缓冲区。这里的缓冲区是指的HW性质的,PMEM设备文件映射的内存。
1) layer的绘制
void Layer::onDraw(const Region& clip) const
{
int index = mFrontBufferIndex;
GLuint textureName = mTextures[index].name;
…
drawWithOpenGL(clip, mTextures[index]);
}
3.2 mCurrentState.layersSortedByZ
以Surface的Z-order序列排列的LayerBase数组,该数组是层显示遮挡的依据。在每个层计算自己的可见区域时,从Z-order 顶层开始计算,是考虑到遮挡区域的裁减,自己之前层的可见区域就是自己的不可见区域。而绘制Layer时,则从Z-order底层开始绘制,这个考虑到透明层的叠加。
4 SurfaceFlinger的运行框架
我们从前面的章节
4.1 SurfaceFlinger::readyToRun()
(1)建立GraphicPanle
(2)建立FrameBufferHardware(确定输出目标)
初始化:OpenGL ES
建立兼容的mainSurface.利用eglCreateWindowSurface。
建立OpenGL ES进程上下文。
建立主Surface(OpenGL ES)。 DisplayHardware的Init()@DisplayHardware.cpp函数对OpenGL做了初始化,并创建立主Surface。为什么叫主Surface,因为所有的Layer在绘制时,都需要先绘制在这个主Surface上,最后系统才将主Surface的内容”投掷”到真正的屏幕上。
(3) 主Surface的绑定
1)在DisplayHandware初始完毕后,hw.makeCurrent()将主Surface,OpenGL ES进程上下文绑定到SurfaceFlinger的上下文中,
2)之后所有的SurfaceFlinger进程中使用EGL的所有的操作目的地都是mSurface@DisplayHardware。
这样,在OpenGL绘制图形时,主Surface被记录在进程的上下文中,所以看不到显示的主Surfce相关参数的传递。下面是Layer-Draw,Hardware.flip的动作示意图:
4.2 ThreadLoop
(1)handleTransaction(…):主要计算每个Layer有无属性修改,如果有修改着内用需要重画。
(2)handlePageFlip()
computeVisibleRegions:根据Z-Order序列计算每个Layer的可见区域和被覆盖区域。裁剪输出范围计算-
在生成裁剪区域的时候,根据Z_order依次,每个Layer在计算自己在屏幕的可显示区域时,需要经历如下步骤:
1)以自己的W,H给出自己初始的可见区域
2)减去自己上面窗口所覆盖的区域
在绘制时,Layer将根据自己的可将区域做相应的区域数据Copy。
(3)handleRepaint()
composeSurfaces(需要刷新区域):
根据每个Layer的可见区域与需要刷新区域的交集区域从Z-Order序列从底部开始绘制到主Surface上。
(4)postFramebuffer()
(DisplayHardware)hw.flip(mInvalidRegion);
eglSwapBuffers(display,mSurface) :将mSruface投递到屏幕。
5 总结
现在SurfaceFlinger干的事情利用下面的示意图表示出来:
27.Android GDI 之SurfaceFlinger之动态结构示意图
1 SurfaceSession的建立
客户端请求建立Surface时,首先在要与SurfaceFlinger建立一个Session,然后再Session上建立一个Connection通过概念返回Bclient对象。WindowManagerService在添加第一个窗口前会检查SurfaceSession是否建立,如何没有建立,将会新建立一个实例来代表与SurfaceFlinger的一个连接。
new SurfaceSession()@windowAddedLocked() @WindowManagerService.java。
SurfaceSession的建立过程大部分是在C++ Native空间中完成的,表现在SurfaceSession的初始化函数:init()本地函数上。从下面的初始化函数可以看到:
Init()<->SurfaceSession_init@android_view_Surface.cpp
new SurfaceComposerClient
SurfaceSession在C++Native空间建立一个SurfaceComposerClient实例。而该实例的建立实现了如下的与SurfaceFlinger通讯基础:
(1)建立了代理SurfaceFlinger服务的代理服务端
(2)建立了IsurfaceFlingerClient连接,在SurfaceFlinger端建立了对应的Client,并将BClient返回给WindowManagerService。
2 Surface的建立
在WindowManagerService中WindowState类中,我们知道每个主窗口子啊需要是都需要建立一个Surface与之对应。win.createSurfaceLocked()@relayoutWindow
Surface.java
Init()< -- >Surface_init(….,session,pid,dpy,w,h,format)@android_view_Surface.cpp
SurfaceControl surface(client->createSurface
在mClient的连接上:建立ISurface接口:
M_Client->greateSurface(...)@
Bclient ::createSurface(mId...)@SurfaceFlinger.cpp
mFlinger->createSurface(clientid....)
createNormalSurfaceLocked
*createNormalSurfaceLocked:建立一个Layer分配显示内存
*createPushBuffersSurfaceLocked:建立一个LayBuffer但是不分配显示内存。
28.Android GDI之Surface&Canvas
Canvas为在画布的意思。Android上层的作图几乎都通过Canvas实例来完成,其实Canvas更多是一种接口的包装。drawPaints ,drawPoints,drawRect,drawBitmap ...
1 Canvas与Surface之间本质关系
对于本节,我们不去研究Skia图形引擎本身,我们需要了解的我们的所做的图形到底放置到了那个地方,并且这个Canvas如何与Surface连接在一起的。
Canvas(Java)在C++Native层有一个Native Canvas的C++对象所对应。
lockCanvas()@java
Surface_lockCanvas@android_view_Surface.cpp
SurfaceControl->new Surface(control) @Surface.cpp
Surface: lock操作:
GraphicBuffer :lock
getBufferMapper().lock<-> GraphicBufferMapper ::lock
mAllocMod->lock<->gralloc_module_t::lock
通过SurfaceLock可取得Surface(mLockedBuffe)所对应的图形缓冲区地址。
(1) 建立与SkCanvas连接的位图设备,而该位图使用上面取得的图形缓冲区地址做自己的位图内存。
(2) 设置SkCanvas的作图目标设备为该位图。
通过该过程就建立起了SurfaceControl与Canvas之间的联系。
2 View:OnDraw的本源
不是使用OpenGL绘制时,Android在View属性发生变化,新建View时,或者Z-order发生变化时,需要对系统屏幕上的View重新绘制,此时我们的View会执行OnDraw(canvas),这个根源在哪里呢?
ViewRoot.Java
performTraversals(..)
…
draw()
canvas = surface.lockCanvas(dirty);
…
mView.draw(canvas);
draw(cavas)@view.java
background.draw(canvas);
onDraw(cavas)
dispatchDraw(cavas)
onDrawScrolbars(cavas)
surface.unlockCanvasAndPost(canvas);