Android JNI
Java Native Interface (JNI) 允許 Java 和其他語言的 code 可以進行溝通。
而 JNI 提供的通道是雙向的,即 Java 可以使用 C/C++ code 中的功能,而 C/C++ code 也可以使用到 VM 上的 Java AP 的功能.
在 Android 的應用層級類別都是用 Java 撰寫的,這些 Java 類別轉譯為 Dex 型式的Bytecode 之後,必須仰賴 Dalvik 虛擬機器 (VM: Virtual Machine) 來執行之。
在執行 Java 類別的過程中,如果 Java 類別需要與 C 組件溝通時,VM 就會去載入 C 組件,然後讓 Java 的函數順利地呼叫到 C 組件的函數。此時,VM 扮演著橋樑的角色,讓 Java 與 C 組件能透過標準的 JNI 介面而相互溝通。那麼 Java 程式又如何要求 VM 去載入所指定的 C 組件呢 ?
可使用下述指令: System.loadLibrary(*.so的檔名);
看看 Android 所提供的 MediaPlayer.java 類別:
public class MediaPlayer{ static { System.loadLibrary("media_jni"); } …… }要求 VM 去載入 Android 的 /system/lib/libmedia_jni.so 檔案。
載入 *.so 檔之後,Java 類別與 *.so 檔就匯合起來,一起執行了。
當 VM 執行到 System.loadLibrary() 函數時,首先會去執行 C 組件裡的 JNI_OnLoad()函數。
它的用途有二:
(1) 告訴 VM 此 C 組件使用那一個 JNI 版本。如果你的 *.so 檔沒有提供 JNI_OnLoad()函數,VM 會默認該 *.so 檔是使用最老的 JNI 1.1 版本。由於新版的 JNI 做了許多擴充,如果需要使用 JNI 的新版功能,例如 JNI 1.4 的 java.nio.ByteBuffer 就必須藉由 JNI_OnLoad() 函數來告知 VM。
(2) 由於 VM 執行到 System.loadLibrary() 函數時,就會立即先呼叫 JNI_OnLoad(),所以 C 組件的開發者可以藉由 JNI_OnLoad() 來進行 C 組件內的初期值之設定(Initialization)。
當 VM 載入 libmedia_jni.so 檔案時,就呼叫 JNI_OnLoad() 函數。
接著 JNI_OnLoad() 呼叫 register_android_media_MediaPlayer() 函數。
此時就會呼叫到 AndroidRuntime::registerNativeMethods() 函數,向 VM (即AndroidRuntime) 登記 gMethods[] 表格所含的本地函數了。 簡而言之,registerNativeMethods() 函數的用途有二:
(1) 更有效率去找到函數。
(2) 可在執行期間進行抽換。由於gMethods[]是一個<名稱,函數指標>對照表,在程式執行時,可多次呼叫 registerNativeMethods() 函數來更換本地函數之指標,而達到彈性抽換函數。
我們可以在 frameworks/base/media/jni/ 找到 sample code。
依照下面的步驟可以實現一個非常簡單的 JNI:
(1)在 development 目錄下新增目錄 hellolib,並 add hellolib.c and 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 的規範,Java_com_test_TestHelloLib_printHello 的命名對應後面在 java code中,package 名字是 com.test,class 名是 TestHelloLib,native 函數名是 printHello。
另外 log 是採用了 Android 所提供的 LOG 機制,這樣才能用 logcat 工具看到 log。
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 - 鏈接時需要的 LIB LOCAL_PRELINK_MODULE - 是否需要 Prelink 處理 LOCAL_MODULE - 編譯的目標 BUILD_SHARED_LIBRARY - 指明要編譯成動態庫 # cd $(YOUR_ANDROID) && make libhello 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。 修改的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(); }static { System.loadLibrary("hello"); } => 是用來 load 上面步驟中生成 libhello.so 。
這一步驟可生成 Android 開發者所熟悉的apk文件:TestHelloLib.apk。
(3)測試 TestHelloLib.apk and libhello.so。 將 libhello.so 上傳到 emulator 的 /system/lib 目錄。
運行 logcat 可以找到下面的 log 片斷:
D/dalvikvm( 174): +++ not scanning '/system/lib/libwebcore.so' D/dalvikvm( 174): +++ not scanning '/system/lib/libmedia_jni.so' D/TestLib ( 174): Hello LIB!
About [ D/dalvikvm( 174): +++ not scanning '/system/lib/libmedia_jni.so' ] message,
this is a "DEBUG" message, not an error message.
Copy from Re: Dalvik error message (wrong cl)
This message occurs when there is an unresolved native method, and the VM has to go searching through every known shared library for a matching symbol. The class that caused the method resolution is in a different class loader from the one that loaded libmedia_jni.so, so the VM is letting you know that it's not going to search there. Native methods that are explicitly registered (e.g. during JNI_OnLoad) don't go through the dlsym() search and will never generate this message.
測試結果只要增加 JNI_OnLoad 就可以解決了^^"
0 comments