在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類的對象



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章