4个案例速学JNI(C 函数 调用 Java 代码,附完整项目)
整个Demo下载
https://github.com/linweimao/NDK-C-Java-
C回调Java方法的核心思想
- C回调Java方法的核心思想: 反射
得到一个方法的签名
-
如何得到一个方法的签名?
执行命令: javap -s 全类名, 显示所有方法的签名信息
在执行命令之前最好编译一下,这样才有 class 文件(编译的意思就是Build(Rebuild Project)一下)
先进入到要生成方法签名的类的目录下
在输入命令
javap -s JNITest.class
//或者
javap -s JNITest
这个是输入javap -s JNITest.class
这个是输入javap -s JNITest
生成头文件的方式
点击 Terminal 输入命令,便可以生成一个JNI的 C 头文件.
输入第二个命令行提示错误的原因是注释为中文。所以在输入命令行时不能出现注释或者中文
javac -h ./ JNI.java
上面的命令的作用:根据Java中的 native 方法生成对应在 C 中的方法该怎么写(自动生成)
()V就为方法签名
cd进去到 JNITest 的根目录下 输入命令查看方法签名 :javap -s JNITest
C回调Java方法
测试1: 回调一般方法(无参无返回)
Java端 native
public native void callbackHelloFromJava();
C端函数
/*
测试1: 回调一般方法(无参无返回)
//1. 加载类得到jclass对象:
//jclass (*FindClass)(JNIEnv*, const char*);
//2. 得到对应方法的Method对象 : GetMethodId()
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*)
//3. 创建类对象
//jobject (*AllocObject)(JNIEnv*, jclass);
//4. 调用方法(无返回值)
//void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...)
*/
void Java_com_atguigu_ccalljava_JNI_callbackHelloFromJava
(JNIEnv * env, jobject obj) {
//1. 加载类得到jclass对象:
//jclass (*FindClass)(JNIEnv*, const char*);
jclass jc = (*env)->FindClass(env, "com/atguigu/ccalljava/JNI");
//2. 得到对应方法的Method对象 : GetMethodId()
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*)
jmethodID method = (*env)->GetMethodID(env, jc, "helloFromJava", "()V");
//3. 创建类对象
//jobject (*AllocObject)(JNIEnv*, jclass);
jobject obj2 = (*env)->AllocObject(env, jc);
//4. 调用方法
(*env)->CallVoidMethod(env, obj2, method);
}
java端被回调方法
public void helloFromJava() {
Log.e("TAG", "helloFromJava()");
}
测试2: 回调带int参数方法
Java端 native
public native void callbackAdd();
C端函数
/*
测试2: 回调带int参数方法
//1. 加载类得到jclass对象:
//2. 得到对应方法的Method对象 :
//3. 创建类对象
//4. 调用方法(无返回值)
//jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...)
*/
void JNICALL Java_com_atguigu_ccalljava_JNI_callbackAdd
(JNIEnv * env, jobject obj) {
//1. 加载类得到class对象
jclass jc = (*env)->FindClass(env, "com/atguigu/ccalljava/JNI");
//2. 得到对应方法的Method对象
jmethodID method = (*env)->GetMethodID(env, jc, "add", "(II)I");
//3. 创建类对象
jobject obj2 = (*env)->AllocObject(env, jc);
//4. 调用方法
(*env)->CallIntMethod(env, obj2, method, 3, 4);
}
java端被回调方法
public int add(int x, int y) {
Log.e("TAG","add() x=" + x+" y="+y);
return x + y;
}
测试3: 回调带String参数方法
Java端 native
public native void callbackPrintString();
C端函数
/*
测试3: 回调带String参数方法
//1. 加载类得到jclass对象:
//2. 得到对应方法的Method对象 :
//3. 创建类对象
//4. 调用方法(带String参数的无返回值)
//void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...)
*/
void Java_com_atguigu_ccalljava_JNI_callbackPrintString
(JNIEnv * env, jobject obj) {
//1. 加载类得到class对象
jclass jc = (*env)->FindClass(env, "com/atguigu/ccalljava/JNI");
//2. 得到对应方法的Method对象
jmethodID method = (*env)->GetMethodID(env, jc, "printString", "(Ljava/lang/String;)V");
//3. 创建类对象
jobject obj2 = (*env)->AllocObject(env, jc);
//4. 调用方法
jstring js = (*env)->NewStringUTF(env, "I from C");
(*env)->CallVoidMethod(env, obj2, method, js);
}
java端被回调方法
public void printString(String s) {
Log.e("TAG","C中输入的:" + s);
}
测试4: 回调静态方法
Java端 native
public native void callbackSayHello();
C端函数
/*
测试4: 回调静态方法
//1. 加载类得到jclass对象:
//2. 得到对应方法的Method对象 :
//jmethodID (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*)
//3. 调用方法
//void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...)
*/
void Java_com_atguigu_ccalljava_JNI_callbackSayHello
(JNIEnv * env, jobject obj) {
//1. 加载类得到class对象
jclass jc = (*env)->FindClass(env, "com/atguigu/ccalljava/JNI");
//2. 得到对应方法的Method对象
jmethodID method = (*env)->GetStaticMethodID(env, jc, "sayHello", "(Ljava/lang/String;)V");
//3. 调用方法
jstring js = (*env)->NewStringUTF(env, "I from C");
(*env)->CallStaticVoidMethod(env, jc, method, js);
}
java端被回调方法
public static void sayHello(String s){
Log.e("TAG", "我是java代码中的JNI."
+ "java中的sayHello(String s)静态方法,我被C调用了:"+ s);
}
JNI层日志打印
其实就是靠这个(liblog.so)so库打印日志
- 配置 jni 文件下的 Android.mk文件 输入 LOCAL_LDLIBS := -llog 打印日志库
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_LDLIBS := -llog
LOCAL_MODULE := ccalljava
LOCAL_SRC_FILES := CCallJava.c
include $(BUILD_SHARED_LIBRARY)
2. 在代码中要打印日志就要定义出来
3. 包含日志头文件, 定义日志输出函数
#include <android/log.h>
#define LOG_TAG "atlinweimao"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
调用 __android_log_print 这个方法打印日志
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, VA_ARGS)
第一个参数为日志的等级,
第二个参数为 TAG:标志
第三个参数为__VA_ARGS__:可变参数