本文主要讲:
- jni开发接口的编写:静态和动态方式介绍
Java 的native 方法如何与so中的方法关联,相互调用呢?
这就需要用到注册的概念了,通过注册,将指定的 native 方法和 so 中对应的方法绑定起来(函数映射表),这样就能够找到相应的方法了。
注册分为 静态注册 和 动态注册 两种。
静态注册
原理:根据函数名来建立 java 方法与 JNI 函数的一一对应关系;
实现流程:
编写 java 代码;
利用 javah 指令生成对应的 .h 文件;
对 .h 中的声明进行实现;
弊端:
编写不方便,JNI 方法名字必须遵循规则且名字很长;
编写过程步骤多,不方便;
程序运行效率低,因为初次调用native函数时需要根据根据函数名在JNI层中搜索对应的本地函数,然后建立对应关系,这个过程比较耗时;
在AS中新建一个Java Library名为media,写一个简单的MediaRecorder.java,如下所示。
1.编写一个java类,在里面加载对应的so库并且通过native关键字定义需要调用的函数
package com.example;
public class MediaRecorder {
static {
System.loadLibrary("media_jni");
native_init();
}
private static native final void native_init();
public native void start() ;
}
2.接着进入项目的media/src/main/java目录中执行如下命令
javac com.example.MediaRecorder.java
javah com.example.MediaRecorder
/* 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
返回值 + Java前缀+全路径类名+方法名+参数1JNIEnv+参数2jobject+其他参数
3.将头文件拷贝到jni目录下(在src同级目录建立文件夹,Android studio 在java同级目录建立文件夹)
4.编写C/C++源代码 并把刚拷贝的头文件包含进去 ,复制头文件中函数的定义部分,并实现其中的你想要的功能
然后编写Android.mk Application.mk(Application.mk主要用来定义适应的平台,x86 arm等)
Android.mk如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := HelloJni
LOCAL_SRC_FILES := HelloJni.cpp
include $(BUILD_SHARED_LIBRARY)
Application.mk如下:
#支持的CPU架构
APP_ABI := arm64-v8a
include $(BUILD_SHARED_LIBRARY)
其中LOCAL_MODULE定义的名字就是生成的so库名字,so库前面都会有个lib前缀,上面生产的so应该为 libHelloJni.so
5.在命令行中进入jni目录,输入ndk-build 即可生产对应so库,会自动放在libs文件夹下 至此就可以运行程序了
注意事项:
注意分隔符:
Java前缀与类名以及类名之间的包名和方法名之间使用"_“进行分割; 注意静态:
如果在Java中声明的方法是"静态的”,则native方法也是static。否则不是
如果你的JNI的native方法不是通过静态注册方式来实现的,则不需要符合上面的这些规范,可以格局自己习惯随意命名
动态注册
原理:利用 RegisterNatives 方法来注册 java 方法与 JNI 函数的一一对应关系;
实现流程:
原理:在JNI_Onload()函数中通过JNI中提供的RegisterNatives()方法来将C/C++方法和java方法对应起来(注册), 我们在调用 System.loadLibrary的时候,会在C/C++文件中回调一个名为 JNI_OnLoad ()的函数,在这个函数中一般是做一些初始化相关操作, 我们可以在这个方法里面注册函数, 注册整体流程如下:
- 编写Java端的相关native方法
- 编写C/C++代码, 实现JNI_Onload()方法
- 将Java 方法和 C/C++方法通过签名信息一一对应起来
- 通过JavaVM获取JNIEnv, JNIEnv主要用于获取Java类和调用一些JNI提供的方法
- 使用类名和对应起来的方法作为参数, 调用JNI提供的函数RegisterNatives()注册方法
// jni头文件
#include <jni.h>
#include <cassert>
#include <cstdlib>
#include <iostream>
using namespace std;
//native 方法实现
jint get_random_num(){
return rand();
}
/*需要注册的函数列表,放在JNINativeMethod 类型的数组中,
以后如果需要增加函数,只需在这里添加就行了
参数:
1.java中用native关键字声明的函数名
2.签名(传进来参数类型和返回值类型的说明)
3.C/C++中对应函数的函数名(地址)
*/
static JNINativeMethod getMethods[] = {
{"getRandomNum","()I",(void*)get_random_num},
};
//此函数通过调用RegisterNatives方法来注册我们的函数
static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* getMethods,int methodsNum){
jclass clazz;
//找到声明native方法的类
clazz = env->FindClass(className);
if(clazz == NULL){
return JNI_FALSE;
}
//注册函数 参数:java类 所要注册的函数数组 注册函数的个数
if(env->RegisterNatives(clazz,getMethods,methodsNum) < 0){
return JNI_FALSE;
}
return JNI_TRUE;
}
static int registerNatives(JNIEnv* env){
//指定类的路径,通过FindClass 方法来找到对应的类
const char* className = "com/example/wenzhe/myjni/JniTest";
return registerNativeMethods(env,className,getMethods, sizeof(getMethods)/ sizeof(getMethods[0]));
}
//回调函数
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){
JNIEnv* env = NULL;
//获取JNIEnv
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
assert(env != NULL);
//注册函数 registerNatives ->registerNativeMethods ->env->RegisterNatives
if(!registerNatives(env)){
return -1;
}
//返回jni 的版本
return JNI_VERSION_1_6;
}
优点:
流程更加清晰可控;
效率更高;
查看类中的方法的签名
可以使用 javap命令:
举例:javap -s -p MainActivity.class
public class com.example.hellojni.MainActivity extends android.app.Activity {
static {};
Signature: ()V
public com.example.hellojni.MainActivity();
Signature: ()V
protected void onCreate(android.os.Bundle);
Signature: (Landroid/os/Bundle;)V
public native java.lang.String stringFromJNI(); //native 方法
Signature: ()Ljava/lang/String; //签名
public native int max(int, int); //native 方法
Signature: (II)I //签名
}