Android框架基础JNI

Android框架基础JNI

1 JNI在Android系统中所处的位置

JNI在Android系统中所处的位置如图所示
Android框架基础JNI_第1张图片

注意JNI与NDK的区别:NDK是为了便于开发基于JNI的应用而提供的一套开发和编译工具集,而JNI则是一套编程接口,可以运用在应用层,也可以运用在应用框架层,以实现Java与本地代码的互操作。

JNI编程模型的结构可以概括为以下三个步骤:
* Java层声明native方法
* JNI层实现Java层声明的native方法,在JNI层可以调用底层库或者回调Java层方法。这部分将被编译为动态库(so文件)供系统加载
* 加载JNI层代码编译后生成的共享库

2 JNI框架层实例分析

2.1 Log系统Java层分析

Log系统Java层中Log.java文件中定义了两个native方法

/**
 * Send a {@link #DEBUG} log message.
 * @param tag Used to identify the source of a log message.  It usually identifies
 *        the class or activity where the log call occurs.
 * @param msg The message you would like logged.
 */
public static int d(String tag, String msg) {
    return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
}

/**
 * Checks to see whether or not a log for the specified tag is loggable at the specified level.
 *
 *  The default level of any tag is set to INFO. This means that any level above and including
 *  INFO will be logged. Before you make any calls to a logging method you should check to see
 *  if your tag should be logged. You can change the default level by setting a system property:
 *      'setprop log.tag.<YOUR_LOG_TAG> <LEVEL>'
 *  Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
 *  turn off all logging for your tag. You can also create a local.prop file that with the
 *  following in it:
 *      'log.tag.<YOUR_LOG_TAG>=<LEVEL>'
 *  and place that in /data/local.prop.
 *
 * @param tag The tag to check.
 * @param level The level to check.
 * @return Whether or not that this is allowed to be logged.
 * @throws IllegalArgumentException is thrown if the tag.length() > 23
 *         for Nougat (7.0) releases (API <= 23) and prior, there is no
 *         tag limit of concern after this API level.
 */
public static native boolean isLoggable(String tag, int level);

······

/** @hide */ public static native int println_native(int bufID, int priority, String tag, String msg);

2.2 Log系统的JNI层

打开android_util_Log.cpp文件
#define LOG_NAMESPACE “log.tag.”
#define LOG_TAG “Log_println”

······

#include "jni.h"    // 符合JNI规范的头文件
#include "JNIHelp.h"    // Android为更好地支持JNI提供的头文件
#include "utils/misc.h"
#include "core_jni_helpers.h"
#include "android_util_Log.h"

namespace android {

    ······

    /** 
      * JNI方法增加了JNIEnv和jobject两个参数,其余参数和返回值只是将Java参数   
      * 映射成JNI数据类型,然后通过调用本地库和JNIEnv提供的JNI函数处理数据,   
      * 最后返回给Java层 
     **/
    static jboolean isLoggable(const char* tag, jint level) {
        return __android_log_is_loggable(level, tag, ANDROID_LOG_INFO);
    }

    static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
    {
        if (tag == NULL) {
            return false;
        }

        const char* chars = env->GetStringUTFChars(tag, NULL);
        if (!chars) {
            return false;
        }

        jboolean result = isLoggable(chars, level);

        env->ReleaseStringUTFChars(tag, chars);
        return result;
    }

    ······

    /*
     * In class android.util.Log:
     *  public static native int println_native(int buffer, int priority, String tag, String msg)
     */
    static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
            jint bufID, jint priority, jstring tagObj, jstring msgObj)
    {
        const char* tag = NULL;
        const char* msg = NULL;

        if (msgObj == NULL) {
            jniThrowNullPointerException(env, "println needs a message");
            return -1;
        }

        if (bufID < 0 || bufID >= LOG_ID_MAX) {
            jniThrowNullPointerException(env, "bad bufID");
            return -1;
        }

        if (tagObj != NULL)
            tag = env->GetStringUTFChars(tagObj, NULL);
        msg = env->GetStringUTFChars(msgObj, NULL);

        int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);

        // 调用JNI函数释放资源
        if (tag != NULL)
            env->ReleaseStringUTFChars(tagObj, tag);
        env->ReleaseStringUTFChars(msgObj, msg);

        return res;
    }

    ······

    int register_android_util_Log(JNIEnv* env)
    {
        jclass clazz = FindClassOrDie(env, "android/util/Log");

        levels.verbose = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "VERBOSE", "I"));
        levels.debug = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "DEBUG", "I"));
        levels.info = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "INFO", "I"));
        levels.warn = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "WARN", "I"));
        levels.error = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "ERROR", "I"));
        levels.assert = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "ASSERT", "I"));

        return RegisterMethodsOrDie(env, "android/util/Log", gMethods, NELEM(gMethods));
    }

}; // namespace android

JNI层的实现方法只是根据一定的规则与Java层声明的方法做了一个映射,然后通过调用本地块函数或者JNIEnv提供的JNI函数响应Java层的调用

2.3 Log系统的JNI方法注册

定位到android_util_Log.cpp的源码

/*
 * JNI registration.
 */
static const JNINativeMethod gMethods[] = {
    /* name, signature, funcPtr */
    { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
    { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
    { "logger_entry_max_payload_native",  "()I", (void*) android_util_Log_logger_entry_max_payload_native },
};

GMethods用来存储JNINativeMethod类型的数据

typedef struct {
    const char* name;       // Java层声明的native函数的函数名
    const char* signature;  // Java函数的签名,依据JNI的签名规则
    void*       fnPtr;      // 函数指针,指向JNI的实现方法
} JNINativeMethod;

JNINativeMethod是一个结构体类型,保存了Java层声明函数和JNI实现函数的一一对应的关系。

可是如何告诉虚拟机这种对应的关系?我们继续看android_util_Log.cpp中的register_android_util_Log函数

int register_android_util_Log(JNIEnv* env)
{
    jclass clazz = env->FindClass("android/util/Log");

    ······

    levels.debug = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "DEBUG", "I"));

    ······

    return AndroidRuntime::registerMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
}

这里我们来解析三个问题
* registerMethods函数的作用是什么?

定位到函数内部

/*
 * 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);
}

在JNIHelp.h和JNIHelp.cpp中找到函数jniRegisterNativeMethods的实现

int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods);

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));
    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) {
        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;
}

这里最终调用了JNIEnv的RegisterNatives函数,将gMethods中存储的方法关联信息传递给虚拟机。其作用就是向clazz参数指定的类注册本地方法,这样虚拟机就得到了Java层和JNI层之间的对应关系,就可以实现Java和C/C++代码的互操作了
* register_android_util_Log函数是在哪调用的

这个函数是在系统启动过程中通过AndroidRuntime.cpp的register_jni_procs方法执行的,进而调用到register_android_util_Log函数将这种映射关系注册给Dalvik虚拟机

建立Java层声明函数和JNI层实现函数之间的对应关系有两种方式
* 遵守JNI方法命名规范
* 采用函数注册方式

应用层多采用第一种,框架层多采用第二种。

3 JNIEnv介绍

我们先看看JNIEnv的体系结构
Android框架基础JNI_第2张图片
从图中我们可以看出JNIEnv首先指向一个线程相关的结构,该结构又指向一个指针数组,在这个指针数组中每一个元素最终又指向一个JNI函数,所有可以通过JNIEnv去调用JNI函数。JNIEnv是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境

JavaVM和JNIEnv的关系
* JavaVM是Java虚拟机在JNI层的代表,JNI全局只有一个
* JNIEnv是一个线程相关的结构体,每个线程都有一个,JNI中可能有多个

相关源码就不看了,我们直接给出结论
* 在C++中:JNIEnv就是struct _JNIEnv,JNIEnv * env等价于struct _JNIEnv * env,在调用JNI函数的时候,只需要env->FindClass就会直接调用JNINativeInterface结构体里定义的函数指针,而无需首先对env解引用
* 在C中:JNIEnv就是const struct JNINativeInterface * ,JNIEnv * env实际上等价于const struct JNINativeInterface * env,因此要得到JNINativeInterface结构体内的函数指针就必须先对env解引用得到( env),即const struct JNINativeInterface ,这个指针才是真正指向JNINativeInterface结构体的指针,然后再通过它调用具体的JNI函数,因此需要( env)->FindClass这样调用

注意:

JNIEnv只在当前线程中有效。本地方法不能讲JNIEnv从一个线程传递到另一个线程,相同的Java线程对本地方法多次调用时传递的JNIEnv是相同的。但是一个本地方法可以被不同的Java线程调用,因此可以接受不同的JNIEnv

4 JNI规范

4.1 Java数据类型和JNI数据类型转换

  • 基本数据类型转换
    image
  • 引用数据类型转换
    image

4.2 JNI方法命名规则

JNI方法由以下几个部分组成:
* 前缀: Java_
* 类的全限定名,用下划线进行分隔(_):com_lms_jni_JniTest
* 方法名:getTestString
* jni函数指定第一个参数: JNIEnv *
* jni函数指定第二个参数: jobject
* 实际Java参数: jstring, jint ….
* 返回值的参数 : jstring, jint….

如果采用函数注册的方式,可以不遵守上述规则

4.3 JNI方法签名规则

JNI的签名规则如下:

(参数1类型签名参数2类型签名···参数n类型签名)返回值类型签名

注意:中间没有任何符号

Android框架基础JNI_第3张图片

注意:类的签名规则是:”L+全限定类名+;”,其中全限定类名由”/”分隔。例如

long fun(int n, String str, int[] arr);

其方法签名为:(ILjava/lang/String;[I)J

5 JNI操作Java对象

5.1 访问Java对象

JNI提供的类和对象操作的方法有很多,常用的有两个:FindClass和GetObjectClass,在C和C++中有不同的函数原型:
* C++:

    jclass FindClass(const char * name);

    jclass GetObjectClass(jobject obj);

* C:

    jclass (* FindClass)(JNIEnv *, const char *);

    jclass (* GetObjectClass)(JNIEnv *, jobject);

我们看看Log系统是如何操作Java对象的

jclass clazz = env->FindClass("android/util/Log");

5.2 操作成员变量和方法

我们还是来看看Log系统是如何操作成员变量的

jclass clazz = env->FindClass("android/util/Log");
levels.debug = env->GetStaticIntField(clazz, GetStaticFieldID(env, clazz, "DEBUG", "I"));

首先通过FindClass找到android/util/Log的类信息clazz,然后以clazz为参数调用GetStaticFieldId(clazz, “DEBUG”, “I”),其函数原型如下:

jfieldId GetStaticFieldId(jclass clazz, const char * name, const char * sig);

最后将返回的jfieldId传递给GetStaticIntField方法得到android/util/Log.java的成员变量DEBUG的值

JNI操Java层的方法类似,流程是
FindClass->GetMethodId->返回jMethodId->CallMethod
image

5.3 全局引用、弱全局引用、局部引用

  • 局部引用:可以增加引用计数,作用范围为本线程,生命周期为一次Native调用。局部引用包括多数JNI函数创建的引用,Native方法返回值和参数。局部引用只在创建它的Native方法的线程中有效,并且只在Native方法的一次调用中有效,在该方法返回后,被虚拟机回收(不同于C中的局部变量,返回后会立即回收)。
  • 可以增加引用计数。作用范围为多线程,多个Native方法,生命周期到显式释放。全局引用通过JNI函数NewGlobalRef创建,并通过DeleteGlobalRef释放。如果程序员不显式释放,将永远不会被垃圾回收。
  • 不能增加引用计数。作用范围为多线程,多个Native方法,生命周期到显式释放。不过其对应的Java对象生命周期依然取决于虚拟机,意思是即便弱全局引用没有被释放,其引用的Java对象可能已经被释放。弱全局引用通过JNI函数NewWeakGlobalRef创建,并通过DeleteWeakGlobalRef释放。弱全局引用的优点是:既可以保存对象,又不会阻止该对象被回收。

注:本文参考《Android设计与实现卷I(杨云君)》

你可能感兴趣的:(Android开发)