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

  • Android框架层
  • Android深入理解JNI
  • Android框架层

本文首发于微信公众号「刘望舒」

前言

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

<!--more-->

1.JNI概述

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

2.MediaRecorder框架概述

MediaRecorder我们应该都不陌生,它用于录音和录像。这里不会主要介绍MediaRecorder框架,而是MediaRecorder框架中的JNI。 VmSY7T.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

1public class MediaRecorder{ 2static { 3 System.loadLibrary("media_jni");//1 4 native_init();//2 5 } 6... 7 private static native final void native_init();//3 8... 9 public native void start() throws IllegalStateException; 10... 11}

在静态代码块中首先调用了注释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

1static void 2android_media_MediaRecorder_native_init(JNIEnv *env) 3{ 4 jclass clazz; 5 6 clazz = env->FindClass("android/media/MediaRecorder"); 7 if (clazz == NULL) { 8 return; 9 } 10 ... 11 fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative", 12 "(Ljava/lang/Object;IIILjava/lang/Object;)V"); 13 if (fields.post_event == NULL) { 14 return; 15 } 16} 17 18static void 19android_media_MediaRecorder_start(JNIEnv *env, jobject thiz) 20{ 21 ALOGV("start"); 22 sp<MediaRecorder> mr = getMediaRecorder(env, thiz); 23 process_media_recorder_call(env, mr->start(), "java/lang/RuntimeException", "start failed."); 24}

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,如下所示。

1package com.example; 2public class MediaRecorder { 3 static { 4 System.loadLibrary("media_jni"); 5 native_init(); 6 } 7 8 private static native final void native_init(); 9 public native void start() throws IllegalStateException; 10}

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

1javac com/example/MediaRecorder.java 2javah com.example.MediaRecorder

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

1/* DO NOT EDIT THIS FILE - it is machine generated */ 2#include <jni.h> 3/* Header for class com_example_MediaRecorder */ 4 5#ifndef _Included_com_example_MediaRecorder 6#define _Included_com_example_MediaRecorder 7#ifdef __cplusplus 8extern "C" { 9#endif 10/* 11 * Class: com_example_MediaRecorder 12 * Method: native_init 13 * Signature: ()V 14 */ 15JNIEXPORT void JNICALL Java_com_example_MediaRecorder_native_1init 16 (JNIEnv *, jclass);//1 17 18/* 19 * Class: com_example_MediaRecorder 20 * Method: start 21 * Signature: ()V 22 */ 23JNIEXPORT void JNICALL Java_com_example_MediaRecorder_start 24 (JNIEnv *, jobject); 25 26#ifdef __cplusplus 27} 28#endif 29#endif 30

nativeinit方法被声明为注释1处的方法,也就是Java_com_example_MediaRecorder_native_1init,"Java_" 开头说明是在Java平台中调用JNI方法,后面的com_example_MediaRecorder_native_1init指的是包名+类名+方法名的格式,原本在Java中应该是以"."来进行分割,这里却用了"",这是因为在Native语言中"."有特殊的含义。还有眼尖的同学发现了注释1处的方法名还多了一个“1”,这是因为Java的native_init方法包含了"",转换成JNI方法后会变成"_1"。

其中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中被定义:

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

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

1static const JNINativeMethod gMethods[] = { 2... 3 {"start", "()V", (void *)android_media_MediaRecorder_start},//1 4 {"stop", "()V", (void *)android_media_MediaRecorder_stop}, 5 {"pause", "()V", (void *)android_media_MediaRecorder_pause}, 6 {"resume", "()V", (void *)android_media_MediaRecorder_resume}, 7 {"native_reset", "()V", (void *)android_media_MediaRecorder_native_reset}, 8 {"release", "()V", (void *)android_media_MediaRecorder_release}, 9 {"native_init", "()V", (void *)android_media_MediaRecorder_native_init}, 10 ... 11};

上面定义了一个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

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

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

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

registerNativeMethods方法中又return了jniRegisterNativeMethods方法: libnativehelper/JNIHelp.cpp

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

从注释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

1jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) 2{ 3 JNIEnv* env = NULL; 4 jint result = -1; 5 if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { 6 ALOGE("ERROR: GetEnv failed\n"); 7 goto *bail; 8 } 9 assert(env != NULL); 10 ... 11 if (register_android_media_MediaPlayer(env) < 0) { 12 ALOGE("ERROR: MediaPlayer native registration failed\n"); 13 goto *bail; 14 } 15 if (register_android_media_MediaRecorder(env) < 0) {//1 16 ALOGE("ERROR: MediaRecorder native registration failed\n"); 17 goto *bail; 18 } 19 ... 20 result = JNI_VERSION_1_4; 21bail: 22 return result; 23}

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

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

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

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Android深入理解JNI(二)类型转换、方法签名和JNIEnv

Android框架层Android深入理解JNIAndroid框架层本文首发于微信公众号「刘望舒」前言上一篇文章介绍了JNI的基本原理和注册,这一篇接着带领大家来学习JNI的数据类型转换、方法签名和JNIEnv。<!more1.数据类型的转换首先给出上一篇文章中androidmediaMediaRecorder.cpp中的androidmediaMe

Android深入理解Context(一)Context关联类和Application Context创建过程

Android框架层Android深入理解Contextcategories:Android框架层本文首发于微信公众号「刘望舒」前言Context也就是上下文对象,是Android较为常用的类,但是对于Context,很多人都停留在会用的阶段,这个系列会带大家从源码角度来分析Context,从而更加深入的理解它。<!more1.Context概述Co