深入安卓
阅读原文:http://wiki.jikexueyuan.com/project/deep-android-v1/prepare.html
安卓系统架构
Android系统大体可分为四层,从下往上依次是:
1. Linux内核层,目前Android2.2(代号为Froyo)基于Linux内核2.6版本。
2. Libraries层,这一层提供动态库(也叫共享库)、Android运行时库、Dalvik虚拟机等。从编程语言上来说,这一层大部分都是用C或C++写的,所以也可以简单地把它看成是Native层。
3. Libraries层之上是Framework层,这一层大部分用Java语言编写。它是Android平台上Java世界的基石。
4. Framework层之上就是Applications层了,和用户直接交互的就是这些应用程序,它们都是用Java开发的。
安卓的系统架构运转依赖另一个被Google极力隐藏的Native世界,关系如下图:
从上图可知:
1. Java虽具有和平台无关的特性,但Java和具体平台之间的隔离却是由JNI层来做到的。Java是通过JNI层调用Linux OS中的系统调用来完成对应的功能的。例如创建一个文件、创建一个Socket等。
2. 除了Java世界外,还有一个核心的Native世界,它为整个系统高效和平稳的运行提供了强有力的支持。一般而言,Java世界经由JNI层通过IPC方式和Native世界交互。
JNI(Java Native Interface)
JNI是一种技术,通过这种技术可以做到以下两点:
1. Java程序中的函数可以调用Native语言写的函数,Native一般指的是C/C++编写的函数。
2. Native程序中的函数可以调用Java层的函数,也就是在C/C++程序中可以调用Java的函数。
有了JNI技术,就可以对Java层屏蔽具体的虚拟机实现上的差异了。
通过MediaScanner了解JNI流程
MediaScanner是Android平台中多媒体系统的重要组成部分,它的功能是扫描媒体文件,得到诸如歌曲时长、歌曲作者等媒体信息,并将它们存入到媒体数据库中,供其他应用程序使用。
JNI库的名字可以随便取,不过Android平台基本上都采用“lib模块名_jni.so”的命名方式。
上图中,MediaScanner将通过JNI库libmedia_jni.so和Native的libmedia.so交互。
Java层的MediaScanner分析
分析
1. Java要调用Native函数,就必须通过一个位于JNI层的动态库才能做到
2. native_init和processFile函数前都有Java的关键字native,它表示这两个函数将由JNI层来实现。
只要完成下面两项工作就可以使用JNI了,它们是:
1、 加载对应的JNI库。
2、 声明由关键字native修饰的函数
JNI层的MediaScanner分析
1. 注册JNI函数
“注册”之意就是将Java层的native函数和JNI层对应的实现函数关联起来,有了这种关联,调用Java层的native函数时,就能顺利转到JNI层对应的函数执行了
关联大概方法:native_init函数位于android.media这个包中,它的全路径名应该是android.media.MediaScanner.native_init,而JNI层函数的名字是android_media_MediaScanner_native_init。因为在Native语言中,符号“.”有着特殊的意义,所以JNI层需要把“.”换成“_”。也就是通过这种方式,native_init找到了自己JNI层的本家兄弟android.media.MediaScanner.native_init。
具体的注册方法:具体的注册方法还是去看相关教程,这里仅仅是分析过程原理所以不细纠结。
2. 数据类型转换
Java数据类型分为基本数据类型和引用数据类型两种,JNI层也是区别对待这二者的。先来看基本数据类型的转换。
基本类型的对照
应务必注意,转换成Native类型后对应数据类型的字长。
引用类型的对照
了Java中基本数据类型的数组、Class、String和Throwable外,其余所有Java对象的数据类型在JNI中都用jobject表示。
对比下java和jni的函数:
//Java层processFile有三个参数。
processFile(Stringpath,String mimeType,MediaScannerClient client);
//JNI层对应的函数,最后三个参数和processFile的参数对应。
android_media_MediaScanner_processFile(JNIEnv*env,jobject thiz,
jstring path,jstring mimeType, jobjectclient)
参数对应:
第一个参数接下来具体介绍,因为很重要。
第二个参数jobject代表Java层的MediaScanner对象。
其余参数可以一一对应。
3. JNIEnv介绍
JNIEnv是一个和线程相关的,代表JNI环境的结构体。
JNIEnv实际上就是提供了一些JNI系统函数。通过这些函数可以做到:
1. 调用Java的函数。
2. 操作jobject对象等很多事情。
线程相关:也就是说,线程A有一个JNIEnv,线程B有一个JNIEnv。由于线程相关,所以不能在线程B中使用线程A的JNIEnv结构体。
为了解决当后台线程收到一个网络消息,而又需要由Native层函数主动回调Java层函数时由于JNIEnv是线程相关造成的不便,我们可以用JavaVM。
JavaVM,它是虚拟机在JNI层的代表,不论进程中有多少个线程,JavaVM却是独此一份,所以在任何地方都可以使用它。
4. 通过JNIEnv操作jobject
操作jobject的本质就应当是操作这些对象的成员变量和成员函数。
获取变量和方法的ID
成员变量和成员函数是由类定义的,它是类的属性,所以在JNI规则中,用jfieldID 和jmethodID 来表示Java类的成员变量和成员函数,它们通过JNIEnv的下面两个函数可以得到:
jfieldID GetFieldID(jclass clazz,const char*name, constchar *sig);
jmethodID GetMethodID(jclass clazz, const char*name,constchar *sig);
jclass代表Java类,name表示成员函数或成员变量的名字,sig为这个函数和变量的签名信息。
MyMediaScannerClient(JNIEnv *env,jobjectclient)......
{
//先找到android.media.MediaScannerClient类在JNI层中对应的jclass实例。
jclass mediaScannerClientInterface =
env->FindClass("android/media/MediaScannerClient");
//取出MediaScannerClient类中函数scanFile的jMethodID。
mScanFileMethodID = env->GetMethodID(
mediaScannerClientInterface,"scanFile",
"(Ljava/lang/String;JJ)V");
//取出MediaScannerClient类中函数handleStringTag的jMethodID。
mHandleStringTagMethodID = env->GetMethodID(
mediaScannerClientInterface,"handleStringTag",
"(Ljava/lang/String;Ljava/lang/String;)V");
......
}
使用id(我也没具体用过等后面找教程再看看)
[-->android_media_MediaScanner.cpp::MyMediaScannerClient的scanFile]
virtualbool scanFile(const char*path, long long lastModified,
long long fileSize)
{
jstringpathStr;
if((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;
/*
调用JNIEnv的CallVoidMethod函数,注意CallVoidMethod的参数:
第一个是代表MediaScannerClient的jobject对象,
第二个参数是函数scanFile的jmethodID,
后面是Java中scanFile的参数。
*/
mEnv->CallVoidMethod(mClient,mScanFileMethodID,pathStr,
lastModified, fileSize);
mEnv->DeleteLocalRef(pathStr);
return(!mEnv->ExceptionCheck());
}
JNI签名介绍
它是Java中对应函数的签名信息,由参数类型和返回值类型共同组成。
因为Java支持函数重载,也就是说,可以定义同名但不同参数的函数。但仅仅根据函数名,是没法找到具体函数的。为了解决这个问题,JNI技术中就使用了参数类型和返回值类型的组合,作为一个函数的签名信息,有了签名信息和函数名,就能很顺利地找到Java中的函数了。
具体的签名格式:(参数1类型标示参数2类型标示...参数n类型标示)返回值类型标示。
Eg:
Java中函数定义为voidprocessFile(String path, String mimeType)
对应的JNI函数签名就是
(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V
括号内是参数类型的标示,最右边是返回值类型的标示,void类型对应的标示是V。
参数的类型是引用类型时,其格式是”L包名;”,其中包名中的”.”换成”/”。
类型标识:
函数签名:
JNI的垃圾回收
JNI中的数据引用类型:
1. Local Reference:本地引用。在JNI层函数中使用的非全局引用对象都是Local Reference。它包括函数调用时传入的jobject、在JNI层函数中创建的jobject。LocalReference最大的特点就是,一旦JNI层函数返回,这些jobject就可能被垃圾回收。
2. Global Reference:全局引用,这种对象如不主动释放,就永远不会被垃圾回收。
3. Weak Global Reference:弱全局引用,一种特殊的GlobalReference,在运行过程中可能会被垃圾回收。所以在程序中使用它之前,需要调用JNIEnv的IsSameObject判断它是不是被回收了。
Init(用户空间的第一个进程)
Android是基于Linux内核的,所以init也是Android系统中用户空间的第一个进程,它的进程号是1。
他的作用:
1. init进程负责创建系统中的几个关键进程,尤其是Zygote。
2. Android系统有很多属性,于是init就提供了一个property service(属性服务)来管理它们。
Init分析
粗略步骤
1. 解析两个配置文件,init.rc文件和机器相关的配置文件。
2. 执行各个阶段的动作,创建Zygote的工作就是在其中的某个阶段完成的。
3. 调用property_init初始化属性相关的资源,并且通过property_start_service启动属性服务。
4. init进入一个无限循环,并且等待一些事情的发生。重点关注init如何处理来自socket和来自属性服务器相关的事情。
具体步骤
第一步:
init中会解析两个配置文件,其中一个是系统配置文件init.rc,另外一个是和硬件平台相关的配置文件。
第二步:
1. service_list链表将解析后的service全部链接到了一起,并且是一个双向链表,前向节点用prev表示,后向节点用next表示。
2. socketinfo也是一个双向链表,因为zygote只有一个socket,所以画了一个虚框socket做为链表的示范。
3. onrestart通过commands指向一个commands链表,zygote有三个commands。
执行队列里的命令,在执行do_class_start命令中,我们启动zygote这个service。service一般运行于另外一个进程中,这个进程也是init的子进程,所以启动service前需要判断对应的可执行文件是否存在,zygote对应的可执行文件是/system/bin/app_process。在执行/system/bin/app_process时就进入到app_process的main函数中了。最终,zygote是通过fork和execv共同创建。
当zygote死后它的父进程init会获得一个信号量,并找到死掉的service进行重启。Zygote在重启的时候会杀掉zygote创建的所有子进程,然后Java世界崩溃。
第三步:
Windows平台上有一个叫注册表的东西。注册表可以存储一些类似key/value的键值对。系统或某些应用程序会把自己的一些属性存储在注册表中,即使下次系统重启或应用程序重启,它还能够根据之前在注册表中设置的属性,进行相应的初始化工作。Android平台也提供了一个类型机制,可称之为属性服务(property service)。应用程序可通过这个属性机制,查询或设置属性。
初始化工作内存并创建服务:
在这一步,init为属性在共享内存上开辟了一块内存。为了让系统的其他进程也能读到相关属性。
同时在属性服务中创建了一个socket来接收相关请求。
处理设置属性请求:
通过TCP链接和客户端进程建立请求通信,当用户权限足够时就接收用户的相关请求。有属性就给用户,没属性就新建。
Zygote和System_service
Zygote本身是一个Native的应用程序,和驱动、内核等均无关系。Zygote是由init进程根据init.rc文件中的配置项而创建的。
zygote最初的名字叫“app_process”,这个名字是在Android.mk文件中被指定的,但app_process在运行过程中,通过Linux下的pctrl系统调用将自己的名字换成了“zygote”。
Zygote分析
在ZygoteInit的main中,创建了AppRuntime 对象,并由其初启动Zygote服务。
AppRuntime
AppRuntime类的声明和实现均在App_main.cpp中,它是从AndroidRuntime类派生出来的。
在void AndroidRuntime::start(constchar*className, const bool startSystemServer)中,
1、创建虚拟机。2、注册Jni函数,目标是获取java的main函数。3、通过JNI调用java函数中的main,从而让Zygote便进入了Java世界
创建虚拟机——startVm
调用JNI的虚拟机创建函数,虚拟机创建时的一些参数却是在startVm中被确定的。这个函数绝大部分代码都是设置虚拟机的参数。
例如:
设置虚拟机heapsize,默认为16MB。绝大多数厂商都会修改这个值,一般是32MB。
heapsize不能设置过小,否则在操作大尺寸的图片时无法分配所需内存。
注册JNI函数——startReg
需要给这个虚拟机注册一些JNI函数。正是因为后续Java世界用到的一些函数是采用native方式来实现的,所以才必须提前注册这些函数。
让Zygote进入java的世界
在com.android.internal.os.ZygoteInit的main函数中
1、 注册Zygote用的socket
2、 预加载类和资源(由于预加载类有点多,而每个类需要1.25秒的加载时间,这导致了安卓较慢的原因之一)
3、 启动system_server进程,该进程是framework的核心,如果它死了,就会导致zygote自杀。system_server是Zygote在判断pid==0即无进程的情况下fork出来的。
4、runSelectLoopMode。在registerZygoteSocket中注册了一个用于IPC的Socket,该socket在这里得到使用,处理客户连接和客户请求。
Zygote总结
Zygote是创建Android系统中Java世界的盘古,它创建了第一个Java虚拟机,同时它又是女娲,它成功地繁殖了framework的核心system_server进程。
回顾一下Zygote创建Java世界的步骤:
第一天:创建AppRuntime对象,并调用它的start。此后的活动则由AppRuntime来控制。
第二天:调用startVm创建Java虚拟机,然后调用startReg来注册JNI函数。
第三天:通过JNI调用com.android.internal.os.ZygoteInit类的main函数,从此进入了Java世界。然而在这个世界刚开创的时候,什么东西都没有。
第四天:调用registerZygoteSocket。通过这个函数,它可以响应子孙后代的请求。同时Zygote调用preloadClasses和preloadResources,为Java世界添砖加瓦。
第五天:Zygote觉得自己工作压力太大,便通过调用startSystemServer分裂一个子进程system_server来为Java世界服务。
第六天:Zygote完成了Java世界的初创工作,它已经很满足了。下一步该做的就是调用runSelectLoopMode后,便沉沉地睡去了。
以后的日子:Zygote随时守护在我们的周围,当接收到子孙后代的请求时,它会随时醒来,为它们工作,创建进程。
SystemServer分析
SS做为Zygote的嫡长子,其重要性不言而喻。
SS创建后的过程;
1、调用zygoteInitNative进行Native层的初始化
SS在创建被Zygote fork出来之后也拥有者AppRuntime对象,同时它必须关闭Zygote的binder的进程通信,同时通过调用zygoteInitNative去开启自己的binder
2、为了通过invokeStaticMain去调用com.android.server.SystemServer类的main函数
采用了在invokeStaticMain中抛异常的方式去调用。因为调用是在ZygoteInit.main中,相当于Native的main函数,即入口函数,位于堆栈的顶层,为了不浪费调用栈,所以采用抛异常的方式。
SS的真面目:
ZygoteInit分裂产生的SS,其实就是为了调用com.android.server.SystemServer的main函数。
在main中首先调用了init1,创建了一些系统服务,然后把调用线程加入Binder通信中。不过其间还通过JNI调用了com.android.server.SystemServer类的init2函数。Init2中启动了一个ServerThread线程,用以启动Java世界的核心Service。
SystemServer的总结:
Zygote孵化进程过程
1、 ActivityManagerService发送请求
ActivityManagerService也是由SystemServer创建的。假设通过startActivit来启动一个新的Activity,而这个Activity附属于一个还未启动的进程。ActivityManagerService通过socket向Zygote发送请求了。在请求中封装各请求参数,其中有一个字符串,它的值是“android.app.ActivityThread”。
2、Zygote相应请求
每当有请求数据发来时,Zygote都会调用ZygoteConnection的runOnce函数。在其中,Zygote会fork出一个进程,并传入设置的属性同时调用AppRuntime的onZygoteInit,在那个函数中建立了和Binder的关系。最后将在handleParentProc中做一些扫尾工作,然后继续等待请求进行下一次分裂。
在进程中通过activityThread.Main开启我们的应用
具体UML的时序图:
守护service的watch dog
Android对SystemServer对参数是否被设置也很谨慎,专门为它增加了一条看门狗,一旦发现Service出了问题,就会杀掉system_server,这样就使zygote随其一起自杀,最后导致重启Java世界。
它隔一段时间给另外一个线程发送一条MONITOR消息,那个线程将检查各个Service的健康情况。而看门狗会等待检查结果,如果第二次还没有返回结果,那么它会杀掉SS。
一共有三个Service是需要交给Watchdog检查的:
· ActivityManagerService
· PowerManagerService
· WindowManagerService
要想支持看门狗的检查,就需要这些Service实现monitor接口,然后Watchdog就会调用它们的monitor函数进行检查了。检查的地方是在HeartbeatHandler类的handleMessage中。