Android系统的JNI原理分析(二)

声明

  • 前阶段在项目中使用了Android的JNI技术,在此文中做些技术知识总结。
  • 本文参考了一些书籍的若干章节,比如《Android进阶解密-第9章-JNI原理》、《深入理解Android虚拟机-第4章-分析JNI》、《深入理解Android系统-第2章-分析JNI》、《Android NDK Beginner_'s Guide》等
  • 之所以会参考这么多书籍的原因是我在看某一技术问题时,习惯参考不同书籍作者的看法,毕竟不同的书都有自己的侧重点,而且不同的作者对同一技术问题的理解深度也不同。
  • 本文使用的代码时LineageOS的cm-14.1,对应Android 7.1.2,可以参考我的另一篇博客:如何下载Nexus5的LineageOS14.1(cm-14.1)系统源码并编译、刷机

3 Java层和JNI层的数据类型转换和方法签名

    进入到源码路径:
vim ~/LineageOS/frameworks/base/media/jni/android_
media_MediaRecorder.cpp,查看函数android_media_MediaRecorder_native_setup:

static void
android_media_MediaRecorder_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
                                         jstring packageName, jstring opPackageName)
{
    ALOGV("setup");

    ScopedUtfChars opPackageNameStr(env, opPackageName);

    sp<MediaRecorder> mr = new MediaRecorder(String16(opPackageNameStr.c_str()));
    if (mr == NULL) {
        jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
        return;
    }
    if (mr->initCheck() != NO_ERROR) {
        jniThrowException(env, "java/lang/RuntimeException", "Unable to initialize media recorder");
        return;
    }

    // create new listener and give it to MediaRecorder
    sp<JNIMediaRecorderListener> listener = new JNIMediaRecorderListener(env, thiz, weak_this);
    mr->setListener(listener);

    // Convert client name jstring to String16
    const char16_t *rawClientName = reinterpret_cast<const char16_t*>(
        env->GetStringChars(packageName, NULL));
    jsize rawClientNameLen = env->GetStringLength(packageName);
    String16 clientName(rawClientName, rawClientNameLen);
    env->ReleaseStringChars(packageName,
                            reinterpret_cast<const jchar*>(rawClientName));

    // pass client package name for permissions tracking
    mr->setClientName(clientName);

    setMediaRecorder(env, thiz, mr);
}

    其中jobject、jstring类型参数都是JNI层的数据类型,Java层数据类型到JNI层就要转换为JNI层数据结构。包括基本数据类型引用数据类型

3.1 Java层至JNI层基本数据类型的转换

    从转换表中可以看出Java层基本数据类型转换到JNI层只需将数据类型前加个“j”即可(除了void类型)。

3.2 Java层至JNI层引用数据类型的转换

Java Native 签名(以;结尾)
所有对象 jobject L+classname +;
Class jclass Ljava/lang/Clas;
String jstring Ljava/lang/String;
Trowable jthrowable Ljava/lang/Throwable;
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

    以~/LineageOS/frameworks/base/media/java/android/media/
MediaRecorder.java中的native_setup方法为例:

private native final void native_setup(Object mediarecorder_this,
             String clientName, String opPackageName) throws IllegalStateException;

    对应在~/LineageOS/frameworks/base/media/jni/android_
media_MediaRecorder.cpp,查看函数android_media_MediaRecorder_native_setup:

static void
android_media_MediaRecorder_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
                                         jstring packageName, jstring opPackageName)
{
...省略n行...
}

    可以发现:Object类型转换为jobject类型,String类型转换为jstring类型。

3.3 方法签名

    进入到源码路径: vim ~/LineageOS/frameworks/base/media/jni/
android_media_MediaRecorder.cpp,在数组gMethods[]中可看到签名信息:

static const JNINativeMethod gMethods[] = {
    {"setCamera",            "(Landroid/hardware/Camera;)V",    (void *)android_media_MediaRecorder_setCamera},
    {"setVideoSource",       "(I)V",                            (void *)android_media_MediaRecorder_setVideoSource},
    {"setAudioSource",       "(I)V",                            (void *)android_media_MediaRecorder_setAudioSource},
    {"setOutputFormat",      "(I)V",                            (void *)android_media_MediaRecorder_setOutputFormat},
    {"setVideoEncoder",      "(I)V",                            (void *)android_media_MediaRecorder_setVideoEncoder},
    {"setAudioEncoder",      "(I)V",                            (void *)android_media_MediaRecorder_setAudioEncoder},
    {"setParameter",         "(Ljava/lang/String;)V",           (void *)android_media_MediaRecorder_setParameter},
    {"_setOutputFile",       "(Ljava/io/FileDescriptor;JJ)V",   (void *)android_media_MediaRecorder_setOutputFileFD},
    {"setVideoSize",         "(II)V",                           (void *)android_media_MediaRecorder_setVideoSize},
    {"setVideoFrameRate",    "(I)V",                            (void *)android_media_MediaRecorder_setVideoFrameRate},
    {"setMaxDuration",       "(I)V",                            (void *)android_media_MediaRecorder_setMaxDuration},
    {"setMaxFileSize",       "(J)V",                            (void *)android_media_MediaRecorder_setMaxFileSize},
    {"_prepare",             "()V",                             (void *)android_media_MediaRecorder_prepare},
    {"getSurface",           "()Landroid/view/Surface;",        (void *)android_media_MediaRecorder_getSurface},
    {"getMaxAmplitude",      "()I",                             (void *)android_media_MediaRecorder_native_getMaxAmplitude},
    {"start",                "()V",                             (void *)android_media_MediaRecorder_start},
    {"stop",                 "()V",                             (void *)android_media_MediaRecorder_stop},
    {"pause",                "()V",                             (void *)android_media_MediaRecorder_pause},
    {"resume",               "()V",                             (void *)android_media_MediaRecorder_resume},
    {"native_reset",         "()V",                             (void *)android_media_MediaRecorder_native_reset},
    {"release",              "()V",                             (void *)android_media_MediaRecorder_release},
    {"native_init",          "()V",                             (void *)android_media_MediaRecorder_native_init},
    {"native_setup",         "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V",
                                                                (void *)android_media_MediaRecorder_native_setup},
    {"native_finalize",      "()V",                             (void *)android_media_MediaRecorder_native_finalize},
    {"native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaRecorder_setInputSurface },
};

    简单地说,存在签名的原因就是Java语言的方法是可以重载的,重载的方法名字相同而参数不同,所以JNI仅通过方法名无法确定对应的是重载的哪个方法,必须要参数签名来辅助其关联。

4 JNI函数中的关键参数:JNIEnv 指针

    JNIEnv指针是Native世界中Java环境的代表,通过该指针Native世界就可以访问Java世界的代码进行操作了,其具有以下主要特点及作用:

  1. JNIEnv *只对创建它的线程有效,不能跨线程传递;
  2. 通过JNIEnv *可调用Java的方法;
  3. 通过JNIEnv *可操作Java中的变量和对象;

    进入到源码路径: vim ~/LineageOS/libnativehelper/include/nativehelper/jni.h,查看JNIEnv结构体的声明:

#if defined(__cplusplus)
//C++中JNIEnv的类型
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
//C中JNIEnv的类型
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif

    其中,JavaVM虚拟机在JNI层的代表,在一个虚拟机进程中只有一个JavaVM,该进程的所有线程都可以使用此JavaVM。

    其实追踪_JNIEnv结构体代码,发现其最终使用的还是JNINativeInterface结构体:

/*
 * C++ object wrapper.
 *
 * This is usually overlaid on a C struct whose first element is a
 * JNINativeInterface*.  We rely somewhat on compiler behavior.
 */
struct _JNIEnv {
    /* do not rename this; it does not seem to be entirely opaque */
    //这里调用的依然是JNINativeInterface结构体指针
    const struct JNINativeInterface* functions;

#if defined(__cplusplus)

    jint GetVersion()
    { return functions->GetVersion(this); }

    jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
        jsize bufLen)
    { return functions->DefineClass(this, name, loader, buf, bufLen); }

    jclass FindClass(const char* name)
    { return functions->FindClass(this, name); }
...省略n行...
    jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
    { return functions->GetFieldID(this, clazz, name, sig); }

    jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
    { return functions->GetMethodID(this, clazz, name, sig); }
}

    所以,无论是C或C++,JNIEnv的类型都使用的JNINativeInterface机构。

    继续在源码路径: vim ~/LineageOS/libnativehelper/include/nativehelper/jni.h,查看JNINativeInterface结构体的声明:

/*
 * Table of interface function pointers.
 */
struct JNINativeInterface {
    void*       reserved0;
    void*       reserved1;
    void*       reserved2;
    void*       reserved3;

    jint        (*GetVersion)(JNIEnv *);

    jclass      (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,
                        jsize);
    jclass      (*FindClass)(JNIEnv*, const char*);

    jmethodID   (*FromReflectedMethod)(JNIEnv*, jobject);
    jfieldID    (*FromReflectedField)(JNIEnv*, jobject);
    /* spec doesn't show jboolean parameter */
    jobject     (*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean);

    void        (*SetStaticLongField)(JNIEnv*, jclass, jfieldID, jlong);
    void        (*SetStaticFloatField)(JNIEnv*, jclass, jfieldID, jfloat);
    void        (*SetStaticDoubleField)(JNIEnv*, jclass, jfieldID, jdouble);

...省略n行...

    /* added in JNI 1.6 */
    jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject);
};

    其中包含了很多函数指针都与JNIEnv结构体中的函数指针对应,这些函数指针就是在编写JNI程序时所需调用的。通过这些函数指针,就能定位到虚拟机中的JNI函数表,从而实现JNI层在虚拟机中的函数调用,这样JNI层就可以调用Java世界代码。

4.1 _JNIEnv结构体中定义的函数返回值

    _JNIEnv结构体中定义的函数返回值有多种,包括:jobject、jboolean、jstring、jmethodID、jfieldID等,其中jmethodID、jfieldID分别用以代表Java中的成员方法和成员变量。

    jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
    { return functions->GetFieldID(this, clazz, name, sig); }

    jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
    { return functions->GetMethodID(this, clazz, name, sig); }
}

其中jclass代表Java类、name代表成员方法或成员变量的名字、sig为此方法和变量的签名。

    进入到源码路径: vim ~/LineageOS/frameworks/base/media/jni/
android_media_MediaRecorder.cpp中,查看android_media_MediaRecorder_
native_init函数

// This function gets some field IDs, which in turn causes class initialization.
// It is called from a static block in MediaRecorder, which won't run until the
// first time an instance of this class is used.
static void
android_media_MediaRecorder_native_init(JNIEnv *env)
{
    jclass clazz;
    //利用FindClass找到Java层的MediaRecorder的Class对象,赋予clazz。
    clazz = env->FindClass("android/media/MediaRecorder");
    if (clazz == NULL) {
        return;
    }
    //利用GetFieldID找到Java层MediaRecorder中名为mNativeContext的成员变量
    fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
    if (fields.context == NULL) {
        return;
    }
    //利用GetFieldID找到Java层MediaRecorder中名为mSurface的成员变量
    fields.surface = env->GetFieldID(clazz, "mSurface", "Landroid/view/Surface;");
    if (fields.surface == NULL) {
        return;
    }

    jclass surface = env->FindClass("android/view/Surface");
    if (surface == NULL) {
        return;
    }
    //利用GetStaticMethodID找到Java层MediaRecorder中名为postEventFromNative的静态方法,赋予fields.post_event
    fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
                                               "(Ljava/lang/Object;IIILjava/lang/Object;)V");
    if (fields.post_event == NULL) {
        return;
    }

    这里面的field是fields_t类型的结构体,在MediaRecorder框架的JNI层android_media_MediaRecorder_native_init函数将jfieldID和jmethodID类型的变量保存下来,已被后面的调用高效利用。

4.2 jmethodID、jfieldID使用方法

看源码…

5 JNI中的引用类型(本地引用、全局引用、弱全局引用)

待补充…

Enjoy it !! _

发布了48 篇原创文章 · 获赞 5 · 访问量 7811

猜你喜欢

转载自blog.csdn.net/Xiaoma_Pedro/article/details/103892732
今日推荐