0.前言

在体系进程介绍中,咱们在剖析源码时,经常能看到Java层代码和Native层代码相互调用,而完结这个的技能便是JNI技能。

JNI是Java Native Interface的缩写,翻译为Java本地接口,是Java与其他言语通信的桥梁。当呈现一些用Java无法处理的任务时,就需求用到JNI技能,一般有如下情况:

  • 需求调用Java言语不支持的依赖于操作体系渠道特性的一些功用。比方调用linux体系某个功用,该功用Java不支持。
  • 为了整合一些以前的非Java言语开发的体系。比方用到早期完结的C/C++言语开发的一些功用或体系,将这些功用整合到当时的体系中。
  • 为了节约程序的运转时间,有必要采用其他言语(比方C/C++)来提高运转功率。比方游戏、音视频开发涉及的音视频编码和图像制作需求更快的处理速度。

本篇文章咱们首要以体系源码中用到的JNI为引子,来剖析JNI注册、怎么找到JNI办法、JNI原理等常识。

1.正文

Android体系按言语来区别的话由俩个国际组成,别离是Java国际和Native国际,至于为什么要有Native,在咱们之前剖析Android架构就知道了,Android体系中有大量的库是由C/C++言语写的,位于Native层。

连通的桥梁便是JNI,JNI的效果用跟粗浅的话来翻译便是中间人,在Java代码中想调用一个C++代码,Java层就需求告知JNI这个中间人,然后JNI去Native层找到这个C++办法,履行完,把成果再经过JNI告知Java层。所以JNI的完结,有必要是虚拟机和Android体系一起完结的,适当于把请求在中间转了一手

1.1 MediaRecorder中的JNI

MediaRecoder咱们并不陌生,它是用来录音和录像的,这儿不会介绍其详细功用,而是重点介绍里边用到的JNI结构。这儿先给出结构图:

framework | 一文搞定JNI原理

这儿MediaRecorder.java便是咱们平时运用的Java类,JNI层对应的是libmedia_jni.so,它是一个JNI的动态库,而Native层对应的是libmedia.so,这个动态库完结了实际的调用功用。

上面这段话说起来简略,可是关于C/C++不了解的话会不知道这个动态库.so到底是个啥,不易了解。这儿对so做个简略介绍,当咱们编写的C++代码经过编译就能够生成.so文件,这是一个二进制文件,经过链接就能够运转了,所以这儿暂且以为so动态库便是C++代码的可运转形态(至于为什么是动态库,还有链接的问题,后边会有独自文章剖析)。

然后上面架构图就能够变成下面这样:

framework | 一文搞定JNI原理

这儿就又有个问题,为什么.java文件直接调用最基层的.cpp不就能够了,为什么中间又一层.cpp代码呢?这也便是JNI层的表现形式,中间的JNI层其实也是C++代码,它的效果便是桥梁,在这个C++代码中咱们能够调用Java层代码也能够调用Native层的C++代码(天然能够,都是C++)。由于这儿特别的C++代码,能够把俩种混调用(Java和C++),所以JNI有它自己的类型

好了,现在咱们知道在这个JNI的C++代码中能够调用Native中的C++代码,还有一个问题需求处理,便是我在Java代码中界说一个叫做native init()函数,该函数在对应在JNI层中C++代码的哪一个呢?是不是处理这个问题,这整个链路就通了,接下来咱们就来依据源码剖析。

1.2 Java层的MediaRecorder

先来看看Java层的.java代码:

/frameworks/base/media/java/android/media/MediaRecorder.java
public class MediaRecorder
80 {
81     static {
82         System.loadLibrary("media_jni");
83         native_init();
84     }
    ...
    private static native final void native_init();
    ...
 }   

这儿先加载了名为”media_jni”的动态库,也便是libmedia_jni.so,然后调用native_init()办法,而该办法是用native润饰的,阐明它是一个native办法,由JNI来完结。

在Java层需求的做法比较简略,便是申明native办法,然后把完结该native办法的C/C++代码(JNI层)经过System.loadLibrary加载进来(加载的是so,其实从前面知道便是C/C++代码)。

1.3 JNI层的MediaRecorder

MediaRecoder的JNI层由android_media_record.cpp完结,刚刚上面界说的native_init办法界说如下:

/frameworks/base/media/jni/android_media_MediaRecorder.cpp
        static void  android_media_MediaRecorder_native_init(JNIEnv *env)
540 {
541     jclass clazz;
542 
543     clazz = env->FindClass("android/media/MediaRecorder");
544     if (clazz == NULL) {
545         return;
546     }
547 
548     fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
549     if (fields.context == NULL) {
550         return;
551     }
552 
553     fields.surface = env->GetFieldID(clazz, "mSurface", "Landroid/view/Surface;");
554     if (fields.surface == NULL) {
555         return;
556     }
557 
558     jclass surface = env->FindClass("android/view/Surface");
559     if (surface == NULL) {
560         return;
561     }
562 
563     fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
564                                                "(Ljava/lang/Object;IIILjava/lang/Object;)V");
565     if (fields.post_event == NULL) {
566         return;
567     }
568 }

这儿的android_media_MediaRecorder_native_init办法便是Java层native_init办法在JNI层的完结,在Java代码中调用native_init办法,就会调用该办法。

那这2个办法的对应联络是怎么确认的呢?就适当于这个文件的C++代码和前面所说的Java代码都加载到了内存,Java文件里调用native_init办法,是怎么找到C++文件中对应的办法,这个就需求了解JNI办法注册的常识。

1.4 Native办法注册

Native办法注册分为静态注册和动态注册,其间静态注册多用于NDK开发,而动态注册多用于Framework开发,下面咱们就别离介绍。

1.4.1 静态注册

这儿咱们直接仿照Framework的代码,创立一个MediaRecorder.java类,如下:

package com.wayeal.module;
public class MediaRecorder {
    static {
        System.loadLibrary("media_jni");
        native_init();
    }
    private static native final void native_init();
}

然后运用javah指令能够生成该Java文件的.h头文件,这儿关于javah的指令履行起来有个小细节,我这儿如下:

D:\AllAndroidProject\wayealProject\GuanWangPaiShui\app\src\main\java> javah -jni com.wayeal.module.MediaRecorder

尽管我这儿的MediaRecorder.java是在com.wayeal.module下面,可是履行该javah指令时,需求在java目录,而且后边跟上类的全名,履行完javah指令后,会在java目录生成一个com_wayeal_module_MediaRecorder.h文件,内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_wayeal_module_MediaRecorder */
#ifndef _Included_com_wayeal_module_MediaRecorder
#define _Included_com_wayeal_module_MediaRecorder
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_wayeal_module_MediaRecorder
 * Method:    native_init
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_wayeal_module_MediaRecorder_native_1init
  (JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif

至于为什么会生成这个文件?那是由于javah指令便是专门JNI为.java文件生成对应的.h文件所规划的,关于该文件有如下注意事项:

  • 什么是.h文件?即头文件,是运用在C/C++中的一种文件,在头文件中界说函数,然后在.c/.cpp中详细完结,所以能够看成是一种声明。
  • 这个文件是生成文件,最好不要修正,即能够直接把该.h文件拷贝到C++项目中,对其间的代码进行完结。
  • 这儿能够发现生成的文件和办法都是很有规矩的。首要是文件名,即把原来Java全类名:com.wayeal.module.MediaRecorder中的.换成下划线_得到头文件名:com_wayeal_module_MediaRecorder.h;其次是在Java中界说的native_init办法,在头文件中以”Java”+全类名+办法名的格局生成,详细为:Java_com_wayeal_module_MediaRecorder_native_1init,这儿多了一个”1″,这时由于Java办法中的_被换成了_1。
  • 办法的参数是JNIEnv指针,其间JNIEnv是重点,它代表Native国际中Java环境的代表,经过JNIEnv* 指针能够在Native国际拜访Java国际的代码,进行操作,它只在创立它的线程中有用,不能跨线程传递。
  • 这儿的jclass是JNI的数据类型,对应Java中的Class类型,关于JNI类型的呈现咱们前面剖析过了,至于详细的JNI类型,后边细说。

由此可见,经过javah指令,就树立了Java和C++的联络。在Java代码中调用native_init办法,就会从JNI中寻找Java_com_wayeal_module_MediaRecorder_native_1init办法,其实便是在JNI中找到而且保存该函数的函数指针,保存是为了下次再调用时,能够快速找到。

所以静态注册其实便是依据办法名,将Java办法和JNI函数树立相关,缺陷很明显,如下:

  • JNI层的函数名十分长。
  • 声明Native办法的类需求用javah生成头文件,当Java文件改动时,需求多次调用javah办法。
  • 初次调用native办法时,JNI结构需求找函数,树立相关,会影响功率。

1.4.2 动态注册

剖析一下前面的静态注册,其实生成.h文件便是为了树立一致的命名规矩,然后JNI结构便利把Java层中的native办法和详细完结的当地给对应起来,而这个对应联络其实就适当于是保存一个键值对,键便是Java中的native办法,值便是C++办法的函数指针。

已然静态注册这么费事,那能不能直接把Java办法和C++办法树立相关呢?动态注册便是这样做的。

首要便是这种Java办法和C++办法相相联络的界说,是运用JNINativeMethod结构体界说,如下:

/development/ndk/platforms/android-9/include/jni.h
typedef struct {
144     const char* name;
145     const char* signature;
146     void*       fnPtr;
147 } JNINativeMethod;

这儿别离表明Java办法的名字、Java办法的签名信息和JNI中对应的办法指针。

前面说了,动态注册常用于Framework开发,比方体系的MediaRecoder便是的,咱们来看其JNI层是怎么界说的。

在JNI的C++代码中有一个JNINativeMethod类型的数组,如下:

/frameworks/base/media/jni/android_media_MediaRecorder.cpp
static const JNINativeMethod gMethods[] = {
663     {"setCamera",            "(Landroid/hardware/Camera;)V",    (void *)android_media_MediaRecorder_setCamera},
664     {"setVideoSource",       "(I)V",                            (void *)android_media_MediaRecorder_setVideoSource},
665     {"setAudioSource",       "(I)V",                            (void *)android_media_MediaRecorder_setAudioSource},
666     {"setOutputFormat",      "(I)V",                            (void *)android_media_MediaRecorder_setOutputFormat},
667     {"setVideoEncoder",      "(I)V",                            (void *)android_media_MediaRecorder_setVideoEncoder},
668     {"setAudioEncoder",      "(I)V",                            (void *)android_media_MediaRecorder_setAudioEncoder},
669     {"setParameter",         "(Ljava/lang/String;)V",           (void *)android_media_MediaRecorder_setParameter},
670     {"_setOutputFile",       "(Ljava/io/FileDescriptor;)V",     (void *)android_media_MediaRecorder_setOutputFileFD},
671     {"_setNextOutputFile",   "(Ljava/io/FileDescriptor;)V",     (void *)android_media_MediaRecorder_setNextOutputFileFD},
672     {"setVideoSize",         "(II)V",                           (void *)android_media_MediaRecorder_setVideoSize},
673     {"setVideoFrameRate",    "(I)V",                            (void *)android_media_MediaRecorder_setVideoFrameRate},
674     {"setMaxDuration",       "(I)V",                            (void *)android_media_MediaRecorder_setMaxDuration},
675     {"setMaxFileSize",       "(J)V",                            (void *)android_media_MediaRecorder_setMaxFileSize},
676     {"_prepare",             "()V",                             (void *)android_media_MediaRecorder_prepare},
677     {"getSurface",           "()Landroid/view/Surface;",        (void *)android_media_MediaRecorder_getSurface},
678     {"getMaxAmplitude",      "()I",                             (void *)android_media_MediaRecorder_native_getMaxAmplitude},
679     {"start",                "()V",                             (void *)android_media_MediaRecorder_start},
680     {"stop",                 "()V",                             (void *)android_media_MediaRecorder_stop},
681     {"pause",                "()V",                             (void *)android_media_MediaRecorder_pause},
682     {"resume",               "()V",                             (void *)android_media_MediaRecorder_resume},
683     {"native_reset",         "()V",                             (void *)android_media_MediaRecorder_native_reset},
684     {"release",              "()V",                             (void *)android_media_MediaRecorder_release},
685     {"native_init",          "()V",                             (void *)android_media_MediaRecorder_native_init},
686     {"native_setup",         "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V",
687                                                                 (void *)android_media_MediaRecorder_native_setup},
688     {"native_finalize",      "()V",                             (void *)android_media_MediaRecorder_native_finalize},
689     {"native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaRecorder_setInputSurface },
690 
691     {"native_getMetrics",    "()Landroid/os/PersistableBundle;", (void *)android_media_MediaRecorder_native_getMetrics},
692 };

在这其间咱们发现前面Java中所说的native_init办法也有界说,其办法签名是”()V”,这个办法签名简略来说便是Java办法能够重载,仅靠名字是无法区别的,办法签名便是办法的参数和回来值的合体,这儿就阐明native_init这个Java办法的参数是没有,回来值是void,关于办法签名,后边细说,然后便是指明晰对应的C++函数的函数指针。

有了办法联络还不行,需求把这个联络列表告知给JNI,即需求注册它,注册的函数是register_android_media_MediaRecorder:

/frameworks/base/media/jni/android_media_MediaRecorder.cpp
int register_android_media_MediaRecorder(JNIEnv *env)
697 {
698     return AndroidRuntime::registerNativeMethods(env,
699                 "android/media/MediaRecorder", gMethods, NELEM(gMethods));
700 }

在该办法中会调用AndroidRuntime这个C++类的办法,这儿咱们就知道了保存和查询该联络的便是虚拟机和运转时环境所做的,待会再剖析该函数。

那么问题来了,已然这个办法是注册联络,那这个办法是什么时分调用呢?从该办法注释咱们能够知道,它会在android_media_MediaPlayer.cpp的JNI_OnLoad中调用:

/frameworks/base/media/jni/android_media_MediaPlayer.cpp
    jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
1465 {
1466     JNIEnv* env = NULL;
1467     jint result = -1;
1468 
1469    ...
1490     if (register_android_media_MediaRecorder(env) < 0) {
1491         ALOGE("ERROR: MediaRecorder native registration failed\n");
1492         goto bail;
1493     }
        ...
1574 
1575     /* success -- return valid version number */
1576     result = JNI_VERSION_1_4;
1577 
1578 bail:
1579     return result;
1580 }

那这个JNI_OnLoad会在什么调用呢,该办法会在咱们最开端的System.loadLibrary()函数中调用,这么一看,整个链路就通了。

咱们来总结画个时序图:

sequenceDiagram
MediaRecoder.java->>AndroidRuntime:System.loadLibrary("media_jni")
AndroidRuntime->>android_media_MediaPlayer.cpp:JNI_OnLoad
Note left of android_media_MediaRecorder.cpp:该C++办法的动态库便是"libmedia_jni.so"
android_media_MediaPlayer.cpp->>android_media_MediaRecorder.cpp:register_android_media_MedirRecorder()
android_media_MediaRecorder.cpp->>AndroidRuntime:注册Java层办法和JNI层函数联络
Note right of MediaRecoder.java:调用native_init()
MediaRecoder.java->>AndroidRuntime:告知虚拟机,需求找到该办法对应的JNI层办法
AndroidRuntime->>android_media_MediaRecorder.cpp:依据注册联络找到JNI层中办法
Note right of android_media_MediaRecorder.cpp:开端履行C++的初始化事务

从上面时序图咱们发现JNI的原理完结是虚拟机、AndroidRuntime一起完结的。

1.5 JNI查找办法

了解了Java动态注册,咱们来回想一下之前Android体系启动剖析,首要是用户态的第一个进程init,然后fork出一个横穿Java和C/C++的Zygote进程,在Zygote进程中会创立虚拟机,在创立完虚拟机后会调用starReg来完结虚拟机中JNI办法注册,这也便是为什么创立完虚拟机,能够运用Java代码,而Java代码中能够运用native办法的原因。

1.5.1 注册JNI办法

在Zygote中会调用AndroidRuntime::startReg来注册:

/frameworks/base/core/jni/AndroidRuntime.cpp
int AndroidRuntime::startReg(JNIEnv* env)
1420 {
            ...
1437     env->PushLocalFrame(200);
            //进行注册,gRegJNI保存着待注册的办法
1439     if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
1440         env->PopLocalFrame(NULL);
1441         return -1;
1442     }
1443     env->PopLocalFrame(NULL);
1444 
1445     //createJavaThread("fubar", quickTest, (void*) "hello");
1446 
1447     return 0;
1448 }

这儿咱们来看一下这个gRegJNI数组:

/frameworks/base/core/jni/AndroidRuntime.cpp
static const RegJNIRec gRegJNI[] = {
1264     REG_JNI(register_com_android_internal_os_RuntimeInit),
1265     ...
1279     REG_JNI(register_android_os_Process),
1280     REG_JNI(register_android_os_SystemProperties),
1281     REG_JNI(register_android_os_Binder),
1282     REG_JNI(register_android_os_Parcel),
1283     REG_JNI(register_android_nio_utils),
1284    ...
1353     REG_JNI(register_android_os_MessageQueue),
1354    ...
1413 
1414 };

这儿会注册几百个不同文件的JNI办法,这儿举例咱们十分了解的MessageQueue,看一下:

/frameworks/base/core/jni/android_os_MessageQueue.cpp
    int register_android_os_MessageQueue(JNIEnv* env) {
224     int res = RegisterMethodsOrDie(env, "android/os/MessageQueue", gMessageQueueMethods,
225                                    NELEM(gMessageQueueMethods));
226 
227     jclass clazz = FindClassOrDie(env, "android/os/MessageQueue");
228     gMessageQueueClassInfo.mPtr = GetFieldIDOrDie(env, clazz, "mPtr", "J");
229     gMessageQueueClassInfo.dispatchEvents = GetMethodIDOrDie(env, clazz,
230             "dispatchEvents", "(II)I");
231 
232     return res;
233 }

这儿的RegisterMethodsOrDie便是用来注册办法,gMessageQueueMethods便是需求注册的办法:

/frameworks/base/core/jni/android_os_MessageQueue.cpp
static const JNINativeMethod gMessageQueueMethods[] = {
213     /* name, signature, funcPtr */
214     { "nativeInit", "()J", (void*)android_os_MessageQueue_nativeInit },
215     { "nativeDestroy", "(J)V", (void*)android_os_MessageQueue_nativeDestroy },
216     { "nativePollOnce", "(JI)V", (void*)android_os_MessageQueue_nativePollOnce },
217     { "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake },
218     { "nativeIsPolling", "(J)Z", (void*)android_os_MessageQueue_nativeIsPolling },
219     { "nativeSetFileDescriptorEvents", "(JII)V",
220             (void*)android_os_MessageQueue_nativeSetFileDescriptorEvents },
221 };

这儿咱们能够发现了解的JNI函数界说办法。

1.5.2 怎么查找native办法

已然这些要害的类和办法在虚拟机创立后就进行注册了,那咱们运用Java类时怎么找到对应的JNI办法呢?

这儿咱们以Android音讯机制MessageQueue.java为例,在该类中有如下native办法:

/frameworks/base/core/java/android/os/MessageQueue.java
 private native void nativePollOnce(long ptr, int timeoutMillis)

一起咱们会发现该类并没有像之前动态注册相同调用System.Loadlibrary,由于这些native办法现已注册过了。

那怎么找到JNI办法完结呢,有个最简略的办法便是看类名,这儿的类名是android.os.MessageQueue,那么对应的JNI文件一般便是android_os_MessageQueue.cpp,而这些体系注册的JNI文件位置一般是在/framework/base/core/jni,咱们找到该目录:

framework | 一文搞定JNI原理

会发现这些文件的名字都有很强的规矩。

可是总有意外,有些文件却打破了这个命名规矩,比方咱们了解的Binder,它在Java层全名是android.os.Binder,按理说在JNI中文件名是android_os_binder.cpp,可是它在JNI中的文件名却是android_util_Binder.cpp,至于为什么有少数文件打破这个规矩,我也无从得知。

1.6 数据类型转化

由于JNI代码是C++代码,所以Java的办法想和JNI层的办法做到1对1对应,那么Java办法中的参数类型就需求在JNI中也要有对应,那为什么不必C++的数据类型呢?原因十分简略,这俩个言语的数据类型是不通用的。

Java的数据类型分为根本数据类型和引证数据类型,JNI层对这俩种类型也做了区别,下面咱们来挨个看看。

1.6.1 根本数据类型的转化

根本数据类型的转化比较简略,除了void外,其他都是在原Java类型基础上加个”j”即可,下表便是根本数据类型对应的JNI类型和签名:

Java JNI 签名
byte jbyte B
char jchar C
double jdouble D
float jfloat F
int jinit I
short jshort S
long jlong J
boolean jboolean Z
void void V

1.6.2 引证数据类型转化

引证类型首要包含数组和类,在JNI层中关于数组要以”Array”结束,关于普通的类,以jobject,关于Class、String和Throwable要做特别处理。而签名而言,关于非数组类型一致都是”L+classname+;”的格局,详细如下:

Java JNI 签名
类Object jobject L+class+;
Class jclass Ljava/lang/Class;
Throwable jthrowable Ljava/lang/Throwable;
String jstring Ljava/lang/String;
Object[] jobjectArray [L+classname+;
byte[] jbyteArray [B
char[] jcharArray [C
double[] jdoubleArray [D
float[] jfloatArray [F
int[] jintArray [I
short[] jshortArray [S
long[] jlongArray [J
boolean[] jbooleanArray [Z

其实上面的JNI类型都不难了解,稍微需求记住的便是Class、Throwable和String这3种特别的非数组引证类型,其他一概是jobject,咱们来看个比如,这是Java中界说的一个native办法:

private native void native_setup(Object mediarecorderThis,
        String clientName, @NonNull Parcel attributionSource)

能够发现这儿的参数有Object类型,String类型和Parcl类型,找到对应的JNI层完结函数:

android_media_MediaRecorder_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
                                         jstring packageName, jobject jAttributionSource)

能够发现从Java办法到JNI办法的转变,首要是多了一个类型为JNIEnv指针类型的参数,然后Java中的Object变成了jobject,Java中的String变成了jstring,java中的Parcel变成了jobject,至于为什么多个JNIEnv类型指针,后边咱们细说,简略来说便是它代表着Java国际的环境指针,经过该指针能够控制Java代码。

1.7 办法签名

了解Java的开发者都知道,Java中能够界说同名可是回来值或者参数不同的办法,这个便是Java的办法重载。在前面界说Java办法和JNI办法联络的结构体中,第一个成员是Java办法名,第二个成员是签名,也只要这俩者结合,才能够确认仅有的Java办法。

所以何为办法签名,便是把办法的参数和回来值类型组合在一起便是办法的签名,格局为:

(参数类型)回来值类型

一起这儿的类型也不是Java类型,而是类型的签名,能够看成是一种Java类型的简写,比方在前面咱们都罗列过了,Java的int类型对应于I,这些类型签名标识符是运用在Java虚拟机的。

所以现在咱们再来看看前面动态注册的办法:

{"native_setup",
"(Ljava/lang/Object;Ljava/lang/String;Landroid/os/Parcel;)V",
(void *)android_media_MediaRecorder_native_setup}

这儿咱们发现在Java中的办法名为native_setup,签名转化为Java言语便是该办法的回来值是Void,参数别离是Ojbect、String和Parcel,对应的C++办法是一个函数指针。

办法签名不难了解,可是其签名格局真的让人难以记住,直接写简略犯错,所以能够运用javap -s -p指令检查一个Java类的所有办法和成员签名,测试代码和效果如下:

PS D:\AllAndroidProject\wayealProject\GuanWangPaiShui\app\src\main\java\com\wayeal\module> javac .\MediaRecorder.java
Picked up _JAVA_OPTIONS: -Xmx1024M
PS D:\AllAndroidProject\wayealProject\GuanWangPaiShui\app\src\main\java\com\wayeal\module> javap -s -p .\MediaRecorder.class
Picked up _JAVA_OPTIONS: -Xmx1024M
Compiled from "MediaRecorder.java"
public class com.wayeal.module.MediaRecorder {
  public com.wayeal.module.MediaRecorder();
    descriptor: ()V
  private static final native void native_init();
    descriptor: ()V
  private static final native void native_start(java.lang.String, java.io.File, java.lang.Byte[]);
    descriptor: (Ljava/lang/String;Ljava/io/File;[Ljava/lang/Byte;)V
  static {};
    descriptor: ()V
}

有了这个指令生成办法签名,就再也不怕在动态注册时写错办法签名了。

1.8 解析JNIEnv

在前面剖析中咱们知道Java中的办法对应到JNI中的办法会在参数上多一个JNIEnv指针参数,该参数十分重要,咱们来回忆一下前面native_init办法在JNI层的完结:

/frameworks/base/media/jni/android_media_MediaRecorder.cpp
    static void
439 android_media_MediaRecorder_native_init(JNIEnv *env)
440 {
441     jclass clazz;
442 
443     clazz = env->FindClass("android/media/MediaRecorder");
444     if (clazz == NULL) {
445         return;
446     }
447 
448     fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
449     if (fields.context == NULL) {
450         return;
451     }
452 
453     fields.surface = env->GetFieldID(clazz, "mSurface", "Landroid/view/Surface;");
454     if (fields.surface == NULL) {
455         return;
456     }
457 
458     jclass surface = env->FindClass("android/view/Surface");
459     if (surface == NULL) {
460         return;
461     }
462 
463     fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
464                                                "(Ljava/lang/Object;IIILjava/lang/Object;)V");
465     if (fields.post_event == NULL) {
466         return;
467     }
468 }

在这个办法中,咱们发现env不只能够获取Java国际的类,还能够调用Java国际的办法,所以这个JNIEnv其实便是Native国际中Java环境的代表,经过JNIEnv* 指针就能够在Native国际中拜访Java国际的代码进行操作,它只在创立它的线程中有用,不能跨线程传递,不同线程的JNIEnv是彼此独立的。

为什么有这个JNIEnv也十分好了解,已然JNI作为Java国际和Native国际的”中间人”,它本身便是C++代码,所以调用Native的代码是天生的,可是调用Java国际的代码就费力了,所以这儿就需求有个Java国际的代表:JNIEnv。

其实咱们能够猜想一下这个JNIEnv的功用能拜访Java国际的类和办法,其完结细节肯定也是和虚拟机和AndroidRuntime有关,咱们这儿不做深入探求,来看一下JNIEnv这个结构体的界说:

/libnativehelper/include/nativehelper/jni.h
struct _JNIEnv {
        //C言语
493     const struct JNINativeInterface* functions;
494 
    //C++
495 #if defined(__cplusplus)
496 
497    ...
503 
504     jclass FindClass(const char* name)
505     { return functions->FindClass(this, name); }
506 
507     }
        ...
554 
555     void DeleteLocalRef(jobject localRef)
556     { functions->DeleteLocalRef(this, localRef); }
557 
558    ...
1031 #endif /*__cplusplus*/
1032 };

这儿有点离谱,会发现C++时,会多出几百行的结构体界说,至于为啥这么多,其实也很简略想到。由于C++和Java言语差距巨大,这儿有必要能涉及到所有Java相关的特性,比方类、变量、父类、反常、引证类型、数组、开释内存等等,所以会有办法,这部分就不细说了,需求时能够检查该结构体。

不过这儿咱们发现结构体中即使是C++代码,也是调用JNINativeInterface* 指针的办法,所以重点便是该数据结构:

/libnativehelper/include/nativehelper/jni.h
struct JNINativeInterface {
        ...
160     jclass      (*FindClass)(JNIEnv*, const char*);
161 
162     jmethodID   (*FromReflectedMethod)(JNIEnv*, jobject);
        ...
483 };

在该结构体中,毫不意外地界说了几百个函数指针,这些函数指针都由虚拟机和体系完结,这儿咱们就不过多研讨了,这儿只需求有个大约概念,便是JNI还是十分强壮的,为了能顺利调用Java国际中的内容,做出了巨大尽力。

这儿咱们来看俩个常用的类型,别离是jmethodID和jfieldID,别离表明Java类中的成员变量和办法,咱们再来看看native_init函数,看看能不能读懂代码了:

/frameworks/base/media/jni/android_media_MediaRecorder.cpp
    static void
439 android_media_MediaRecorder_native_init(JNIEnv *env)
440 {
        //clazz是Java类,类型jclass便是Java中的Class
441     jclass clazz;
        //经过env的FindClass找到"android.media.MediaRecoder"这个Java类
443     clazz = env->FindClass("android/media/MediaRecorder");
444     if (clazz == NULL) {
445         return;
446     }
        //经过env的GetFieldId,从clazz找到变量名为"mNativeContext"的变量,然后赋值给field目标的context变量
448     fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
449     if (fields.context == NULL) {
450         return;
451     }
        //从clazz找到名为"mSurface"的变量,保存到field中
453     fields.surface = env->GetFieldID(clazz, "mSurface", "Landroid/view/Surface;");
454     if (fields.surface == NULL) {
455         return;
456     }
        //找到"android.view.Surface"类
458     jclass surface = env->FindClass("android/view/Surface");
459     if (surface == NULL) {
460         return;
461     }
        //找到clazz中名为"postEventFromNative"的静态办法,一起保存到field中
463     fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
464                                                "(Ljava/lang/Object;IIILjava/lang/Object;)V");
465     if (fields.post_event == NULL) {
466         return;
467     }
468 }

这儿代码看似十分费力,就为了获取MediaRecoder.java类中的俩个变量和一个办法,可是这儿边涉及了一个要害常识点便是Class类。

关于Class类,把一个Java代码加载到虚拟机中就会生成一个Class目标,该Class目标描绘了Java类的信息,比方有哪些变量、办法等。就比方上面代码中的clazz目标便是Java中的Class目标,经过clazz获取其间的变量和办法就比较便利。

已然这儿仅仅获取了变量和办法,其实和详细目标是没联络的,咱们来看看什么时分调用这些,经过阅览代码,咱们会发现在notify办法中会调用之前保存这些变量和办法的field变量:

void JNIMediaRecorderListener::notify(int msg, int ext1, int ext2)
105 {
106     ALOGV("JNIMediaRecorderListener::notify");
107 
108     JNIEnv *env = AndroidRuntime::getJNIEnv();
109     env->CallStaticVoidMethod(mClass, fields.post_event, mObject, msg, ext1, ext2, NULL);
110 }

这儿的mClass是经过NewGlobalRef创立的目标,而第二个参数便是前面获取的”postEventFromNative”办法,所以这儿会调用Java中的postEventFromNative办法:

     private static void postEventFromNative(Object mediarecorder_ref,
1150                                             int what, int arg1, int arg2, Object obj)
1151     {
1152         MediaRecorder mr = (MediaRecorder)((WeakReference)mediarecorder_ref).get();
1153         if (mr == null) {
1154             return;
1155         }
1156 
1157         if (mr.mEventHandler != null) {
1158             Message m = mr.mEventHandler.obtainMessage(what, arg1, arg2, obj);
1159             mr.mEventHandler.sendMessage(m);
1160         }
1161     }

这样咱们就能够完整地看见JNI是怎么调用Java层代码了。

1.9 引证类型

了解Java的同学都知道编写Java代码是不必处理其内存收回的,由于Java虚拟机的GC机制即垃圾收回机制会帮咱们收回那些不必的目标的内存。那JNI作为Java和C/C++的中间人,既能够调用Java代码又能够调用C++代码,那该怎么做内存收回呢?

和Java的引证类型相同,JNI也有自己的引证类型,它们别离是本地引证(Local References)、大局引证(Global References)和弱大局引证(Weak Global References)。从名字就能够看出它们大约对应Java中的局部变量,类静态变量和弱引证,有着不同的生命周期和GC机制,咱们就来别离介绍。

1.9.1 本地引证

JNIEnv提供的函数所回来的引证根本上都是本地引证,所以本地引证是JNI中最常见的引证类型,特色如下:

  • 当Native函数回来时,这个本地引证就会被主动开释。
  • 只在创立它的线程中有用,不能跨线程运用。
  • 局部引证是JVM负责的引证类型,受JVM办理。

这儿举个了解的比如:

/frameworks/base/media/jni/android_media_MediaRecorder.cpp
    static void
439 android_media_MediaRecorder_native_init(JNIEnv *env)
440 {
441     jclass clazz;
442 
443     clazz = env->FindClass("android/media/MediaRecorder");
444     if (clazz == NULL) {
445         return;
446     }
    ...

这儿clazz的类型是jclass,是引证数据类型,便是本地引证,也便是办法内的局部变量。它会在native_init()办法履行完会主动开释,咱们也能够运用JNIEnv的DeleteLocalRef函数来手动删除本地引证,该函数运用场景是在native函数回来前占用了大量内存,需求调用DeleteLocalRef函数当即删除本地引证。

1.9.2 大局引证

经过JNIEnv的NewGlobalRef函数能够创立大局引证,该种引证和本地引证简直相反,有如下特色:

  • 在native函数回来时不会被主动开释,因此大局引证需求手动来进行开释,而且不会被GC收回。
  • 大局引证是能够跨线程运用的。
  • 大局引证不受JVM办理。

这儿举个比如,JNIMediaRecorderListener是JNI层中的一个C++类,构造函数如下:

JNIMediaRecorderListener::JNIMediaRecorderListener(JNIEnv* env, jobject thiz, jobject weak_thiz)
79 {
83     jclass clazz = env->GetObjectClass(thiz);
84     if (clazz == NULL) {
85         ALOGE("Can't find android/media/MediaRecorder");
86         jniThrowException(env, "java/lang/Exception", NULL);
87         return;
88     }
        //这儿创立了俩个大局引证变量
89     mClass = (jclass)env->NewGlobalRef(clazz);
93     mObject  = env->NewGlobalRef(weak_thiz);
94 }

然后在其析构函数中需求对这俩个变量进行开释:

JNIMediaRecorderListener::~JNIMediaRecorderListener()
97 {
99     JNIEnv *env = AndroidRuntime::getJNIEnv();
100     env->DeleteGlobalRef(mObject);
101     env->DeleteGlobalRef(mClass);
102 }

所以运用大局引证时,要多加小心。

1.9.3 弱大局引证

这是一个特别的大局引证,它和大局引证的特色相识,不同的是弱大局是能够被GC收回的,弱大局引证被GC收回后,会指向NULL。

说实话,我有点不太清楚这种弱大局引证的效果,由于它有个丧命问题,便是运用NewWeakGlobalRef创立的弱大局引证变量,在运用时还需求调用IsSameObject来判别有没有被GC收回,实在是增加了很多工作量。

2. 总结

JNI技能作为Java国际和Native国际的桥梁,起着十分重要的效果,它是由虚拟机和运转时环境一起完结。现在做个末节:

  • 首要需求了解动态库so是什么,以MediaRecorder.java为例,要能读懂这种Java和C++相互调用的代码。
  • 关于JNI注册的办法,静态注册便是利用javap指令生成头文件,以办法名构建联络;而动态注册则是手动进行Java办法和native办法相关,经过会有必要被调用的JNI_OnLoad函数完结注册。
  • 体系在创立完虚拟机后就会注册一大堆JNI函数,经过类名,咱们能够找到常见类的JNI函数完结位置。
  • 关于特别的JNI数据类型,只需求了解Java数据类型,不难了解JNI的数据类型。
  • 办法签名是用来确认仅有Java办法的,便是参数和回来值的结合体。
  • JNIEnv十分要害,它是native环境中Java国际的代表,经过它能够调用Java国际代码。
  • 关于JNI中的目标,同样能够设置不同引证来设置其生命周期和开释办法。

JNI技能至关重要,后边剖析虚拟机时,会再进行探求。

笔者水平有限,文中有问题,欢迎指正。最终记载一下Flag。# 一个Android开发的学习Flag记载贴

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。