JNI=Java Native Interface,會需要用到這個的原因在於Android的一些效能問題,另外一個原因就是使用vuforia的時候,大部份的Code都會需要用到JNI,可能也是因為效能問題吧!在這邊也是參考一些其他網路的資源然後做個備忘。基本上這個備忘主要是參考Learning Android Chapter 15. Native Development Kit,但是…裡面有些範例用的指令到我這邊都不能用,像是javah -jni [command]
所以寫出來也備忘一下。整個程式其實也不會說很龐大,主要列出比較重要的部分。那在網站的範例當中是用計算費式數列的例子。而結果如圖所顯示這樣,主要比較JNI和Java下的執行效能,可以發現遞迴運算的影響頗大的。
現在開始說明程式碼的部分:
CalcFib.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| package com.esw.ndk.calc.fib;
public class CalcFib {
//Java Fib implementation
public static long JavaFibRecursive(long Num) {
if (Num <= 0)
return 0;
if (Num == 1)
return 1;
return JavaFibRecursive(Num-1) + JavaFibRecursive(Num-2);
}
public static long JavaFibInterative(long Num) {
long previous = -1;
long result = 1;
for (long i = 0; i <= Num; i++) {
long sum = result + previous;
previous = result;
result = sum;
}
return result;
}
//Native Fib implementation
static {
System.loadLibrary("CalcFib");
}
public static native long NativeFibRecursive(long Num);
public static native long NativeFibInterative(long Num);
}
|
其實也沒有什麼好說明,就一個java class裡面有四個function兩個Java兩個Native code,在這邊用兩種費式數列計算方式,一個遞迴另外一個是疊代。實驗的結果就像前面所講的。有關於疊代和遞迴這兩種方式的差異,可以Google看看。在這程式碼當中比較重要的是public static native …
這段,這部分是宣告這邊是需要透過native的方式進行呼叫需要的Function,就像是在跟Java說,這部分我要丟給別人做一樣。
正常來說,都是可以透過javac xxx.java
來轉成xxx.class
,而我們所需要做的就是,透過javah [command]
的指令來去把需要implement的native code自動轉成jni的header檔,不用自己去動手寫header,算是比較方便,尤其jni的檔一堆莫名的底線。那問題來了,在前面所貼的Blog當中,他用的方法是:
1
2
| cd $(path_to_project)/bin //移動到專案資料夾底下的bin資料夾
javah -jni packagename.classname //執行javah指令來產生先前寫的native function的header
|
的方式來使用,但是在我這邊不能正常運作,後來試了其他方式,最後變成了:
1
2
| cd $(path_to_project)/bin //移動到專案資料夾底下的bin資料夾
javah -classpath $(path_to_project)/bin/classes packagename.classname
|
之後就可以在bin
資料夾看到classname.h
的header檔,在這邊我們的packagename=com.esw.ndk.calc.fib
,classname=CalcFib
,所以組合起來=com.esw.ndk.calc.fib.CalcFib
。接著只要在eclipse的專案當中加入jni的資料夾,接著把header搬過去就可以開始寫native code。這是所自動產生的header檔
com_esw_ndk_calc_fib_CalcFib.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| /* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_esw_ndk_calc_fib_CalcFib */
#ifndef _Included_com_esw_ndk_calc_fib_CalcFib
#define _Included_com_esw_ndk_calc_fib_CalcFib
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_esw_ndk_calc_fib_CalcFib
* Method: NativeFibRecursive
* Signature: (J)J
*/
JNIEXPORT jlong JNICALL Java_com_esw_ndk_calc_fib_CalcFib_NativeFibRecursive
(JNIEnv *, jclass, jlong);
/*
* Class: com_esw_ndk_calc_fib_CalcFib
* Method: NativeFibInterative
* Signature: (J)J
*/
JNIEXPORT jlong JNICALL Java_com_esw_ndk_calc_fib_CalcFib_NativeFibInterative
(JNIEnv *, jclass, jlong);
#ifdef __cplusplus
}
#endif
#endif
|
接著建立CalcFib.c的檔案
CalcFib.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| #include "com_esw_ndk_calc_fib_CalcFib.h"
long NativeFibRecursive(long Num) {
if(Num <= 0)
return 0;
if(Num == 1)
return 1;
return NativeFibRecursive(Num-1) + NativeFibRecursive(Num-2);
}
long NativeFibInterative(long Num) {
long previous = -1;
long result = 1;
long i = 0;
int sum = 0;
for (i=0; i <= Num; i++) {
sum = result + previous;
previous = result;
result = sum;
}
return result;
}
JNIEXPORT jlong JNICALL Java_com_esw_ndk_calc_fib_CalcFib_NativeFibRecursive
(JNIEnv *env, jclass obj, jlong Num) {
return NativeFibRecursive(Num);
}
JNIEXPORT jlong JNICALL Java_com_esw_ndk_calc_fib_CalcFib_NativeFibInterative
(JNIEnv *env, jclass obj, jlong Num) {
return NativeFibInterative(Num);
}
|
除了這些東西之外,最重要的就是Android.mk
了,Android
底下的Makefile
檔。
1
2
3
4
5
6
7
8
| LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := CalcFib
LOCAL_SRC_FILES := CalcFib.c
include $(BUILD_SHARED_LIBRARY)
|
裡面的變數可以在Google找到,主要都是gcc的定義參數等,用來清理或是紀錄當前目錄和環境變數。最重要的一點是LOCAL_MODULE
指的是這個Native library的名稱,經過NDK編譯完成之後,會在前面自動加入lib
所以經過NDK編譯之後會變成libCalcFib.so
這不用太理會,同樣在載入library時也不用刻意加入lib,所以在前面的Java Code當中用來載入Library的程式碼為其中的CalcFib就是LOCAL_MODULE
所指定的名稱。
1
2
3
| static {
System.loadLibrary("CalcFib");
}
|
在設定完成之後,開始利用ndk編譯,在ndk的資料夾底下會有:
ndk-build這是給Mac/Linux用的,而ndk-build.cmd,這是給windows用的,所以如果有設定eclipse自動編譯的會需要注意,不要去include錯誤的ndk-build否則會吐一堆錯誤。接著只要
1
2
| cd $(path_to_project)/jni
ndk-build
|
過程如果沒有意外,就可以產生*.so
檔案。對於NDK教學最主要的就是這幾個地方,通常*.so
都應該會被自動加入,在建立apk
檔案的時候,有時候會發現程式執行的時候找不到ModuleName.so
的情況,這很有可能是ndk-build
出錯或是某些.so
檔沒有找到而且放到專案裡面。