这是一个连载的博文系列,我将持续为大家提供尽可能透彻的Android源码分析 github连载地址
前言
前文讲到虚拟机创建后反射调用了ZygoteInit的main方法,说到虚拟机,我们就不得不说下JNI,它是沟通Java和C++的桥梁。
JNI全称是Java Native Interface,可以把它理解为一种接口编程方式,就像我们平常开发的C/S模式一样,
Client和Server要通信,那就得用接口。JNI主要包括两个方面的内容:
- C++调用Java
- Java调用C++
本文涉及到的文件
platform/libnativehelper/include/nativehelper/jni.h
platform/art/runtime/java_vm_ext.cc
platform/art/runtime/jni_internal.cc
platform/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
platform/libcore/dalvik/src/main/java/dalvik/system/ZygoteHooks
platform/art/runtime/native/dalvik_system_ZygoteHooks.cc
platform/art/runtime/runtime.h
platform/libnativehelper/JNIHelp.cpp
platform/libcore/luni/src/main/java/android/system/Os.java
platform/libcore/luni/src/main/java/libcore/io/Libcore.java
platform/libcore/luni/src/main/java/libcore/io/BlockGuardOs.java
platform/libcore/luni/src/main/java/libcore/io/ForwardingOs.java
platform/libcore/luni/src/main/java/libcore/io/Linux.java
platform/libcore/luni/src/main/native/libcore_io_Linux.cpp
一、C++调用Java
为什么我先讲C++调用Java呢?因为前文创建了虚拟机后,首先是从C++调用了Java,所以我接着前文的例子来讲,
我们回顾一下之前C++调用ZygoteInit的main函数的过程,我将分段一步步为大家解释。
void AndroidRuntime::start(const char* className, const Vector& options, bool zygote)
{
/*
* We want to call main() with a String array with arguments in it.
* At present we have two arguments, the class name and an option string.
* Create an array to hold them.
*/
jclass stringClass;
jobjectArray strArray;
jstring classNameStr;
stringClass = env->FindClass("java/lang/String");
assert(stringClass != NULL);
strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
assert(strArray != NULL);
classNameStr = env->NewStringUTF(className);
assert(classNameStr != NULL);
env->SetObjectArrayElement(strArray, 0, classNameStr);
for (size_t i = 0; i < options.size(); ++i) {
jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
assert(optionsStr != NULL);
env->SetObjectArrayElement(strArray, i + 1, optionsStr);
}
/*
* Start VM. This thread becomes the main thread of the VM, and will
* not return until the VM exits.
*/
char* slashClassName = toSlashClassName(className);//将字符中的.转换为/
jclass startClass = env->FindClass(slashClassName);//找到class
if (startClass == NULL) {
ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
/* keep going */
} else {
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL) {
ALOGE("JavaVM unable to find main() in '%s'\n", className);
/* keep going */
} else {
env->CallStaticVoidMethod(startClass, startMeth, strArray);//调用main函数
#if 0
if (env->ExceptionCheck())
threadExitUncaughtException(env);
#endif
}
}
free(slashClassName);
...
}
1.1 Java中各类型在C++的对应关系
比如说我们Java中有常见的Class,String,int,short等,这些在C++中并不是叫原来的名字,而是另外取了个名字,
基本就是在原来的名字前加了个j,表示java. 下面是他们的对应关系
基本数据类型和void
Java类型 | C++类型 |
---|---|
boolean | jboolean |
byte | jbyte |
char | jchar |
short | jshort |
int | jint |
long | jlong |
float | jfloat |
double | jdouble |
void | void |
引用数据类型
Java类型 | C++类型 |
---|---|
All objects | jobject |
java.lang.Class实例 | jclass |
java.lang.String实例 | jstring |
java.lang.Throwable实例 | jthrowable |
Object[](包含Class,String,Throwable) | jobjectArray |
boolean[] | jbooleanArray |
byte[](其他基本数据类型类似) | jbyteArray |
那其实下面的代码就好理解了,就相当于定义了三个局部变量,类型为Class,String[],String
jclass stringClass;
jobjectArray strArray;
jstring classNameStr;
1.2 env->FindClass
我们再接着往下看,env->FindClass, env是虚拟机的环境,可以类比为Android中无处不在的Context,
但是这个env是指特定线程的环境,也就是说一个线程对应一个env.
env有许多的函数,FindClass只是其中一个,作用就是根据ClassName找到对应的class,
用法是不是跟Java中反射获取Class有点像,其实Java反射也是native方法,也得走到C++层,在实现上也是跟env->FindClass一样.
我们来具体看看env->FindClass的实现,env的类型是JNIEnv,定义在platform/libnativehelper/include/nativehelper/jni.h中,
这个JNIEnv 在C环境和C++环境类型不一样,在C环境中定义的是JNINativeInterface* ,
而C++中定义的是_JNIEnv,_JNIEnv其实内部也是调用JNINativeInterface的对应函数,只是做了层代理,
JNINativeInterface是个结构体,里面就有我们要找的函数FindClass
#if defined(__cplusplus) //如果是C++
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else //如果是C
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
struct _JNIEnv {
const struct JNINativeInterface* functions;
...
jclass FindClass(const char* name)
{ return functions->FindClass(this, name); }
...
}
struct JNINativeInterface {
...
jclass (*FindClass)(JNIEnv*, const char*);
...
}
那这个结构体JNINativeInterface中FindClass的函数指针什么时候赋值的呢?还记得上文中有个创建虚拟机的函数JNI_CreateJavaVM,
里面有个参数就是JNIEnv,其实也就是在创建虚拟机的时候把函数指针赋值的,我们知道JNI_CreateJavaVM是加载libart.so时获取的,
那我们就得找libart.so的源码,这个对应的源码在platform/art/runtime/java_vm_ext.cc,它会调用Runtime::Create函数去新建线程,
在线程新建的过程中会对JNIEnv进行赋值,JNI_CreateJavaVM函数最后会去调用线程的GetJniEnv得到JNIEnv的实例,将实例赋值给p_env.
(线程在新建过程中如何对JNIEnv进行赋值的,就不细讲了,我提供几个关键的函数,runtime.cc的Create和Init、thread.cc的Attach和Init、
jni_env_ext.cc的Create、jni_internal.cc的GetJniNativeInterface,涉及到的文件我都放在AOSP项目中,有兴趣的可以去看看. )
extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
...
if (!Runtime::Create(options, ignore_unrecognized)) {
return JNI_ERR;
}
*p_env = Thread::Current()->GetJniEnv();
}
GetJniEnv返回的是一个JNINativeInterface的实例,定义在/platform/art/runtime/jni_internal.cc,其中就有我们要找的FindClass
const JNINativeInterface gJniNativeInterface = {
nullptr, // reserved0.
nullptr, // reserved1.
nullptr, // reserved2.
nullptr, // reserved3.
JNI::GetVersion,
JNI::DefineClass,
JNI::FindClass,
}
我们看到实例中FindClass对应的函数是JNI::FindClass,定义在当前文件中,FindClass的工作是交给ClassLinker,
ClassLinker内部的实现是通过ClassLoader获取一个ClassTable对象,再通过ClassTable中的一个HashSet得到对应的Class,
ClassLoader其实我们也比较熟悉,Java层中就有,我们apk中的dex文件就是需要ClassLoader去加载,最终会将Class装进一个HashSet中,
因此,我们FindClass也去这个HashSet中去找.
(ClassLinker内部的实现我就不细讲了,我提供几个关键的函数,class_linker.cc的FindClass和LookupClass、class_table.cc的Lookup
,涉及到的文件我都放在AOSP项目中,有兴趣同学可以去具体看看.)
static jclass FindClass(JNIEnv* env, const char* name) {
CHECK_NON_NULL_ARGUMENT(name);
Runtime* runtime = Runtime::Current();
ClassLinker* class_linker = runtime->GetClassLinker(); //获取ClassLinker
std::string descriptor(NormalizeJniClassDescriptor(name));
ScopedObjectAccess soa(env);
mirror::Class* c = nullptr;
if (runtime->IsStarted()) {
StackHandleScope<1> hs(soa.Self());
Handle class_loader(hs.NewHandle(GetClassLoader(soa)));
c = class_linker->FindClass(soa.Self(), descriptor.c_str(), class_loader); //查找类
} else {
c = class_linker->FindSystemClass(soa.Self(), descriptor.c_str()); //查找系统类
}
return soa.AddLocalReference(c);
}
说完env->FindClass,其实其他env->方式调用的函数也就大体知道源码在哪儿了,在接下来的分析中我就只说明下对应函数的作用,具体实现可以根据
自己的需要深入去看.
1.3 其他env函数
env函数特别多,我这里只列举一些我们常用的
新建实例,相当于Java中的new
函数名 | 作用 | 类比Java |
---|---|---|
NewObject | 新建Object | new Object |
NewStringUTF | 新建String字符 | new String() |
NewObjectArray | 新建Object数组 | new Object[] |
New(Type)Array | 新建Type数组,如NewByteArray | new byte[] |
获取和设置成员变量和类变量,相当于Java中的获取和设置变量,下面以A a=new A()为例子
函数名 | 作用 | 类比Java |
---|---|---|
GetFieldID | 获取成员变量id,所有获取成员变量的方法都要传入这个值 | -- |
GetObjectField | 获取Object类型的成员变量 | a.object |
Get(Type)Field | 获取Type类型的成员变量,如GetBooleanField | bool b=a.bool |
Set(Type)Field | 设置Type类型的成员变量,如SetBooleanField | a.bool=b |
GetStaticFieldID | 获取类变量id,所有获取类变量的方法都要传入这个值 | -- |
GetStaticObjectField | 获取Object类型的类变量 | A.object |
GetStatic(Type)Field | 获取Type类型的类变量,如GetStaticBooleanField | bool b=A.bool |
SetStatic(Type)Field | 设置Type类型的类变量,如SetStaticBooleanField | A.bool=b |
调用成员方法和类方法,相当于Java中的调用方法,下面以A a=new A()为例子
函数名 | 作用 | 类比Java |
---|---|---|
GetMethodID | 获取成员方法id,所有获取成员方法的方法都要传入这个值 | -- |
CallObjectMethod | 调用返回值为Object类型的成员方法 | Object o=a.a() |
Call(Type)Method | 调用返回值为Type类型的成员方法,如CallBooleanMethod | bool b=a.b() |
GetStaticMethodID | 获取类方法id,所有获取类方法的方法都要传入这个值 | -- |
CallStaticObjectMethod | 调用返回值为Object类型的类方法 | Object o=A.a() |
CallStatic(Type)Method | 调用返回值为Type类型的类方法,如CallStaticBooleanMethod | bool b=A.b() |
数组相关操作,以bool[] bs=new bool[] 为例
函数名 | 作用 | 类比Java |
---|---|---|
Get(Type)ArrayElements | 获取Type类型的数组的某个元素 | bool b=bs[0] |
Set(Type)ArrayElements | 设置Type类型的数组的某个元素 | bs[0]=b |
内存释放相关,这个是C++独有的,没有Java相应的调用
函数名 | 作用 | 类比Java |
---|---|---|
ReleaseStringUTFChars | 释放String | -- |
Release(Typge)ArrayElements | 释放Type类型的数组 | -- |
我这里只是笼统地列举了一些env函数的作用,对于参数及返回值并没有细讲,主要是这些属于API范畴的东西,要用的时候再查也不迟
1.4 函数签名
start函数最后会调用main函数,在获取main函数时需要传递三个参数,第一个是函数所在的类,第二个是函数名称,第三个就是函数签名
jmethodID startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V");
函数签名其实就是对一个函数的参数及返回值的一种符号表示,表示形式是 (params)return 下面我列举一下符号与Java类型的一一对应关系:
基本数据类型和void,我们可以看到除了boolean和long表示得不一样外,其他都是以首字母进行表示,我想主要原因可能是B与byte冲突了,L与object冲突
符号 | Java类型 |
---|---|
B | byte |
C | char |
S | short |
I | int |
F | float |
D | double |
Z | boolean |
J | long |
V | void |
引用数据类型和数组,引用数据类型以L开头,后面接完整路径,最后有个分号,这个分号一定不要忘记!一定不要忘记!一定不要忘记!数组用 [ 表示
符号 | Java类型 |
---|---|
Ljava/lang/String; | String |
[I | int[] |
[Ljava/lang/object; | object[] |
我们回到刚才的例子 ([Ljava/lang/String;)V ,这个就表示main函数的参数是String[],返回值是void.
1.5 异常处理
我们在Java中经常用try catch来处理异常非常方便,我们在C++中调用Java函数时,也可以去捕获异常,我们可以有两种方式:
- ExceptionCheck
- ExceptionOccurred
我先讲讲 ExceptionCheck ,这个函数是会返回一个bool值,true表示有异常,false表示没有异常
env->CallStaticVoidMethod(cls,mid);
if (env->ExceptionCheck()) { // 检查JNI调用是否有引发异常
env->ExceptionDescribe(); //打印错误日志堆栈信息
env->ExceptionClear(); // 清除引发的异常
env->ThrowNew(env->FindClass(env,"java/lang/Exception"),"JNI抛出的异常!"); //抛出异常
}
再看看ExceptionOccurred,这个用法其实跟ExceptionCheck差不多,只是它返回的不是bool值,而是当前异常的引用
jthrowable exc = NULL;
exc = env->ExceptionOccurred(); // 返回一个指向当前异常对象的引用
if (exc) {
env->ExceptionDescribe(); //打印错误日志堆栈信息
env->ExceptionClear(); // 清除引发的异常
env->ThrowNew(env->FindClass(env,"java/lang/Exception"),"JNI抛出的异常!"); //抛出异常
}
start函数最后就用到了ExceptionCheck,因为调用Java的方法是可能引发异常的
二、Java调用C++
讲完了C++调用Java,我们再看看Java如何调用C++,我们接着前面的讲,之前通过 env->CallStaticVoidMethod(startClass, startMeth, strArray)
调用了ZygoteInit的 main 函数,我们就以main函数为例讲解Java调用C++的过程。
2.1 main函数
main函数开头有两个方法调用 startZygoteNoThreadCreation和setpgid,这两个其实都是native方法,接下来我就以这两个为例子。
public static void main(String argv[]) {
...
ZygoteHooks.startZygoteNoThreadCreation(); //设置标记,不允许新建线程
try {
Os.setpgid(0, 0); //设置zygote进程组id为zygote的pid
} catch (ErrnoException ex) {
throw new RuntimeException("Failed to setpgid(0,0)", ex);
}
...
}
startZygoteNoThreadCreation 定义在platform/libcore/dalvik/src/main/java/dalvik/system/ZygoteHooks中
/*
* Called by the zygote when starting up. It marks the point when any thread
* start should be an error, as only internal daemon threads are allowed there.
*/
public static native void startZygoteNoThreadCreation();
2.2 native注册
startZygoteNoThreadCreation 是一个native方法,我们知道native方法有两种注册方式,一种是静态注册,一种动态注册。
所谓静态注册就是根据函数名称和一些关键字就可以注册,
比如 startZygoteNoThreadCreation 要静态注册的话,它对应的实现函数应该是
JNIEXPORT void JNICALL Java_dalvik_system_ZygoteHooks_startZygoteNoThreadCreation(JNIEnv *, jobject){
}
也就是说首先得有JNIEXPORT,JNICALL这些关键字,其次函数名称必须以Java开头,后面接的是native函数所在类的完整路径加native函数名,
最后参数及返回值要相同,参数会多出两个:
- JNIEnv,表示JNI上下文,
- 一个是jobject,如果是static方法表示调用native函数的Class. 如果是普通方法表示调用native函数的对象
只要你按照这个规则写,Java的native函数就会自动调用这个C++层的函数。这种静态的注册方式有个不好的地方就是函数名太长,书写不方便,而且在首次调用时会有一个注册过程,
影响效率,那有没有其他方式呢?答案就是动态注册
其实大多数frameworks层的native函数都是用动态方式注册的,startZygoteNoThreadCreation函数也是
我们怎么寻找startZygoteNoThreadCreation的实现呢?这里有个规律,Google工程师喜欢以native所在类的完整路径为C++的实现类名,比如
startZygoteNoThreadCreation所在类的完整路径是dalvik.system.ZygoteHooks,我们尝试搜索dalvik_system_ZygoteHooks,
就会出现dalvik_system_ZygoteHooks.h和dalvik_system_ZygoteHooks.cc,我们看下dalvik_system_ZygoteHooks.cc
static JNINativeMethod gMethods[] = {
NATIVE_METHOD(ZygoteHooks, nativePreFork, "()J"),
NATIVE_METHOD(ZygoteHooks, nativePostForkChild, "(JIZLjava/lang/String;)V"),
NATIVE_METHOD(ZygoteHooks, startZygoteNoThreadCreation, "()V"),
NATIVE_METHOD(ZygoteHooks, stopZygoteNoThreadCreation, "()V"),
};
void register_dalvik_system_ZygoteHooks(JNIEnv* env) {
REGISTER_NATIVE_METHODS("dalvik/system/ZygoteHooks");
}
本来动态注册是个很简单的过程,直接调用 env->RegisterNatives ,将绑定信息作为参数即可,但是这个源码里写得比较复杂,我一步步讲吧
首先Java的native方法要调用到C++函数,肯定得有个键值对作为绑定信息,也就是告诉虚拟机哪个native该执行哪个C++函数,gMethods就是这样一个角色
gMethods数组的类型是JNINativeMethod,我们回顾下 JNINativeMethod ,它是一个结构体,name表示native函数名,signature表示用字符串描述native函数的参数和返回值,
fnPtr表示native指向的C++函数指针,这其实就是动态注册的映射关系了,将native函数对应一个C++函数
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
但是gMethods数组中却是NATIVE_METHOD,我们看看这个NATIVE_METHOD是什么
#define NATIVE_METHOD(className, functionName, signature) \
{ #functionName, signature, (void*)(className ## _ ## functionName) }
如何理解这个定义呢?#define是宏定义,也就是说编译期间要做宏替换,这里就是把NATIVE_METHOD替换成
{"","",(void)()},具体怎么替换呢?我们看到{}里有些#、##,#表示字符串化,相当于Java中的toString,##表示字符串化拼接,相当于Java中的
String.format,以NATIVE_METHOD(ZygoteHooks, startZygoteNoThreadCreation, "()V")为例,替换后就是
{"startZygoteNoThreadCreation","()V",(void)(ZygoteHooks_startZygoteNoThreadCreation) }
JNINativeMethod只是个结构体,真正注册的函数是在 REGISTER_NATIVE_METHODS("dalvik/system/ZygoteHooks"),我们先看看
REGISTER_NATIVE_METHODS
#define REGISTER_NATIVE_METHODS(jni_class_name) \
RegisterNativeMethods(env, jni_class_name, gMethods, arraysize(gMethods))
它也是一个宏定义,指向的是RegisterNativeMethods,这个函数定义在platform/frameworks/base/core/jni/AndroidRuntime.cpp
/*
* Register native methods using JNI.
*/
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
const char* className, const JNINativeMethod* gMethods, int numMethods)
{
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
其实它调用的是jniRegisterNativeMethods,这个定义在platform/libnativehelper/JNIHelp.cpp,
jniRegisterNativeMethods函数首先是将传过来的类名字符串找到对应的class,然后就是调用(env)->RegisterNatives动态注册JNI,
其实调用这么多层,动态注册最关键的就是构建一个结构体JNINativeMethod,然后调用(env)->RegisterNatives,RegisterNatives属于
虚拟机内的函数了,今后讲虚拟机时我再具体去分析,这里我们知道它的作用就行了.
extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
JNIEnv* e = reinterpret_cast(env);
ALOGV("Registering %s's %d native methods...", className, numMethods);
scoped_local_ref c(env, findClass(env, className)); //根据类名找到class
if (c.get() == NULL) {
char* tmp;
const char* msg;
if (asprintf(&tmp,
"Native registration unable to find class '%s'; aborting...",
className) == -1) {
// Allocation failed, print default warning.
msg = "Native registration unable to find class; aborting...";
} else {
msg = tmp;
}
e->FatalError(msg);
}
if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) { //动态注册jni
char* tmp;
const char* msg;
if (asprintf(&tmp, "RegisterNatives failed for '%s'; aborting...", className) == -1) {
// Allocation failed, print default warning.
msg = "RegisterNatives failed; aborting...";
} else {
msg = tmp;
}
e->FatalError(msg);
}
return 0;
}
我们接着上面的startZygoteNoThreadCreation函数讲,由上可知这个native函数实际会调用ZygoteHooks_startZygoteNoThreadCreation,
它定义在platform/art/runtime/native/dalvik_system_ZygoteHooks.cc
static void ZygoteHooks_startZygoteNoThreadCreation(JNIEnv* env ATTRIBUTE_UNUSED,
jclass klass ATTRIBUTE_UNUSED) {
Runtime::Current()->SetZygoteNoThreadSection(true);
}
其实它又是调用Runtime的SetZygoteNoThreadSection函数,这个定义在platform/art/runtime/runtime.h,这个函数的实现非常简单,
就是将zygote_no_threads_这个bool值设置为想要的值
static Runtime* instance_;
// Whether zygote code is in a section that should not start threads.
bool zygote_no_threads_;
static Runtime* Current() {
return instance_;
}
void SetZygoteNoThreadSection(bool val) {
zygote_no_threads_ = val;
}
由此我们可以看到startZygoteNoThreadCreation这个native函数经过层层调用,最终就是将一个bool变量设置为true. 讲得是有点多了,
这里主要是告诉大家如何去追踪native函数的实现,因为这是阅读frameworks层代码必备的技能. 这里我还是再次推荐大家用Source Insight
来看代码,不管是函数跳转还是全局搜索都是非常方便的,详情请看我之前写的如何阅读Android源码
4.1.2 setpgid
定义在platform/libcore/luni/src/main/java/android/system/Os.java
这个Os.java类是比较特殊的一个类,这个类相当于一个代理类,所有的方法都是去调用Libcore.os类中相关的方法,
/**
* See setpgid(2).
*/
/** @hide */ public static void setpgid(int pid, int pgid) throws ErrnoException { Libcore.os.setpgid(pid, pgid); }
而Libcore.os的实现类是BlockGuardOs,BlockGuardOs的父类是ForwardingOs,ForwardingOs也是个代理类,里面所有方法都是调用
Linux.java中的对应函数,也就是说Os.java中的函数最终调用的是Linux.java中的函数. 另外在BlockGuardOs类中有重载一些方法,做了一些
Policy权限的检查.
public final class Libcore {
private Libcore() { }
/**
* Direct access to syscalls. Code should strongly prefer using {@link #os}
* unless it has a strong reason to bypass the helpful checks/guards that it
* provides.
*/
public static Os rawOs = new Linux();
/**
* Access to syscalls with helpful checks/guards.
*/
public static Os os = new BlockGuardOs(rawOs);
}
我们再来看看Linux.java的实现是怎样的
public final class Linux implements Os {
Linux() { }
...
public native void setpgid(int pid, int pgid) throws ErrnoException;
...
}
没错,这里面全是native函数,这些native的实现又在哪儿呢?老方法,找libcore_io_Linux,果然又找到了libcore_io_Linux.cpp
static JNINativeMethod gMethods[] = {
...
NATIVE_METHOD(Linux, setpgid, "(II)V"),
...
}
void register_libcore_io_Linux(JNIEnv* env) {
jniRegisterNativeMethods(env, "libcore/io/Linux", gMethods, NELEM(gMethods));
}
static void Linux_setpgid(JNIEnv* env, jobject, jint pid, int pgid) {
throwIfMinusOne(env, "setpgid", TEMP_FAILURE_RETRY(setpgid(pid, pgid)));
}
注册方式也是跟之前一样,用jniRegisterNativeMethods,由此我们知道setpgid就是调用Linux的系统调用setgpid.
这个系统调的作用是设置进程组id,第一个参数pid是指设置哪个进程所属的进程组,如果是0,就是当前进程所属的进程组,第二个参数是设置的id值,
如果是0,那么就把当前进程的pid作为进程组的id. 所以setgpid(0,0)的意思就是将zygote进程所在进程组id设置为zygote的pid
小结
作为进入Java世界的铺垫,本篇讲解了C++与Java之间的桥梁JNI,有了它,C++和Java就可以相互调用,本文只是讲了一些皮毛的东西,要深入理解和使用JNI,请参考英文官方,中文手册