Android JNI

Tuesday, December 15, 2009 0


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 就可以解決了^^"

RELATED POSTS

0 comments