Android 的SDK中沒有包括JNI的支持,而且對如何支持JNI也沒有任何文檔說明。不過既然整個Android 平台是開源的,我們可以通過Google發佈的源代碼來找到一些線索(比如frameworks/base/media/jni/目錄),依葫蘆畫瓢的實現上層JAVA程序通過JNI來呼叫Native C程序中的函數。
依照下面的步驟可以實現一個非常簡單的JNI的實例程序:
1. 首先編寫C模塊,實現動態庫。(關於如何在Android中編譯C模塊的更多細節,請參考《Android編譯環境(1) - 編譯Native C的helloworld模塊》。)
在development目錄下添加新目錄hellolib,並添加hellolib.c和Android.mk文件。hellolib.c的內容如下:
|
#include <jni.h>
#define LOG_TAG "TestLib" #undef LOG #include <utils/Log.h>
JNIEXPORT void JNICALL Java_com_test_TestHelloLib_printHello(JNIEnv * env, jobject jobj) { LOGD("Hello LIB!\n"); } |
注意這裡的函數名需要按照JNI的規範(因此也可以用javah -jni工具來生成頭文件,來保證函數名的正確性),Java_com_test_TestHelloLib_printHello的命名對應後面在java代碼中,package名字是com.test,類名是TestHelloLib,native函數名是printHello。
另外,LOGD及#define LOG_TAG "TestLib"等打印log的方式是採用了Android所提供的LOG機制,這樣才能通過Android的logcat工具看到log。
用於編譯C模塊的Android.mk文件內容如下:
|
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \ hellolib.c
LOCAL_C_INCLUDES := \ $(JNI_H_INCLUDE)
LOCAL_SHARED_LIBRARIES := \ libutils
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE := libhello
include $(BUILD_SHARED_LIBRARY) |
該文件中的一些變量分別對應的含義如下:
LOCAL_SRC_FILES - 編譯的源文件
LOCAL_C_INCLUDES - 需要包含的頭文件目錄
LOCAL_SHARED_LIBRARIES - 鏈接時需要的外部庫
LOCAL_PRELINK_MODULE - 是否需要prelink處理(參考prelink的詳細介紹:《動態庫優化——Prelink(預連接)技術》,Android的Toolchain, prelink工具:《Android Toolchain與Bionic Libc》)
LOCAL_MODULE - 編譯的目標對象
BUILD_SHARED_LIBRARY - 指明要編譯成動態庫。
接下來回到Android頂層目錄,並執行make libhello來編譯:
|
# cd $(YOUR_ANDROID) && make libhello target thumb C: libhello <= development/hellolib/hellolib.c target SharedLib: libhello (out/target/product/generic/obj/SHARED_LIBRARIES/libhello_intermediates/LINKED/libhello.so) target Non-prelinked: libhello (out/target/product/generic/symbols/system/lib/libhello.so) target Strip: libhello (out/target/product/generic/obj/lib/libhello.so) Install: out/target/product/generic/system/lib/libhello.so |
編譯結果可得到位於out/target/product/generic/system/lib/目錄的動態共享庫libhello.so
2.編寫Java模塊,來通過JNI方式呼叫C接口。具體Eclipse環境的搭建請參考Android SDK文檔中的詳細說明,及Hello Android程序的創建過程,這裡僅給出我們需要修改的TestHelloLib.java文件:
|
package com.test;
import android.app.Activity; import android.os.Bundle;
public class TestHelloLib extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); printHello(); }
static { System.loadLibrary("hello"); }
private native void printHello(); } |
注意上面代碼中粗體字部分:private native void printHello()用來聲明一個native接口,static { System.loadLibrary("hello"); } 用來加載上面步驟中生成libhello.so(注意loadLibrary方法的參數不是」libhello.so」,而是去掉前綴和後綴之後的」hello」),onCreate()方法中則呼叫了printHello()接口。
通過這一步驟可生成Android開發者所熟悉的apk文件:TestHelloLib.apk。
3.集成測試TestHelloLib.apk和libhello.so。先運行emulator並將TestHelloLib.apk和libhello.so上傳至emulator中。注意要將libhello.so上傳到emulator的/system/lib目錄,由於該目錄是只讀的,上傳之前先要執行adb remount:
|
# adb remount # adb push out/target/product/generic/system/lib/libhello.so /system/lib # adb install TestHelloLib.apk |
接下來在模擬器選單中可以看到已經安裝的TestHelloLib程序,運行即可。
由於JNI接口printHello()並沒有作界面上的改動,要驗證其效果需要用Android的logcat工具來查看。運行」adb logcat」可以找到下面的log片斷:
|
I/ActivityManager( 48): Starting activity: Intent { action=android.intent.action.MAIN categories={android.intent.category.LAUNCHER} flags=0x10200000 comp={com.test/com.test.TestHelloLib} } I/ActivityManager( 48): Start proc com.test for activity com.test/.TestHelloLib: pid=174 uid=10024 gids={} D/dalvikvm( 174): Trying to load lib /system/lib/libhello.so 0x43481c58 D/dalvikvm( 174): Added shared lib /system/lib/libhello.so 0x43481c58 D/dalvikvm( 174): No JNI_OnLoad found in /system/lib/libhello.so 0x43481c58 D/dalvikvm( 174): +++ not scanning '/system/lib/libwebcore.so' for 'printHello' (wrong CL) D/dalvikvm( 174): +++ not scanning '/system/lib/libmedia_jni.so' for 'printHello' (wrong CL) D/TestLib ( 174): Hello LIB! I/ActivityManager( 48): Displayed activity com.test/.TestHelloLib: 806 ms |
這裡包含了呼叫printHello()的log資訊,其中"D/TestLib ( 174): Hello LIB!" 就是printHello()所打印的訊息。至此成功完成Android JNI的實例驗證。

請問一下是不是要在hellolib.c 裡面定義 #define LOG_NDDEBUG 0 訊息才會印出呢?
yes 比較要注意的是,這個定義必須在log.h之前先定義,也就是說 #define LOG_NDDEBUG 0 #include <utils/Log.h>
請問 #define LOG_TAG "TestLib" 這個是標準 android code 一定要加的嗎 如果不加 會送到 那裡 這個是否 所有 AP 都要加這一行呢? Andorid 有文件說這個嗎? 謝謝
那是用來在 logcat 裡面,做log 分類用的,加了之後除非另外指定tag,否則該檔案中使用LOGD就是被分類的此tag中。 不加應該一樣會顯示,只是tag 是空的。 原因可以直接找 LOGD 這個function的 implement,否則就是使用標準的Log.d,每次都自己加tag。
謝謝您 現在我們遇到一個問題是 AP 有人沒有去設 LOG_TAG 結果 我卻在 kmsg 看到那些 log 這樣是正常的嗎? kernel 裡面 可以 有 config 設定 去避免掉這個問題嗎? 謝謝
這可能就要看你們的kmsg是不是跟logcat 共用相同console顯示了,一般來說是不會一起出現才對。 請自己檢查console 設定吧
It’s something about JNI. JNI: The Java Native Interface (JNI) is a programming framework that allows Java code running in a Java Virtual Machine (JVM) to call and to be called by native applications (programs specific to a hardware and operating system platform) and libraries written in other languages, such as C, C++ and assembly. http://en.wikipedia.org/wiki/Java_Native_Interface Method of corresponding functions: /* * Array of methods. * * Each entry has three fields: the name of the method, the method * signature, and a pointer to the native implementation. */ static const JNINativeMethod gMethods[] = { { "_init", "()Z", (void *)mokoid_init }, { "_set_on", "(I)Z", (void *)mokoid_setOn }, { "_set_off", "(I)Z", (void *)mokoid_setOff }, }; Description of corresponding functions: Andoird 中使用了一種不同傳統Java JNI的方式來定義其native的函數。 其中很重要的區別是Andorid使用了一種Java 和 C 函數的映射表陣列,並在其中描述了函數的參數和返回值。 這個陣列的類型是JNINativeMethod,定義如下: typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethod; 第一個變數name是Java中函數的名字。 第二個變數signature,用字串是描述了函數的參數和返回值 第三個變數fnPtr是函數指標,指向C函數。 其中比較難以理解的是第二個參數,例如 "()V" "(II)V" "(Ljava/lang/String;Ljava/lang/String;)V" 實際上這些字元是與函數的參數類型一一對應的。 "()" 中的字元表示參數,後面的則代表返回值。例如"()V" 就表示void Func(); "(II)V" 表示 void Func(int, int); 具體的每一個字元的對應關係如下 字元 Java類型 C類型 V void void Z jboolean boolean I jint int J jlong long D jdouble double F jfloat float B jbyte byte C jchar char S jshort short 陣列則以"["開始,用兩個字元表示 [I jintArray int[] [F jfloatArray float[] [B jbyteArray byte[] [C jcharArray char[] [S jshortArray short[] [D jdoubleArray double[] [J jlongArray long[] [Z jbooleanArray boolean[] 上面的都是基本類型。 如果Java函數的參數是class,則以"L"開頭,以";"結尾中間是用"/" 隔開的包及類名。 而其對應的C函數名的參數則為jobject. 一個例外是String類,其對應的類為jstring Ljava/lang/String; String jstring Ljava/net/Socket; Socket jobject 如果JAVA函數位於一個嵌入類,則用$作為類名間的分隔符號。 例如 "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z" http://hi.baidu.com/zhlg_hzh/blog/item/f0d782081f2f45d963d986f5.html
請問 我透過上述的方式去做一個應用程式.apk 之後傳給別人使用 卻無法正常使用 是否是因為對方也要自行上傳libhello.so這個才行呢?
使用這種方式就一定提供lib,不過lib的部份可以修改 makefile 和apk一起打包