您的位置:首页 > 编程语言 > Go语言

深入安卓JNI,INIT,ZYGOTE——极客学院学习笔记

2017-01-12 08:59 344 查看
深入安卓

阅读原文: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中。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android