1. Java调用C库中函数的步骤
详细使用请参照《jni.pdf》官方文档
(1)加载C库:
在java中:加载C库,并声明在C库中实现的本地方法: System.loadLibrary("libhello");
(2)函数名映射:
在C语言中: 建立Java函数名到C库函数名的映射:Java函数名 <-------映射-----> C库函数名
其中,建立Java函数到C库函数映射的关系的方式有两种
① 隐式建立:
比如如果要在类a.b.c.d.JNIDemo中要调用hello函数,则在C语言中就要实现Java_a_b_c_d_JNIDemo_hello
② 显示建立:
JNI_OnLoad{ RegisterNatives() }
java中加载C库时,会导致这里的OnLoad函数被调用
隐式建立,函数名特别长,不便于阅读和见名知意, 所以在以后的开发中一律采用显示建立方式
函数签名的对应规则如下:
对于基本数据类型:
对应关系比较简单;
对于类:"L包.子包.类名;" (前面有“L”, 后面有";")
String类:"Ljava/lang/String"
其他类:一律用Object表示, "Ljava/lang/Object"
(3)调用函数
2. 自动生成函数签名
使用jdk的javah生成jni头文件,其中就是自动生成的本地方法的函数签名
有如下两种方法:
(1)DOS命令行下:
① 进入对应目录:
jdk 1.7 在项目的src目录下运行javah
jdk 1.6 在项目的bin/classes文件夹下运行javah
② 执行javah命令:
javah 声明native方法的java类的全类名,比如:javah com.example.jni_test.MainActivity
例子: 比如在启明2w中,
D:\4GDataTransport\app\src\main\java\com\visionvera\LTE4GDataTransport\Native
就会生成对应的文件:com_visionvera_LTE4GDataTransport_Native_Native.h
其中就有你要的函数签名了!!!
(2)在eclipse中
现在开发Android,都使用studio了, eclipse早就淘汰了, 所以该方法不再使用
① 工具栏点击External Tools,在弹出的对话框中双击Program,配置如下:
③ C:\Program Files\Java\jdk1.8.0_91\bin\javah.exe
④ ${project_loc}
⑤ -v -classpath "${project_loc}/bin/classes" -d "${project_loc}/jni" ${java_type_name}
② 之后就可以点击工具栏上的External Tools按钮生成头文件了(要先定位到包含本地方法的类文件)
生成的头文件在工程的jni目录中,如果没有显示出来,刷新下即可
3. java 和 C 互调
(1)java代码
// MainActivity.java
package com.example.jni_test;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
// 1. 加载C库: 通常放在静态代码块中,因为静态代码块是在构造对象之前执行, 并且只执行一次, 放在这里比较合适
static {
System.loadLibrary("native"); // libnative.so
}
// 2. 声明本地方法
public static native String native_testString(String s);
public static native int[] native_testArray(int[] arr);
public static native int[] native_testArray(int length);
public native void native_cCallJava();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 3. 调用本地方法
// 3.1 java传递String给C, C返回String给java
String str = native_testString("abcd");
System.out.println(str);
// 3.2 java传递数组给C, C处理后再返回给java
int[] arr = { 1, 2, 3 };
int[] retArr;
retArr = native_testArray(arr);
for (int i = 0; i < retArr.length; i++) {
if (i == retArr.length - 1) {
System.out.println(retArr[i]);
} else {
System.out.print(retArr[i] + ", ");
}
}
// 3.3 C中构建数组, 然后返回给java
retArr = native_testArray(5);
for (int i = 0; i < retArr.length; i++) {
if (i == retArr.length - 1) {
System.out.println(retArr[i]);
} else {
System.out.print(retArr[i] + ", ");
}
}
// 3.4 C中直接调用java中的方法
native_cCallJava();
}
public void showDialog(String message) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("警告!!");
builder.setMessage(message);
builder.show();
}
public void showDialog(String message, int i) {}
}
(2) jni代码
// native.c
#include <jni.h>
#include <cutils/log.h>
#include <assert.h>
#define LOG_TAG "NATIVE"
jstring native_testString(JNIEnv *env, jclass clazz, jstring jstr) {
ALOGI("Call native_testString\n");
/*
* 注意:java传递过来的其实是一个String对象,这里使用jstring,也就是void*来接收
* 要通过GetStringUTFChars从jstring转来解析出c语言中的char*
* const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);
*/
char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL); //这里会分配空间
if (cstr == NULL) {
return 0; /* out of memory */
}
for(int i = 0; i < strlen(cstr); i++) { //把每个字符取出来, +1
cstr[i] = cstr[i] + 1;
}
//jstring (*NewStringUTF)(JNIEnv*, const char*);
jstring jstr_to_return = (*env)->NewStringUTF(env, cstr); //其实这里是转换成了一个String对象, 赋值给jstring, 返回, java中使用String接收即可
(*env)->ReleaseStringUTFChars(env, jstr, cstr); // 将上面分配的空间释放掉
return jstr_to_return;
}
jintArray native_testArray1(JNIEnv *env, jclass clazz, jintArray jArray) {
ALOGI("Call native_testArray1\n");
jint *carr;
jintArray jarr_to_return;
/**
* 注意:这里的jArray是java传递过来的, 它是java的数组, 并不是C的数组
* JNIEnv提供的并不将讲java的数组转换成C的数组 而是如下:
* jsize (*GetArrayLength)(JNIEnv*, jarray);
*/
//jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
carr = (*env)->GetIntArrayElements(env, jArray, 0);
if (carr == NULL) {
return NULL; /* exception occurred */
}
int len = (*env)->GetArrayLength(env, jArray); // 获取数组长度
for (int i = 0; i < len; i++) {
carr[i] = carr[i] + 10;
}
(*env)->SetIntArrayRegion(env, jArray, 0, len, carr);
(*env)->ReleaseIntArrayElements(env, jArray, carr, 0); //别忘了释放
return jArray;
}
jintArray native_testArray2(JNIEnv *env, jclass clazz, jint len) {
ALOGI("Call native_testArray2\n");
int *carr;
jintArray rarr;
carr = malloc(sizeof(int) * len);
for (int i = 0; i < len; i++) {
carr[i] = 10 + i;
}
/* create jintArray */
rarr = (*env)->NewIntArray(env, len);
(*env)->SetIntArrayRegion(env, rarr, 0, len, carr);
free(carr);
return rarr;
}
void native_cCallJava(JNIEnv *env, jobject thiz) {
ALOGI("Call native_cCallJava\n");
//jclass clazz = env->FindClass("com/example/jni_test/MainActivity");
jclass clazz = (*env)->GetObjectClass(env, thiz);
jmethodID methodId = (*env)->GetMethodID(env, clazz, "showDialog", "(Ljava/lang/String;)V");
(*env)->CallVoidMethod(env, thiz, methodId, (*env)->NewStringUTF(env, "Message from C"));
}
/* Added by yinsj
typedef struct {
char *name;
char *signature;
void *fnPtr;
} JNINativeMethod;
*/
static JNINativeMethod gMethods[] = {
{"native_testString", "(Ljava/lang/String;)Ljava/lang/String;",(void*)native_testString},
{"native_testArray", "([I)[I", (void*)native_testArray1},
{"native_testArray", "(I)[I", (void*)native_testArray2},
{"native_cCallJava", "()V", (void*)native_cCallJava},
};
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
jint result = -1;
//1. 首先, 从java虚拟机中获得jni环境, 后面要使用env中提供的大量的函数
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("ERROR: GetEnv failed\n");
goto fail;
}
assert(env != NULL);
jclass clazz;
static const char* const kClassName="com/example/jni_test/MainActivity";
//2. 其次,指定要将这里实现的本地方法映射到哪一个类中
clazz = (*env)->FindClass(env, kClassName);
if(clazz == NULL) {
ALOGE("Can not get class:%s\n", kClassName);
return -1;
}
//3. 最后, 将这里实现的本地方法映射到上面指定的java类中
if((*env)->RegisterNatives(env, clazz,gMethods, sizeof(gMethods)/sizeof(gMethods[0])) != JNI_OK) {
ALOGE("RegisterNatives failed!\n");
return -1;
}
result = JNI_VERSION_1_4;
fail:
return result;
}
(3) Android.mk
#Android.mk
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
# This is the target being built.
LOCAL_MODULE:= libnative
# All of the source files that we will compile.
LOCAL_SRC_FILES:= \
native.c
# All of the shared libraries we link against.
LOCAL_SHARED_LIBRARIES := \
libcutils
# No static libraries.
LOCAL_STATIC_LIBRARIES :=
# Need headers.
LOCAL_C_INCLUDES += \
../prebuilts/ndk/current/platforms/android-19/arch-arm/usr/include
# compiler flags.
LOCAL_CFLAGS += -std=c99 -w
include $(BUILD_SHARED_LIBRARY)
#include $(BUILD_EXECUTABLE)
(4) 编译执行:
在上面结果的显示过程中, 我们自己设置了一个过滤器, 只显示Log Tag为NATIVE和System.out的log,如下:
同时在屏幕上显示对话框:
怎么截取这个截图呢?
答:Tools->Android->Android Device Monitor->Screen Capture
(5) 总结
① 在Android源码目录下,开发通常使用ALOGI, ALOGD, ALOGE来输出信息
此时,需要添加如下两步:
包含头文件#include <cutils/log.h>
并在Android.mk中链接库:
LOCAL_SHARED_LIBRARIES := \
libcutils
libcutils
② 在调用本地方法时:
如果通过static调用,则本地方法的参数中是jclass,传递下去的是java类的Class对象(也就是MainActivity的Class对象)
如果不通过static调用,则本地方法的参数中是jobjcet,传递下去的是java类的对象