The memo blog.

About programming language and useful library.

JNI在Eclipse的使用

| Comments

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
  • ndk-build.cmd

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檔沒有找到而且放到專案裡面。

Comments