JNI,NDK编程专题

JNI编程注册方式

JVM 查找 native 方法有两种方式:

1)、按照 JNI 规范的命名规则(静态注册)

2)、调用 JNI 提供的 RegisterNatives 函数,将本地函数注册到 JVM 中(动态注册)

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

静态注册

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

静态注册就是根据方法名,将Java方法和JNI方法建立关联,但是它有一些缺点:

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

JNI编程步骤

就像C不能直接使用Java的引用类型一样,C也不能直接的访问Java成员变量,而是通过JNI所封装的API来调用Java成员。
通常会有如下的步骤:
1:获取java实例对象的引用
2:通过实例对象获取java成员变量ID
3:通过变量ID获取java成员变量

step1.下载配置NDK
step2.声明native方法

新建一个类,声明native方法:


public class JniTest {
    static { System.loadLibrary("jni-test"); 
}
  public static void main(String args[]) {
    JniTest jniTest = new JniTest();           System.out.print(jniTest.getFromJni()); 
   jniTest.setIntoJni("hello world"); 
} 
  public native String getFromJni(); 
  public native void setIntoJni(String info);
}

由native 修饰的就是我们声明的本地方法:

step.3编译源文件得到.class文件,导出.h头文件
javac com.example/jniTest.java

javah com.example/JniTest

这样就得到了 .class文件和.h头文件

进入.h文件可以看到:

  • 在.h头文件中函数名格式是Java_包名类名方法名;
  • JNIEnv * 表示一个指向JNI环境的指针,通过它可以访问JNI接口提供的方法;
  • jobject:表示Java中的this;
  • JNIEXPORT 和 JNICALL:JNI中所定义的宏;
  • extern “C”:表示内部的函数采用c语言的命名风格来编译
step4.实现声明的native方法,配置Android.mk和Application.mk文件

在工程的主目录下创建一个子目录,然后将.h头文件复制到此目录中,接着创建test.c、Android.mk和Application.mk文件; test.c如下:


/* #include是编译预处理指令,就是在编译前将stdio.h这个文件里 的函数都添加到你写的cpp文件中,然后参与编译,生成.obj文件。 如果没有这个指令,你用到的一些方法时编辑器就会报错: */ 

#include <jni.h> 
#include <stdio.h> 

jstring Java_com_jnidemo_JniTest_getFromJni 
    (JNIEnv *env, jobject thiz){ 
    printf("getFromJni is load")
   return (*env)->NewStringUTF(env, "i am from jni "); 
}; 

void Java_com_jnidemo_JniTest_setIntoJni 
   (JNIEnv *env, jobject thiz, jstring string){ 
   printf("setIntoJni is load") 
   (*env)->ReleaseStringUTFChars(env, string, "setintojni");
};

Android.mk


LOCAL_PATH:= $(call my-dir)

# 清除之前的一些系统变量 
include $(CLEAR_VARS) 

# 编译的源文件 
LOCAL_SRC_FILES:=test.c

# 编译生成的目标对象 用来给java调用的模块名, LOCAL_MODULE := jni-test 

# 指明要编译成动态库 
include $(BUILD_SHARED_LIBRARY)

Application.mk:


#默认情况下NDK会编译产生各个CPU平台的so库 
#指定so库的CPU平台的类型 all标识编译所有平台 

APP_ABI := all

APP_ABI 可以指定so库的CPU平台的类型,常见的架构平台有armeabi,x86和mips,编译的时候尽量这三种都编译,以免在不同cpu平台报出UnsatisfiedLinkError 错误;

step5.通过ndk-build命令编译产生so库文件
ndk-build

这个时候在主目录会自动生成libs目录和obj目录,里面装的就是生成的so库文件:

到此一个基本的通过NDK进行JNI编程就完成了

step6.在调用Native()方法前,加载.so的库文件
System.loadLibrary("Hello");
(文件名个Android.mk文件中的LOCAL_MODULE属性指定的值相同)

一、谈谈你对 JNI 和 NDK 的理解

JNI

JNI 是 Java Native Interface 的缩写,即 Java 的本地接口。

目的是使得 Java 与本地其他语言(如 C/C++)进行交互。

JNI 是属于 Java 的,与 Android 无直接关系。

NDK

NDK 是 Native Development Kit 的缩写,是 Android 的工具开发包。

作用是更方便和快速开发 C/C++ 的动态库,并自动将动态库与应用一起打包到 apk。

NDK 是属于 Android 的,与 Java 无直接关系。

总结

JNI 是实现的目的,NDK 是 Android 中实现 JNI 的手段

二、谈谈你对 JNIEnv 和 JavaVM 理解

JavaVM

JavaVM 是虚拟机在 JNI 层的代表。

一个进程只有一个 JavaVM。(重要!)

所有的线程共用一个 JavaVM。(重要!)

JNIEnv

JNIEnv 表示 Java 调用 native 语言的环境,封装了几乎全部 JNI 方法的指针。

JNIEnv 只在创建它的线程生效,不能跨线程传递,不同线程的 JNIEnv 彼此独立。(重要!)

注意

在 native 环境下创建的线程,要想和 java 通信,即需要获取一个 JNIEnv 对象。我们通过 AttachCurrentThread 和 DetachCurrentThread 方法将 native 的线程与 JavaVM 关联和解除关联。

三、解释一下 JNI 中全局引用和局部引用的区别和使用

全局引用

通过 NewGlobalRef 和 DeleteGlobalRef 方法创建和释放一个全局引用。

全局引用能在多个线程中被使用,且不会被 GC 回收,只能手动释放

局部引用

通过 NewLocalRef 和 DeleteLocalRef 方法创建和释放一个局部引用。

局部引用只在创建它的 native 方法中有效,包括其调用的其它函数中有效。因此我们不能寄望于将一个局部引用直接保存在全局变量中下次使用(请使用全局引用实现该需求)。

我们可以不用删除局部引用,它们会在 native 方法返回时全部自动释放,但是建议对于不再使用的局部引用手动释放,避免内存过度使用。

扩展:弱全局引用

通过 NewWeakGlobalRef 和 DeleteWeakGlobalRef 创建和释放一个弱全局引用。

弱全局引用类似于全局引用,唯一的区别是它不会阻止被 GC 回收。

四、JNI 线程间数据怎么互相访问

考察点和上体类似,线程本来就是共享内存区域的,因此我们需要使用 全局引用。

五、怎么定位 NDK 中的问题和错误

一般在开发阶段的话,我们可以通过 log 来定位和分析问题。

如果是上线状态(即关闭了基本的 log),我们可以借助 NDK 提供的 addr2line 工具和 objdump 工具来定位错误。详情:

so 动态库崩溃问题定位(addr2line与objdump)

其它还可以使用 C/C++ 的一些分析工具。

六、静态注册和动态注册

静态注册

通过 JNIEXPORT 和 JNICALL 两个宏定义声明,Java + 包名 + 类名 + 方法名 形式的函数名。不好的地方就是方法名太长了。

动态注册

通常在 JNI_OnLoad 方法中通过 RegisterNatives 方法注册,可以不再遵从固定的命名写法(当然为了代码容易理解,名称还是尽量和 Java 中保持一致)。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容