关闭

Android深入理解JNI(一)JNI原理与静态、动态注册

标签: jnijni静态注册jni动态注册jni原理
3921人阅读 评论(0) 收藏 举报
分类:

前言

JNI不仅仅在NDK开发中应用,它更是Android系统中Java与Native交互的桥梁,不理解JNI的话,你就只能停留在Java Framework层。这一个系列我们来一起深入学习JNI。

1.JNI概述

Android系统按语言来划分的话由两个世界组成,分别是Java世界和Native世界。那为什么要这么划分呢?Android系统由Java写不好吗?除了性能的之外,最主要的原因就是在Java诞生之前,就有很多程序和库都是由Native语言写的,因此,重复利用这些Native语言编写的库是十分必要的,况且Native语言编写的库具有更好的性能。
这样就产生了一个问题,Java世界的代码要怎么使用Native世界的代码呢,这就需要一个桥梁来将它们连接在一起,而JNI就是这个桥梁。
未命名文件(5).png
通过JNI,Java世界的代码就可以访问Native世界的代码,同样的,Native世界的代码也可以访问Java世界的代码。
为了讲解JNI我们需要分析系统的源码,在即将出版的《Android进阶之光》的最后一章中我拿MediaPlayer框架做了举例,这里换MediaRecorder框架来举例,它和MediaPlayer框架的调用过程十分类似。

2.MediaRecorder框架概述

MediaRecorder我们应该都不陌生,它用于录音和录像。这里不会主要介绍MediaRecorder框架,而是MediaRecorder框架中的JNI。
未命名文件(6).png
Java世界对应的是MediaRecorder.java,也就是我们应用开发中直接调用的类。JNI层对用的是libmedia_jni.so,它是一个JNI的动态库。Native层对应的是libmedia.so,这个动态库完成了实际的调用的功能。

3.Java层的MediaRecorder

我们先来查看MediaRecorder.java的源码,截取部分和JNI有关的部分如下所示。
frameworks/base/media/java/android/media/MediaRecorder.java

public class MediaRecorder{
static {
        System.loadLibrary("media_jni");//1
        native_init();//2
    }
...   
    private static native final void native_init();//3
...
    public native void start() throws IllegalStateException;
...    
}

在静态代码块中首先调用了注释1处的代码,用来加载名为“media_jni“的动态库,也就是libmedia_jni.so。接着调用注释2处的native_init方法,注释3处的native_init方法用native来修饰,说明它是一个native方法,表示由JNI来实现。MediaRecorder的start方法同样也是一个native方法。
对于Java层来说只需要加载对应的JNI库,接着声明native方法就可以了,剩下的工作由JNI层来完成。

4.JNI层的MediaRecorder

MediaRecorder的JNI层由android_media_recorder.cpp实现,native方法native_init和start的JNI层实现如下所示。
frameworks/base/media/jni/android_media_MediaRecorder.cpp

static void
android_media_MediaRecorder_native_init(JNIEnv *env)
{
    jclass clazz;

    clazz = env->FindClass("android/media/MediaRecorder");
    if (clazz == NULL) {
        return;
    }
   ...
    fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
                                               "(Ljava/lang/Object;IIILjava/lang/Object;)V");
    if (fields.post_event == NULL) {
        return;
    }
}

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.");
}

android_media_MediaRecorder_native_init方法是native_init方法在JNI层的实现,android_media_MediaRecorder_start方法则是start方法在JNI层的实现。那么,native_init方法是如何找到对应的android_media_MediaRecorder_native_init方法的呢?
这就需要了解JNI方法注册的知识。

5.JNI方法注册

JNI方法注册分为静态注册和动态注册,其中静态注册多用于NDK开发,而动态注册多用于Framework开发。

静态注册

在AS中新建一个Java Library名为media,这里仿照系统的MediaRecorder.java,写一个简单的MediaRecorder.java,如下所示。

package com.example;
public class MediaRecorder {
    static {
        System.loadLibrary("media_jni");
        native_init();
    }

    private static native final void native_init();
    public native void start() throws IllegalStateException;
}

接着进入项目的media/src/main/java目录中执行如下命令:

javac com.example.MediaRecorder.java
javah com.example.MediaRecorder

第二个命令会在当前目录中(media/src/main/java)生成com_example_MediaRecorder.h文件,如下所示。

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

#ifndef _Included_com_example_MediaRecorder
#define _Included_com_example_MediaRecorder
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_MediaRecorder
 * Method:    native_init
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_MediaRecorder_native_1init
  (JNIEnv *, jclass);//1

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

#ifdef __cplusplus
}
#endif
#endif

native_init方法被声明为注释1处的方法,格式为Java_包名_类名_方法名,注释1处的方法名多了一个“l”,这是因为native_init方法有一个“_”,它会在转换为JNI方法时变成“_l”。
其中JNIEnv * 是一个指向全部JNI方法的指针,该指针只在创建它的线程有效,不能跨线程传递。
jclass是JNI的数据类型,对应Java的java.lang.Class实例。jobject同样也是JNI的数据类型,对应于Java的Object。关于JNIEnv * 以及JNI的数据类型会在本系列的后续文章中进行介绍。

当我们在Java中调用native_init方法时,就会从JNI中寻找Java_com_example_MediaRecorder_native_1init方法,如果没有就会报错,如果找到就会为native_init和Java_com_example_MediaRecorder_native_1init建立关联,其实是保存JNI的方法指针,这样再次调用native_init方法时就会直接使用这个方法指针就可以了。
静态注册就是根据方法名,将Java方法和JNI方法建立关联,但是它有一些缺点:

  • JNI层的方法名称过长。
  • 声明Native方法的类需要用javah生成头文件。
  • 初次调用JIN方法时需要建立关联,影响效率。

我们知道,静态注册就是Java的Native方法通过方法指针来与JNI进行关联的,如果Native方法知道它在JNI中对应的方法指针,就可以避免上述的缺点,这就是动态注册。

动态注册

JNI中有一种结构用来记录Java的Native方法和JNI方法的关联关系,它就是JNINativeMethod,它在jni.h中被定义:

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

系统的MediaRecorder采用的就是动态注册,我们来查看它的JNI层是怎么做的。
frameworks/base/media/jni/android_media_MediaRecorder.cpp

static const JNINativeMethod gMethods[] = {
...
    {"start",            "()V",      (void *)android_media_MediaRecorder_start},//1
    {"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},
   ...
};

上面定义了一个JNINativeMethod类型的gMethods数组,里面存储的就是MediaRecorder的Native方法与JNI层方法的对应关系,其中注释1处”start”是Java层的Native方法,它对应的JNI层的方法为android_media_MediaRecorder_start。”()V”是start方法的签名信息,关于Java方法的签名信息后续的文章会介绍。
只定义JNINativeMethod 类型的数组是没有用的,还需要注册它,注册的方法为register_android_media_MediaRecorder:
frameworks/base/media/jni/android_media_MediaRecorder.cpp

//JNI_OnLoad in android_media_MediaPlayer.cpp
int register_android_media_MediaRecorder(JNIEnv *env)
{
    return AndroidRuntime::registerNativeMethods(env,
                "android/media/MediaRecorder", gMethods, NELEM(gMethods));
}

register_android_media_MediaRecorder方法中return了AndroidRuntime的registerNativeMethods方法,如下所示。
frameworks/base/core/jni/AndroidRuntime.cpp

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

registerNativeMethods方法中又return了jniRegisterNativeMethods方法:
external/conscrypt/src/openjdk/native/JNIHelp.cpp

extern "C" int jniRegisterNativeMethods(JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
   ...
    if (env->RegisterNatives(c.get(), gMethods, numMethods) < 0) {//1
        char* msg;
        (void)asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className);
        env->FatalError(msg);
    }
    return 0;
}

从注释1处可以看出,最终调用的JNIEnv的RegisterNatives方法,JNIEnv在JNI中十分重要,后续文章会介绍它。

register_android_media_MediaRecorder方法最终会调用JNIEnv的RegisterNatives方法,但是register_android_media_MediaRecorder方法是在哪被调用的呢?答案在register_android_media_MediaRecorder方法的注释上:JNI_OnLoad in android_media_MediaPlayer.cpp。这个JNI_OnLoad方法会在System.loadLibrary方法后调用,因为多媒体框架中的很多框架都要进行JNINativeMethod数组注册,因此,注册方法就被统一定义在android_media_MediaPlayer.cpp中的JNI_OnLoad方法中,如下所示。
frameworks/base/media/jni/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;
    }
    if (register_android_media_MediaRecorder(env) < 0) {//1
        ALOGE("ERROR: MediaRecorder native registration failed\n");
        goto *bail;
    }
  ...
   result = JNI_VERSION_1_4;
bail:
    return result;
}

在JNI_OnLoad方法中调用了整个多媒体框架的注册JNINativeMethod数组的方法,注释1处的调用了register_android_media_MediaRecorder方法,同样的,MediaPlayer框架的注册JNINativeMethod数组的方法register_android_media_MediaPlayer也被调用了。

关于动态注册就讲到这里,更多深入JNI的知识请见本系列后续的文章。

参考资料
《深入理解Android卷I》


欢迎关注我的微信公众号,第一时间获得博客更新提醒,以及更多成体系的Android相关原创技术干货。
扫一扫下方二维码或者长按识别二维码,即可关注。

2
0
查看评论

Java中JNI的使用详解第一篇:HelloWorld

今天开始研究JNI技术,首先还是老套路,输出一个HelloWorld:具体流程如下:在Java中定义一个方法,在C++中实现这个方法,在方法内部输出“Hello World",然后再回到Java中进行调用。分为以下步骤: 第一步:在Eclipse中建立一个类:JNIDemo package...
  • jiangwei0910410003
  • jiangwei0910410003
  • 2013-12-21 13:33
  • 81417

JNI学习之步步深入一

      从接触Android的一段时间后,就经常听到JNI这个东东,刚开始,不知是由于对新事物的排斥,还是根本不把它当成一回事,就没有特地地去理他。只是在活跃的社区中零零散散地看到JNI的身影,大致知道所谓的JNI,就是一种不纯的JAVA编程技术...
  • chenjie19891104
  • chenjie19891104
  • 2011-05-11 17:14
  • 10999

JNI 实战全面解析

项目决定移植一款C++开源项目到Android平台,开始对JNI深入研究。 JNI是什么? JNI(Java Native Interface)意为JAVA本地调用,它允许Java代码和其他语言写的代码进行交互,简单的说,一种在Java虚拟机控制下执行代码的标准机制。 NDK是什么? Android...
  • banketree
  • banketree
  • 2014-11-01 09:05
  • 81147

Java中JNI的使用详解第二篇:JNIEnv类型和jobject类型的解释

上一篇说的是一个简单的应用,说明JNI是怎么工作的,这一篇主要来说一下,那个本地方法sayHello的参数的说明,以及其中方法的使用 首先来看一下C++中的sayHello方法的实现: JNIEXPORT void JNICALL Java_com_jni_demo_JNIDemo_sayHello...
  • jiangwei0910410003
  • jiangwei0910410003
  • 2013-12-21 14:33
  • 34460

Android:JNI 与 NDK到底是什么?(含实例教学)

前言 在Android开发中,使用 NDK开发的需求正逐渐增大 但很多人却搞不懂 JNI 与 NDK 到底是怎么回事 今天,我将先介绍JNI 与 NDK & 之间的区别,手把手进行 NDK的使用教学,希望你们会喜欢 目录1. JNI介绍1.1 简介 定义:Java Native Inter...
  • carson_ho
  • carson_ho
  • 2017-06-14 17:03
  • 17545

深入理解JNI

深入理解JNI最近在学习android底层的一些东西,看了一些大神的博客,整体上有了一点把握,也产生了很多疑惑,于是再次把邓大神的深入系列翻出来仔细看看,下面主要是一些阅读笔记。JNI概述JNI是Java Native Interface的缩写 ,通常称为“Java本地调用”,通过这种技术可以做到:...
  • XSF50717
  • XSF50717
  • 2016-06-06 22:27
  • 10992

Android下玩JNI的新老三种姿势

说明:本篇不撸代码,只玩编译,其包含了Android studio 2.2最新的JNI玩法 编译环境:macOS 10.12.3 工具包含:Android Studio 2.2 NDK-r14 在Android下要玩jni首先下载ndk是必须的,可以直接去https://developer....
  • mabeijianxi
  • mabeijianxi
  • 2017-03-30 21:55
  • 6884

JNI详解(一)

1、什么是JNI JNI是java语言的特性,它允许Java类的方法被C/C++实现。 2、JNIEnv指针 每个实现java原生方法的C/C++函数必须传入一个JNIEnv指针,C/C++通过JNIEnv提供的各种内置函数来使用JVM的功能。 注意:传入的JNIEnv指针只在对应java原生方法被...
  • YongYu_IT
  • YongYu_IT
  • 2016-09-08 17:27
  • 916

JNI使用方法

Java通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在Windows平台下是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使Java可以实现和本地机器的紧密联系,调用系统级的各接口方法 使用的简单流程: 一、Java中所需要做的工作 1、在Ja...
  • kai_wei_zhang
  • kai_wei_zhang
  • 2012-10-27 16:41
  • 7303

NDK 与 JNI 的关系

简介  JNI是java语言提供的Java和C/C++相互沟通的机制,Java可以通过JNI调用本地的C/C++代码,本地的C/C++的代码也可以调用java代码。JNI 是本地编程接口,Java和C/C++互相通过的接口。Java通过C/C++使用本地的代码的一个关键性原因在于C/C+...
  • banketree
  • banketree
  • 2013-10-09 11:50
  • 36386
    我的新书
    个人资料
    • 访问:1922662次
    • 积分:16703
    • 等级:
    • 排名:第729名
    • 原创:191篇
    • 转载:52篇
    • 译文:0篇
    • 评论:1022条
    我的微博
    最新评论
    公众号
    百度统计