目录
(一)基本使用
1、Java层声明本地方法
编写java文件:
package com.example.jnitest;
//import android.util.Log;
public class Hello {
public static native String helloFromNative(String name); // 1.声明native函数
public static void main(String[] args) {
String text = helloFromNative("yangxin"); // 3.调用native函数
//LogUtils.i(TAG, "ttttttttttttt"+text);
}
static {
// 2.加载实现了native函数的动态库,只需要写动态库的名字,
// 这里名字叫HelloWorld,那么动态库就是libHelloWorld.so
System.loadLibrary("HelloWorld");
}
}
深入理解 System.loadLibrary - Pqpo's Notes
(import暂时注释掉了,因为不知道怎么在import的情况下进行下一步用javac出来class文件...我真菜)
(哦我知道了,就正常编译就行,不用javac,去out目录下找class文件就行,比如我的在 out/target/common/obj/APPS/FactoryKitTest_intermediates/classes/里面hhh)
2、Native层关联Java层本地方法
2.1 使用静态注册的方法关联Java层本地方法(不推荐)
2.1.1 javac 生成class文件
javac src/com/study/jnilearn/HelloWorld.java -d ./bin
-d是表示将编译后的class文件放到指定的目录下,不加也行
2.1.2 用javah -jni命令,根据class字节码文件生成.h头文件
(ps:如果使用动态注册的话就不用生成.h头文件,但必须实现JNI_OnLoad回调函数,动态注册的工作就是在这里完成的)
javah -jni -classpath ./bin -d ./jni com.study.jnilearn.HelloWorld 或者
javah -jni -classpath ./bin -o HelloWorld.h com.study.jnilearn.HelloWorld
-classpath :类搜索路径,这里表示从当前的bin目录下查找
-d :将生成的头文件放到当前的jni目录下
-o : 指定生成的头文件名称,默认以类全路径名生成(包名+类名.h)
注意:-d和-o只能使用其中一个参数。
我这次用的javah -jni -classpath ./src/ -d . 包名.类名
这里需要注意:
javah找不到类文件(已解决)_绝代不风华的博客-CSDN博客_java找不到类
(1)java和class不能放在同一个文件夹中!!!
(2)-classpath 后面的路径必须是包名的上一级目录!!!
2.1.3 编写cpp文件
引入生成的头文件,实现头文件中的方法;
#include "com_example_jnitest_Hello.h"
JNIEXPORT jstring JNICALL Java_com_example_jnitest_Hello_helloFromNative
(JNIEnv *env, jobject obj){
return env->NewStringUTF("Hello From Native");
}
2.2 使用动态注册的方法关联Java层本地方法 (推荐)
动态注册不用生成.h头文件,而是直接编写cpp文件。
注意。需要在cpp文件中实现JNI_OnLoad回调函数,也就是在这里完成动态注册的工作。
(因为Java层执行System.loadLibrary加载完动态库后,JNI_OnLoad方法会被调用(如果有的话),所以在JNI_OnLoad方法中通过调用RegisterNatives方法完成注册操作是非常合理的,另外虽然静态方法不需要实现这个JNI_OnLoad,但我们都建议实现一下,因为显然这个函数适合做一些初始化的操作)
#include <jni.h>
#include <cassert>
#include <cstdlib>
#include <iostream>
using namespace std;
/*native 方法实现
方法名无要求,但要保证方法的返回值和参数格式与java层保持一致,
其中返回值使用与java中返回值对应的jni类型;
而参数最少有2个:JNIEnv、jobect或jclass。
第一个是JNIEnv,固定不变(与java虚拟机线程相关的代表jni环境的结构体)
第二个jobject 代表的是java层的Hello这个类的对象(调用这个函数的对象),当然如果是实例方法才为jobject,如果是静态方法则为jclass;
剩下的参数与java方法中的参数保持一致。
*/
jstring native_hello(JNIEnv *env, jobject obj){
return env->NewStringUTF("Hello From Native");
}
/*将需要注册的函数列表,放在JNINativeMethod 类型数组中
以后如果需要增加函数,只需在这里添加就行了
参数:
1.java代码中用native关键字声明的函数名
2.方法描述符(方法签名,包含参数类型和返回值类型),具体规则见下面的Jni描述符章节
3.C/C++中对应函数的函数名
*/
static JNINativeMethod getMethods[] = {
{ "helloFromNative", "()Ljava/lang/String;", (void*)native_hello},
};
//此函数通过调用JNI中 RegisterNatives 方法注册函数
static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* getMethods,int methodsNum){
jclass clazz;
//找到声明native方法的Java类
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){
//指定Java层的类描述符(具体规则见下面的Jni描述符章节),通过FindClass 方法来找到对应的类
const char* className = "com/example/jnitest/Hello";
return registerNativeMethods(env,className,getMethods, sizeof(getMethods)/ sizeof(getMethods[0]));
}
//回调函数,Java层调用System.loadLibrary后执行, 在这里面注册函数
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){
JNIEnv* env = NULL;
//判断虚拟机状态是否有问题
if(vm->GetEnv((void**)&env,JNI_VERSION_1_6)!= JNI_OK){
return -1;
}
assert(env != NULL);
//开始注册函数, 调用顺序registerNatives -》registerNativeMethods -》env->RegisterNatives;
//其实就是最终要调用env->RegisterNatives
if(!registerNatives(env)){
return -1;
}
//返回jni 的版本
return JNI_VERSION_1_6;
}
看着麻烦但是其实核心函数就下面的部分,嫌麻烦可以用JNIHelper.h
clazz = env->FindClass(className);
if(clazz == NULL){
return JNI_FALSE;
}
//注册函数 参数:java类 所要注册的函数数组 注册函数的个数
if(env->RegisterNatives(clazz,getMethods,methodsNum) < 0){
return JNI_FALSE;
}
在Android中使用JNI_gogo_wei的博客-CSDN博客_android jni使用
(二)数据类型
1.
基本数据类型:
引用数据类型:
可以看到,在引用数据类型里,除了数组和String,Class,Throwable,其余的都在jni中是jobject