Bootstrap

在ubuntu虚拟机中开发JNI

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
② 在调用本地方法时: 
如果通过static调用,则本地方法的参数中是jclass,传递下去的是java类的Class对象(也就是MainActivity的Class对象)
如果不通过static调用,则本地方法的参数中是jobjcet,传递下去的是java类的对象



;