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)系统源码并编译、刷机

1 JNI的原理是什么?

    JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性,但是同时JNI会提升程序的性能。

    通常情况下,下面几种情况下才会使用JNI技术:

  1. 需要调用Java语言不支持但依赖于操作系统平特性的一些功能。例如,需要调用当前Linux系统的某功能而Java却不支持。
  2. 整合以前以非Java语言开发的系统,毕竟重复利用这些Native语言库也符合代码复用的编程原则。例如,要用到早期实现的C/C++语言开发的一些功能或系统,将这些功能或系统整合到新的系统或版本中。
  3. 节省运行时间,提高运行效率(需借用C/C++)。例如,游戏、音视频开发涉及到图像、音频解码这种要求效率的功能。

    JNI在Android里的使用很广泛,主要是音视频开发、热修复和插件化、逆向开发、系统源码调用等。涉及到的工具那就是NDK(Native Development Kit)了,这个大家一定是听过的,后面在写一篇关于NDK的文章,现在先不对其做详细介绍了。
    本文侧重在系统源码中对JNI原理进行分析,就现实来看如果你是一个Android APP开发者,不理解JNI原理的话,视野会局限在Framework层;热修复和插件化原理需要JNI的知识储备;

                                

    既然要分析Android源码中的JNI,那就选择使用cm-14.1中的MediaRecorder框架作为例子。

2 MediaRecorder框架里的JNI

    MediaRecorder在Framework中用于录音/录像,其中Java-JNI-Native三层的代码对应关系如下图所示:

                             

2.1 Framework层中的MediaRecorder.java

    进入到源码路径:vim ~/LineageOS/frameworks/base/media/java/android/media/MediaRecorder.java

    截取一段JNI相关的代码:

public class MediaRecorder
{
    //对于Framework层,只要其类中加载了对应的JNI库,在其类中直接声明native方法即可,其余工作会由#JNI层#完成
    static {
        System.loadLibrary("media_jni");//用来加载名为libmedia_jni.so的动态库。
        native_init();//调用Native方法完成JNI的注册。
    }
    private final static String TAG = "MediaRecorder";

...省略n行代码...
    public native void start() throws IllegalStateException;//native开头的方法代表其为Java类中的一个native方法。

...省略n行代码...
    private static native final void native_init();

2.2 JNI层中的android_media_MediaRecorder.cpp

    进入到源码路径:vim ~/LineageOS/frameworks/base/media/jni/android_media_MediaRecorder.cpp

    上一节2.1所述的MediaRecorder.java中的native方法native_init()、start()的JNI层实现就在这里了:

//MediaRecorder.java中的native方法native_init()在JNI层的实现
// 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;

    clazz = env->FindClass("android/media/MediaRecorder");
    if (clazz == NULL) {
        return;
    }

    fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
    if (fields.context == NULL) {
        return;
    }

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

    fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
                                               "(Ljava/lang/Object;IIILjava/lang/Object;)V");
    if (fields.post_event == NULL) {
        return;
    }
}
//MediaRecorder.java中的native方法start()在JNI层的实现
static void
android_media_MediaRecorder_start(JNIEnv *env, jobject thiz)
{
    ALOGV("start");
    sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
    process_media_recorder_call(env, mr->start(), "java/lang/RuntimeException", "start failed.");
}

    进入到源码路径:vim ~/LineageOS/frameworks/base/media/jni/Android.mk

    就能发现这个android_media_MediaRecorder.cpp和同目录下的其它cpp文件共同编译出了动态库libmedia_jni.so

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES:= \
    android_media_AmrInputStream.cpp \
    android_media_ExifInterface.cpp \
    android_media_ImageWriter.cpp \
    android_media_ImageReader.cpp \
    android_media_MediaCrypto.cpp \
    android_media_MediaCodec.cpp \
    android_media_MediaCodecList.cpp \
    android_media_MediaDataSource.cpp \
    android_media_MediaDrm.cpp \
    android_media_MediaExtractor.cpp \
    android_media_MediaHTTPConnection.cpp \
    android_media_MediaMetadataRetriever.cpp \
    android_media_MediaMuxer.cpp \
    android_media_MediaPlayer.cpp \
    android_media_MediaProfiles.cpp \
    android_media_MediaRecorder.cpp \
    android_media_MediaScanner.cpp \
    android_media_MediaSync.cpp \
    android_media_ResampleInputStream.cpp \
    android_media_SyncParams.cpp \
    android_media_Utils.cpp \
    android_mtp_MtpDatabase.cpp \
    android_mtp_MtpDevice.cpp \
    android_mtp_MtpServer.cpp \

LOCAL_SHARED_LIBRARIES := \
    libandroid_runtime \
    libnativehelper \
    libutils \
    libbinder \
    libmedia \
    libmediadrm \
    libskia \
    libui \
    liblog \
    libcutils \
    libgui \
    libstagefright \
    libstagefright_foundation \
    libcamera_client \
    libmtp \
    libusbhost \
    libexif \
    libpiex \
    libstagefright_amrnb_common

LOCAL_STATIC_LIBRARIES := \
    libstagefright_amrnbenc

LOCAL_C_INCLUDES += \
    external/libexif/ \
    external/piex/ \
    external/tremor/Tremor \
    frameworks/base/core/jni \
    frameworks/base/libs/hwui \
    frameworks/av/media/libmedia \
    frameworks/av/media/libstagefright \
    frameworks/av/media/libstagefright/codecs/amrnb/enc/src \
    frameworks/av/media/libstagefright/codecs/amrnb/common \
    frameworks/av/media/libstagefright/codecs/amrnb/common/include \
    frameworks/av/media/mtp \
    frameworks/native/include/media/openmax \
    $(call include-path-for, libhardware)/hardware \
    $(PV_INCLUDES) \
    $(JNI_H_INCLUDE)

LOCAL_CFLAGS += -Wall -Werror -Wno-error=deprecated-declarations -Wunused -Wunreachable-code

#在这里编译出libmedia_jni.so
LOCAL_MODULE:= libmedia_jni

include $(BUILD_SHARED_LIBRARY)

# build libsoundpool.so
# build libaudioeffect_jni.so
include $(call all-makefiles-under,$(LOCAL_PATH))

2.3 Native方法的注册

    上一节遗留了个疑问,native_init方法是如何找对自己所对应的android_media_MediaRecorder_native_init方法呢?这就涉及到了本节所述JNI方法的注册问题了。

    Native方法的注册分为静态注册(常用于应用侧的NDK开发)和动态注册(常用于系统中Framework开发),

2.3.1 Native静态注册

    1. 在Android Studio中创建一个Empty Project,参照MediaRecorder.java代码写一个简单的demo:

          

    2. 利用Android Studio自带的Terminal,进入到我的项目路径F:\Android_Projects\booksource-master\chapter2\MyApplication3\app\src\main\java中输入下面命令:

"C:\Program Files\Java\jdk1.7.0_80\bin\javac.exe" com\example\myapplication\MediaRecorder.java

    将会生成MediaRecorder.class文件,如下图所示:

          

    3. 继续执行下面命令:

"C:\Program Files\Java\jdk1.7.0_80\bin\javah.exe" com.example.myapplication.MediaRecorder

    将会生成com_example_myapplication_MediaRecorder.h文件,如下图所示:

          

    生成的com_example_myapplication_MediaRecorder.h文件内容为:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_myapplication_MediaRecorder */

#ifndef _Included_com_example_myapplication_MediaRecorder
#define _Included_com_example_myapplication_MediaRecorder
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_myapplication_MediaRecorder
 * Method:    native_init
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_myapplication_MediaRecorder_native_1init
  (JNIEnv *, jclass);

/*
 * Class:     com_example_myapplication_MediaRecorder
 * Method:    start
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_myapplication_MediaRecorder_start
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

    可以看出来nativeinit方法声明为以Java_com_example_myapplication_
MediaRecorder_native_1init命名的方法:

    当在Java中调用init_native方法时,Java虚拟机就会去JNI中寻找Java_com_example_myapplication_MediaRecorder_native_1init函数,如果没找到会报错,如果找到了就会为这两者建立关联(通过保存JNI的函数指针,这样在下次调用native_init方法时直接使用这个函数指针即可)

    所以,静态注册的要义所在就是通过方法名将Java方法和JNI函数建立关联。但是虽然静态注册操作简单,适合在Android Studio中写APP时使用,却是有明显缺点的:


  1. JNI层的函数名太长;
  2. 声明Native方法的类需要用javah工具生成头文件;
  3. 第一次调用Native方法需要建立关联,影响运行效率;

    那么问题来了,怎样克服这些缺点呢???就是下面的动态注册方式 !

2.3.2 动态注册

    在Android系统jni.h中,有专门的数据结构JNINativeMethod用来记录Java的Native方法和JNI函数的关联关系。

    进入到源码路径:vim ~/LineageOS/libnativehelper/
include/nativehelper/jni.h

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

    依然以MediaRecorder为例,其采用的注册方式就是动态注册。进入到源码路径:
vim ~/LineageOS/frameworks/base/media/jni/android_media_MediaRecorder.cpp

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 },
};

    该gMethods[]数组中存放的就是MediaRecorder的Native方法与JNI层函数的对应关系。依然以native_init为例:

{"native_init",          "()V",                             (void *)android_media_MediaRecorder_native_init},
  • “native_init” 代表Java层的Native方法;
  • “()V” 代表native_initve_init方法的签名信息;
  • (void *)android_media_MediaRecorder_native_init 代表对应JNI层函数;

    注册函数register_android_media_MediaRecorder的功能就是gMethods[]数组中Java方法和JNI函数的对应关系动态注册到系统中。

// This function only registers the native methods, and is called from
// JNI_OnLoad in android_media_MediaPlayer.cpp
int register_android_media_MediaRecorder(JNIEnv *env)
{
    return AndroidRuntime::registerNativeMethods(env,
                "android/media/MediaRecorder", gMethods, NELEM(gMethods));
}

    注释中说在android_media_MediaPlayer.cpp的JNI_OnLoad函数中会调用register_android_media_MediaRecorder函数,那就先到android_media_MediaPlayer.cpp中去看一下这个JNI_OnLoad函数:

    进入到源码路径: vim ~/LineageOS/frameworks/base/media/jni/
android_media_MediaPlayer.cpp

extern int register_android_media_ExifInterface(JNIEnv *env);
extern int register_android_media_ImageReader(JNIEnv *env);
extern int register_android_media_ImageWriter(JNIEnv *env);
extern int register_android_media_Crypto(JNIEnv *env);
extern int register_android_media_Drm(JNIEnv *env);
extern int register_android_media_MediaCodec(JNIEnv *env);
extern int register_android_media_MediaExtractor(JNIEnv *env);
extern int register_android_media_MediaCodecList(JNIEnv *env);
extern int register_android_media_MediaHTTPConnection(JNIEnv *env);
extern int register_android_media_MediaMetadataRetriever(JNIEnv *env);
extern int register_android_media_MediaMuxer(JNIEnv *env);
extern int register_android_media_MediaRecorder(JNIEnv *env);
extern int register_android_media_MediaScanner(JNIEnv *env);
extern int register_android_media_MediaSync(JNIEnv *env);
extern int register_android_media_ResampleInputStream(JNIEnv *env);
extern int register_android_media_MediaProfiles(JNIEnv *env);
extern int register_android_media_AmrInputStream(JNIEnv *env);
extern int register_android_mtp_MtpDatabase(JNIEnv *env);
extern int register_android_mtp_MtpDevice(JNIEnv *env);
extern int register_android_mtp_MtpServer(JNIEnv *env);


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_ImageWriter(env) != JNI_OK) {
        ALOGE("ERROR: ImageWriter native registration failed");
        goto bail;
    }

    if (register_android_media_ImageReader(env) < 0) {
        ALOGE("ERROR: ImageReader native registration failed");
        goto bail;
    }

    if (register_android_media_MediaPlayer(env) < 0) {
        ALOGE("ERROR: MediaPlayer native registration failed\n");
        goto bail;
    }

    if (register_android_media_MediaRecorder(env) < 0) {
        ALOGE("ERROR: MediaRecorder native registration failed\n");
        goto bail;
    }

    if (register_android_media_MediaScanner(env) < 0) {
        ALOGE("ERROR: MediaScanner native registration failed\n");
        goto bail;
    }

    if (register_android_media_MediaMetadataRetriever(env) < 0) {
        ALOGE("ERROR: MediaMetadataRetriever native registration failed\n");
        goto bail;
    }

    if (register_android_media_AmrInputStream(env) < 0) {
        ALOGE("ERROR: AmrInputStream native registration failed\n");
        goto bail;
    }

    if (register_android_media_ResampleInputStream(env) < 0) {
        ALOGE("ERROR: ResampleInputStream native registration failed\n");
        goto bail;

    if (register_android_media_MediaProfiles(env) < 0) {
        ALOGE("ERROR: MediaProfiles native registration failed");
        goto bail;
    }

    if (register_android_mtp_MtpDatabase(env) < 0) {
        ALOGE("ERROR: MtpDatabase native registration failed");
        goto bail;
    }

    if (register_android_mtp_MtpDevice(env) < 0) {
        ALOGE("ERROR: MtpDevice native registration failed");
        goto bail;
    }

    if (register_android_mtp_MtpServer(env) < 0) {
        ALOGE("ERROR: MtpServer native registration failed");
        goto bail;
    }

    if (register_android_media_MediaCodec(env) < 0) {
        ALOGE("ERROR: MediaCodec native registration failed");
        goto bail;
    }

    if (register_android_media_MediaSync(env) < 0) {
        ALOGE("ERROR: MediaSync native registration failed");
        goto bail;
    }

    if (register_android_media_MediaExtractor(env) < 0) {
        ALOGE("ERROR: MediaCodec native registration failed");
        goto bail;
    }

    if (register_android_media_MediaMuxer(env) < 0) {
        ALOGE("ERROR: MediaMuxer native registration failed");
        goto bail;
    }

    if (register_android_media_MediaCodecList(env) < 0) {
        ALOGE("ERROR: MediaCodec native registration failed");
        goto bail;
    }

    if (register_android_media_Crypto(env) < 0) {
        ALOGE("ERROR: MediaCodec native registration failed");
        goto bail;
    }

    if (register_android_media_Drm(env) < 0) {
        ALOGE("ERROR: MediaDrm native registration failed");
        goto bail;
    }

    if (register_android_media_MediaHTTPConnection(env) < 0) {
        ALOGE("ERROR: MediaHTTPConnection native registration failed");
        goto bail;
    }

    if (register_android_media_ExifInterface(env) < 0) {
        ALOGE("ERROR: ExifInterface native registration failed");
        goto bail;
    }

    /* success -- return valid version number */
    result = JNI_VERSION_1_4;

bail:
    return result;
}

    可以发现多媒体框架中有很多框架都需要进行JNINativeMethod数组的注册,所以,统一在android_media_MediaPlayer.cpp中的JNI_OnLoad函数中,而JNI_OnLoad的调用会在System.loadLibrary函数调用之后被调用

    追踪一下register_android_media_MediaRecorder函数实际调用的是~/LineageOS/frameworks/base/core/jni/AndroidRuntime.cpp中的AndroidRuntime::registerNativeMethods函数:

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

    AndroidRuntime::registerNativeMethods函数中又调用了帮助类~/LineageOS/libnativehelper/JNIHelp.cpp中的jniRegisterNativeMethods函数。

extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);

    ALOGV("Registering %s's %d native methods...", className, numMethods);

    scoped_local_ref<jclass> 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);
    }
//最终调用了JNIEnv的RegisterNatives函数完成JNI的注册!!!
    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;

    JNI的动态注册最终调用了JNIEnv的RegisterNatives函数去完成JNI的动态注册!!!

避免博客拖太长,后续参见下篇《[日更-2019.4.7]Android系统的JNI原理分析(二)》

Enjoy it !!

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

猜你喜欢

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