Android基础——JNI机制

一、JNI原理

在这里插入图片描述

1.java加载本地so库的过程:

(1)在java类中加载本地.so库文件,并声明native方法;(native方法就是C/C++方法)
(也就是在java层只需要加载对应的JNI库和声明由关键字native修饰的函数)
(2)在C代码中实现so库中对应的方法;
(3)在java层需要调用native方法的地方进行调用;
·从动态加载开始看:
动态加载so库:NDK就是用动态加载,动态加载 .so库并通过 JNI调用其 .so库封装好的方法,.so库一般是由 C/C++编译的,只能被反编译成汇编代码,很难被破解, 一般情况是把 .so库一并打包到APK内部, .so库也可以动态的从 SD卡或者网络中加载进来;
(1)Android中加载so库:调用load()或者loadLibrary()方法;(具体看Android中加载so
·System.load(String filename)是指定动态库的完整路径名;System.loadLibrary(String libname)只会从指定lib目录下查找,并加上lib前缀和.so后缀;
·loadLibrary()会将xxx动态库的名字转换为libxxx.so,再从/data/app/packagename/lib/arm64或者/vendor/lib64或者/system/lib64等路径中查询对应的动态库
·System.loadLibrary()或System.load()都会调用BaseDexClassLoader的findLibrary,再到DexPathList
·在构造classloader时候都会传一个app的library路径,这就是app的so目录了(这个到类加载器去看BaseDexClassLoader.java和DexPathList.java)(具体看类加载器
·这就说到类加载器了:
BaseDexClassLoader的findLibrary:源码链接

public class BaseDexClassLoader extends ClassLoader {
    
    
   ...
    @UnsupportedAppUsage
    private final DexPathList pathList;
   ...
       public BaseDexClassLoader(String dexPath,
            String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,
            ClassLoader[] sharedLibraryLoadersAfter,
            boolean isTrusted) {
    
    
        super(parent);
     ...
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
     ...
           }
        @Override
    public String findLibrary(String name) {
    
    
        return pathList.findLibrary(name);
    }
}

DexPathList.java:源码链接

    public DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory) {
    
    
        this(definingContext, dexPath, librarySearchPath, optimizedDirectory, false);
    }
    //对DexPathList的初始化:一个是dexElements,一个是nativeLibraryPathElements
    DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
    
    
      ...
        this.definingContext = definingContext;
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        // save dexPath for BaseDexClassLoader
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext, isTrusted);
        //app目录的native库
        this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
        //系统目录的native库
        this.systemNativeLibraryDirectories =
                splitPaths(System.getProperty("java.library.path"), true);
        //记录所有的native库(包括app目录和系统目录的)
        this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());
...
    }

java层传递的参数会在jni进行一层转换
在这里插入图片描述

  • 虚拟机调用一个方法的实现,它发现如果这是一个native方法,则使用Method对象中的nativeFunc函数指针对象调用:
    (1)nativeFunc会指向一个解析方法;
    (2)通过JNIEnv提供的注册方法,手动建立native方法与java方法的关系**(动态注册)**,此时nativeFunc就指向了java方法。
  • 第一次调用native方法时,由于不知道其具体实现,就用resolveNativeMethod去查找,等找到后,就将nativeFunc替换为找到的函数,这样下次再用的时候就省去了查找的过程。
  • 第一次从dex中加载类时,会加载类中的所有方法,对于native方法,会将nativeFunc赋值为ResolveNativeMethod(此方法中有两种查询方法)
    如果是静态方法,首先确保所属的class已经初始化
    (1)内部本地方法查询
    内部本地方法表是一个集合,这个集合里包含了java语言和虚拟机本身用到的native方法
    在这个集合的结构定义里有调用native方法数组,就可以调用所有的native方法了。
    (2)动态链接库查询(System.loadLibrary()加载的so库)
    如果so中有JNI_OnLoad方法,则执行该方法。可以在该方法中做一些初始化工作,还可以手动建立java类中的native方法和so中的native方法的对应关系。(动态注册)

java对于native方法调用最后都是通过dlsym获取相应函数指针执行

二、JNI方法注册机制:

(将java层调用的native方法和native层的实现方法关联起来)

1、JNI的注册时机

1)Android系统在启动过程中,先启动Kernel创建init进程------>由init进程fork第一个横穿Java和C/C++的进程,即Zygote进程------>Zygote启动过程中会在AndroidRuntime.cpp中的startVm创建虚拟机----->VM创建完成后,调用startReg完成虚拟机中的JNI方法注册。
AndroidRuntime.cpp中的startReg方法源码链接

/*
 * Register android native functions with the VM.
 */
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
    
    
    ATRACE_NAME("RegisterAndroidNatives");
    /*
     * This hook causes all future threads created in this process to be
     * attached to the JavaVM.  
     */
     //设置线程创建方法为androidSetCreateThreadFunc
    androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
    ALOGV("--- registering native functions ---\n");
    env->PushLocalFrame(200);
    //进程JNI方法的注册
    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
    
    //register_jni_procs就是循环调用gRegJNI数组成员所对应的方法。
        env->PopLocalFrame(NULL);
        return -1;
    }
    env->PopLocalFrame(NULL);
    return 0;
}

其中gRegJNI数组有很多成员变量,每个成员变量都代表一个文件的jni映射。因为Android系统在启动的时候就注册了大量的JNI方法,就在这个数组中。这些JNI方法就是Android系统在启动的时候预注册好的。(就在AndroidRuntime.cpp中)
在这里插入图片描述
其中REG_JNI是一个宏定义,作用就是调用相应的方法。
2)另一种就是应用程序自己注册JNI(以MediaPlayer.java为例)源码链接

public class MediaPlayer extends PlayerBase
                         implements SubtitleController.Listener
                                  , VolumeAutomation
                                  , AudioRouting
{
    
    
...
    static {
    
    
        System.loadLibrary("media_jni");//通过loadLibrary加载动态库"media_jni",会自动将名称扩展为media_jni.so
        native_init();//native方法
    }
...
    private static native final void native_init();//将native关键字加在native_init之前,就可以在java层直接使用native层的方法
...

其对应的JNI层文件在/frameworks/base/media/jni/中源码链接
在这里插入图片描述可以看到android_media_MediaPlayer.cpp文件,其中就有对应的方法:也就是java层中的native_init()方法对应的native层中的android_media_MediaPlayer_native_init函数

static void
android_media_MediaPlayer_native_init(JNIEnv *env)
{
    
    
    jclass clazz;
    clazz = env->FindClass("android/media/MediaPlayer");
    if (clazz == NULL) {
    
    
        return;
    }
    fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
    if (fields.context == NULL) {
    
    
        return;
    }
...

所以JNI有两种注册时机
(1)在启动的时候注册,可以通过查询AndroidRuntime.cpp中的gRegJNI,看看是否存在对应的register方法;
(2)应用程序调用System.loadLibrary()方式自己注册JNI。

2.JNI的注册方法

·可以看到上述java层中的native_init()方法对应的native层中的函数就是android_media_MediaPlayer_native_init函数,那么它是如何知道的呢?
因为native_init函数位于android.media这个包中(路径),它的全路径名是android.media.MediaPlayer.native_init,而JNI层函数的名字是android_media_MediaPlayer_native_init。因为在Native语言中,符号“.”有着特殊的意义,所以JNI层需要把“.”换成“_”。也就是通过这种方式,native_init找到了JNI层的函数。
JNI函数的两种注册方式:
(1)静态注册:
根据函数名建立java方法和JNI函数间的对应关系,要求JNI层函数的名字必须遵循特定的格式
·过程:编写Java代码,编译生成.class文件----->使用Java的工具程序javah,如:javah –o output 包名.类名(class文件)---->生成一个native层头文件----->在头文件里,声明了对应的native层函数---->在native层将生成的.h 文件拷到文件夹内,并将头文件写入.c文件中---->在c文件中实现里面的函数即可。
这个头文件的名字一般都会使用包名_类名.h的样式
在这里插入图片描述
静态注册:Java层调用native_init函数时,会从对应JNI库Java_android_media_MediaPlayer_native_linit查找方法,如果找到,会为这个native_init和native层的函数建立关联关系,保存JNI层函数的函数指针。以后再调用native_init函数时,直接使用这个函数指针。
(2)动态注册:(以MediaPlayer为例)
在JNI技术中,有一种结构JNINativeMethod结构体用来记录这种关系:源码链接

typedef struct {
    
    
    char *name;//java方法的名字
    char *signature;//java方法的签名信息
    void *fnPtr;//JNI中对应的方法指针
} JNINativeMethod;

(这里涉及到签名:因为Java支持函数重载,可以定义同名但不同参数的函数。因此只根据函数名,是无法找到具体函数的。JNI技术中就使用参数类型和返回值类型的组合,作为一个函数的签名信息,有了签名信息和函数名,就能找到具体的函数啦!)
MediaPlayer在native层对结构体的使用(java层和native层的一一对应)(在 android_media_MediaPlayer.cpp中)源码链接

static const JNINativeMethod gMethods[] = {
    
    
    {
    
    
        "nativeSetDataSource",
        "(Landroid/os/IBinder;Ljava/lang/String;[Ljava/lang/String;"
        "[Ljava/lang/String;)V",
        (void *)android_media_MediaPlayer_setDataSourceAndHeaders
    },
    ...
    {
    
    "native_init",         "()V",                              (void *)android_media_MediaPlayer_native_init},
    {
    
    "native_setup",        "(Ljava/lang/Object;Landroid/os/Parcel;)V",(void *)android_media_MediaPlayer_native_setup},
    {
    
    "native_finalize",     "()V",                              (void *)android_media_MediaPlayer_native_finalize},
   ...
};

注册使用的方法:

static int register_android_media_MediaPlayer(JNIEnv *env)
{
    
    
    return AndroidRuntime::registerNativeMethods(env,
                "android/media/MediaPlayer", gMethods, NELEM(gMethods));
}

可以看到注册使用的时候调用了registerNativeMethods函数,该函数在AndroidRunTime类中:
源码链接

int AndroidRuntime::registerNativeMethods(JNIEnv* env,
        const char* className, const JNINativeMethod* gMethods, int numMethods) {
    
    
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}

可以看到这里又调用了jniRegisterNativeMethods方法:源码链接

static int jniRegisterNativeMethods(
    JNIEnv* env, const char* className, const JNINativeMethod* methods,
    int numMethods) {
    
    
  using namespace android::jnihelp;
  jclass clazz = env->FindClass(className);//寻找全路径,查找类名
  ...
  int result = env->RegisterNatives(clazz, methods, numMethods);//实际调用JNIEnv的RegisterNatives函数完成注册
  env->DeleteLocalRef(clazz);
  if (result == 0) {
    
    
    return 0;
  }
  // Failure to register natives is fatal. Try to report the corresponding
  // exception, otherwise abort with generic failure message.
  jthrowable thrown = env->ExceptionOccurred();
  if (thrown != nullptr) {
    
    
    struct ExpandableString summary;
    ExpandableStringInitialize(&summary);
    if (GetExceptionSummary(env, thrown, &summary)) {
    
    
      //      __android_log_print(ANDROID_LOG_FATAL, "JNIHelp", "%s",
      //      summary.data);
    }
    ExpandableStringRelease(&summary);
    env->DeleteLocalRef(thrown);
  }
  //  __android_log_print(ANDROID_LOG_FATAL, "JNIHelp",
  //                      "RegisterNatives failed for '%s'; aborting...",
  //                      className);
  return result;
}

其中调用了RegisterNatives方法(在jin.h中)源码链接

    jint RegisterNatives(jclass clazz, const JNINativeMethod *methods,
                         jint nMethods) {
    
    
        return functions->RegisterNatives(this,clazz,methods,nMethods);
    }

functions是指向JNINativeInterface结构体指针,也就是调用:

struct JNINativeInterface {
    
    
...
    jint (JNICALL *RegisterNatives)
      (JNIEnv *env, jclass clazz, const JNINativeMethod *methods,
       jint nMethods);
...
}

动态注册就是在Java层通过System.loadLibrary加载完动态库后,查找该so库中的JNI_OnLoad函数,如果有就调用它,动态注册就是在这里完成的。(也就是在JNI_OnLoad函数中完成的动态注册)。
这个过程完成了gMethods数组中的方法的映射关系,比如java层的native_init()方法,映射到native层的android_media_MediaPlayer_native_init()方法。
以MediaPlayer为例的话,其so库为上边说过的libmedia_jni.so,看这个so的JNI_OnLoad函数是如何实现的:(在android_media_MediaPlayer.cpp中)源码链接

jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
    
    
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
    
    
        ALOGE("ERROR: GetEnv failed\n");
        goto bail;
    }
    assert(env != NULL);
...
    if (register_android_media_MediaPlayer(env) < 0) {
    
    
        ALOGE("ERROR: MediaPlayer native registration failed\n");
        goto bail;
    }
...
    /* success -- return valid version number */
    result = JNI_VERSION_1_4;

bail:
    return result;
}

三、JavaVM和JNIEnv

  1. JavaVM是java虚拟机在jni层的代表,一个进程只有一个JavaVM, 所有线程共享一个JavaVM。
  2. JNIEnv表示java调用native语言的环境,是一个指向全部jni方法的指针。只在创建它的线程有效,不能跨线程传递。不同线程JNIEnv彼此独立。

获取JavaVM
(1)通过JNIEnv获取JavaVM
(2)在加载动态链接库的时候,JVM会调用JNI_OnLoad(JavaVM* jvm, void* reserved)(如果定义了该函数),第一个参数会传入JavaVM指针。
获取JNIEnv
不同线程间的JNIEnv不同,JavaVM一个进程只有一个,java代码调用c++代码的时候jni给了一个JNIEnv变量,但是如果在c++中新创建了一个子线程用于处理任务,那么在新的子线程中不能使用前面的那个JNIEnv。如果子线程中想要使用JNIEnv,只能通过JavaVM得到一个新的JNIEnv,在新的子线程中使用。

猜你喜欢

转载自blog.csdn.net/weixin_44901971/article/details/127789058