前言

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

1.JNI概述

Android系统按语言来划分的话由两个世界组成,分别是Java世界和Native世界。那为什么要这么划分呢?Android系统由Java写不好吗?除了性能的之外,最主要的原因就是在Java诞生之前,就有很多程序和库都是由Native语言写的,因此,重复利用这些Native语言编写的库是十分必要的,况且Native语言编写的库具有更好的性能。
这样就产生了一个问题,Java世界的代码要怎么使用Native世界的代码呢,这就需要一个桥梁来将它们连接在一起,而JNI就是这个桥梁。

通过JNI,Java世界的代码就可以访问Native世界的代码,同样的,Native世界的代码也可以访问Java世界的代码。
为了讲解JNI我们需要分析系统的源码,在即将出版的《Android进阶之光》的最后一章中我拿MediaPlayer框架做了举例,这里换MediaRecorder框架来举例,它和MediaPlayer框架的调用过程十分类似。

2.MediaRecorder框架概述

MediaRecorder我们应该都不陌生,它用于录音和录像。这里不会主要介绍MediaRecorder框架,而是MediaRecorder框架中的JNI。

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开发。

静态注册

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中对应的方法指针,就可以避免上述的缺点,这就是动态注册。

查看上一博文http://www.cnblogs.com/CZM-/p/7943572.html

动态注册

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

typedef struct {
const char* name;//Java方法的名字
const char* signature;//Java方法的签名信息
void* fnPtr;//JNI中对应的方法指针
} JNINativeMethod;
HardControl.c这是一个jni点灯的demo,函数名字不需要跟java类有关系省去了编译头文件的步骤
#if 0
typedef struct {
char *name; /* Java里调用的函数名 */
char *signature; /* JNI字段描述符, 用来表示Java里调用的函数的参数和返回值类型 */
void *fnPtr; /* C语言实现的本地函数 */
} JNINativeMethod;
#endif static jint fd;
#define DEVICE_NAME "/dev/leds"
jint ledOpen(JNIEnv *env, jclass cls)
{ fd = open(DEVICE_NAME, O_RDWR);
__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", DEVICE_NAME);
__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledOpen fd: %d", fd);
if(fd >= )
return ;
else
return -;
return ;
} void ledClose(JNIEnv *env, jclass cls)
{
__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledClose......");
close(fd);
} jint ledCtrl(JNIEnv *env, jclass cls,jint which, jint status)
{
int ret = ioctl(fd, status, which);
__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledCtrl: %d, %d ret: %d",which, status, ret); return ret;
} static const JNINativeMethod methods[] = {
{"ledOpen", "()I", (void *)ledOpen},
{"ledClose", "()V", (void *)ledClose},
{"ledCtrl", "(II)I", (void *)ledCtrl},
}; /* System.loadLibrary */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jclass cls; if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
return JNI_ERR; /* JNI version not supported */
}
cls = (*env)->FindClass(env, "com/otherway/myapplication/HardControl");
if (cls == NULL) {
return JNI_ERR;
} /* 2. map hello java<-->c c_hello */
if((*env)->RegisterNatives(env, cls, methods, sizeof(methods) / sizeof(methods[])) < )
return JNI_ERR; return JNI_VERSION_1_4;
}
HardControl.java
package com.otherway.myapplication;

public class HardControl {
public static native int ledCtrl(int which, int status);
public static native int ledOpen();
public static native void ledClose(); static {
try {
System.loadLibrary("hardcontrol");
} catch (Exception e) {
e.printStackTrace();
}
} }

上面定义了一个JNINativeMethod类型的gMethods数组,里面存储的就是HardControl的Native方法与JNI层方法的对应关系,其中注释1处”ledClose”是Java层的Native方法,它对应的JNI层的方法为ledClose。”()V”是ledClose方法的签名信息,关于Java方法的签名信息后续的文章会介绍。
在不同的类空间使用直接修改cls = (*env)->FindClass(env, "com/otherway/myapplication/HardControl");即可

JNI原理与静态、动态注册的更多相关文章

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

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

  2. JNI静态注册与动态注册详解

    JNI注册,是指将java层方法(native关键字修饰的)和C层方法对应起来,以实现java层代码调用c层代码的目的.JNI注册分为静态注册和动态注册两种,静态注册是通过固定格式方法名进行关联,动态 ...

  3. ndk学习20: jni之OnLoad动态注册函数

    一.原理 当在系统中调用System.loadLibrary函数时,该函数会找到对应的动态库, 然后首先试图找到"JNI_OnLoad"函数,如果该函数存在,则调用它 JNI_On ...

  4. Android 动态注册JNI函数

    1.JNI函数注册方式 在Android开发中,由于种种原因我们需要调用C/C++代码,在这个时候我们就需要使用jni了, jni在使用时要对定义的函数进行注册,这样java才能通过native关键字 ...

  5. Android JNI之动态注册

    所谓动态注册,就是不用像静态注册那样按规则严格的命名native方法,而是在加载so库的时候完成这个从Java方法到native方法的匹配工作,而这个匹配工作,需要我们写native代码来完成.下面直 ...

  6. Android开发4: Notification编程基础、Broadcast的使用及其静态注册、动态注册方式

    前言 啦啦啦~(博主每次开篇都要卖个萌,大家是不是都厌倦了呢~) 本篇博文希望帮助大家掌握 Broadcast 编程基础,实现动态注册 Broadcast 和静态注册 Broadcast 的方式以及学 ...

  7. Oracle监听的静态注册和动态注册

    静态注册:通过解析listene.ora文件 动态注册:由PMON进程动态注册至监听中 在没有listener.ora配置文件的情况下,如果启动监听,则监听为动态注册.用图形化netca创建的监听,默 ...

  8. Oracle 监听动态注册与静态注册

    静态注册 静态注册是在启动listener时,listener会从listener.ora文件中获取服务名及相关信息.信息包括:实例名和服务名等. --静态注册时,listener.ora中的内容如下 ...

  9. Android(java)学习笔记173:BroadcastReceiver之 静态注册 和 动态注册

    1. 广播接受者>什么是广播.收音机.电台:对外发送信号.收音机:接收电台的信号. >在android系统里面,系统有很多重要的事件: 电池电量低,插入充电器,sd卡被移除,有电话打出去, ...

随机推荐

  1. docker镜像仓库

    搭建私有镜像仓库 Docker Hub作为Docker默认官方公共镜像,如果想自己搭建私有镜像仓库,官方也提供registry镜像,使得搭建私有仓库非常简单. 下载registry镜像并启动 [roo ...

  2. CSS实现不换行/自动换行/文本超出隐藏显示省略号

    在写页面的时候,我们经常会需要用到关于文本的换行,强制换行以及显示几行超过显示省略号等,今天我们就对这些问题来做个汇总吧! 1.自动换行 div{ word-wrap:break-word; word ...

  3. 用react+redux+webpack搭建项目一些疑惑

    --save-dev开发用 例如:webpack --save开发和发布用 例如:react

  4. Java 编程技巧之数据结构

    前言: 介绍几种常见的java数据结构及应用. 使用HashSet判断主键是否存在 HashSet 实现 Set 接口,由哈希表(实际上是 HashMap )实现,但不保证 set  的迭代顺序,并允 ...

  5. 关于GeneXus封装方法Model的方法

     最近 刚从外地出差回来 工作任务不是很重 能够抽点时间记点东西 下午花了2个多钟头尝试了一下GeneXus的封装方法的功能,这里记一下便于自己以后查看.我们在许多项目中或多或少都会有着重复代码编写的 ...

  6. java 数组的定义

    package java03; /* 数组的初始化:就是创建一个数组,并向其中古语一些默认的值 两种常见的初始化方式: 1.动态初始化(指定长度) 2.静态初始化(指定内容) 动态初始化数组格式: 数 ...

  7. Node的优点和缺点

    (优点)因为Node是基于事件驱动和无阻塞的,所以非常适合处理并发请求, 因此构建在Node上的代理服务器相比其他技术实现(如Ruby)的服务器表现要好得多. 此外,与Node代理服务器交互的客户端代 ...

  8. hibernate 双向1对多

    1: 还是用客户Customer和订单Order来解释: “一对多”的物理意义:一个客户可以有多个订单,某个订单只能归宿于一个客户. “双向”的物理意义:客户知道自己有哪些订单,订单也知道自己归宿于哪 ...

  9. css text文本

    CSS 文本格式 文本格式 This text is styled with some of the text formatting properties. The heading uses the ...

  10. lazy图片懒加载使用

    看到一个小伙子写的言简意赅很不错,摘录如下: https://www.npmjs.com/package/vue-lazyload 首先我们先在npm上下载vue-lazyload的包 1 npm i ...