Android平臺上的JNI技術介紹

NDK簡介

Android是由Google領導開發的操做系統,Android依靠其開放性,迅速普及,成爲目前最流行的智能手機操做系統。html

http://liuyix.com/wordpress/wp-content/uploads/2012/07/wpid-android-system-architecture.jpg

圖0-1 Android系統架構圖java

圖0-1是Android系統架構圖。大多數程序位於最上層的Java Application層。Android經過把系統劃分爲幾個層次從而使得開發者可使用平臺無關的Java語言進行Android應用開發,沒必要關心程序實際的硬件環境。 Google不只爲開發者提供了SDK開發套件,爲了能讓開發者使用C/C++編寫的本地化的共享庫,利用編譯後的共享庫更高效的完成計算密集型的操做來提升應用的性能,或者移植重用已有的C/C++組件,提升開發效率,Android 1.5以後,又推出了NDK(Native Development Kit)。有了NDK,開發者可以在Android平臺上使用JNI(Java Native Interface)技術,實現應用程序中調用本地二進制共享庫。 因爲Android系統不一樣於以往的JNI使用環境而是在嵌入式硬件環境下,Android NDK提供了一套交叉編譯工具鏈,和構建程序的工具方便開發者在桌面環境下編譯目標平臺的二進制共享庫。 目前NDK提供了對ARMv5TE,ARMv7-A,x86和MIPS指令集平臺的支持,同時在本地接口的支持上,目前如下本地接口支持linux

  • libc
  • libm
  • libz
  • liblog
  • OpenGL ES 1.1 and OpenGL ES 2.0 (3D graphics libraries) headers
  • libjnigraphics (Pixel buffer access) header (Android 2.2 以上可用).
  • C++頭文件的一個子集
  • Android native應用API接口
  • JNI頭文件接口

由上面的介紹,咱們能夠知道,實際上NDK開發是以JNI技術爲基礎的,所以要求開發者必需要掌握基本的JNI技術,這樣才能進行有效的NDK開發。android

JNI技術簡介

JNI(Java Native Interface)是Java SDK 1.1時正式推出的,目的是爲不一樣JVM實現間提供一個標準接口,從而使Java應用可使用本地二進制共享庫,擴充了原有JVM的能力,同時Java程序仍然無需再次編譯就能夠運行在其餘平臺上,即保持了平臺獨立性又能使用平臺相關的本地共享庫提高性能。在Java開發中的位置以下圖所示。JNI做爲鏈接平臺獨立的Java層(如下簡稱Java層)與與平臺相關的本地環境(如下簡稱Native層)之間的橋樑。數組

http://liuyix.com/wordpress/wp-content/uploads/2012/07/wpid-role-of-jni-intro.gif

圖1-1 JNI在Java開發中的位置緩存

實際上在Android內部就大量的使用了JNI技術,尤爲是在Libraries層和Framework層。數據結構

什麼時候使用Android NDK

Google在其文檔提到了NDK不能讓大多數應用獲益,其增長的複雜度遠大於得到的性能的代價。Google建議當須要作大量的cpu密集同時少許存儲操做或者重用C/C++代碼時能夠考慮使用NDK。 本文的餘下部分將具體介紹Android平臺下經過NDK的支持的如何進行JNI的開發。架構

Hello,NDK

本節經過一個簡單的例子,介紹NDK開發流程以及JNI的基本使用。 筆者假定你已經下載了NDK,且有Android SDK開發的經驗。 在NDK開發包中就有若干的NDK示例。其中hello-jni 是一個簡單的實例。該實例從native層傳遞字符串到java層,並顯示在界面上。(你能夠在Eclipse裏選擇 新建Anroid項目 ,以後選擇 「Create project from existing source」,並定位到NDK目錄中的 Sample/hello-jni ,這樣就能夠將示例代碼導入到Eclipse中。) HelloJni的Java代碼以下:oracle

package com.example.hellojni;

import android.app.Activity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.os.Bundle;
import android.view.View.OnClickListener;

public class HelloJni extends Activity
{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button btn = (Button)findViewById(R.id.btn);
        final TextView txtv = (TextView)findViewById(R.id.txtv);
        btn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                txtv.setText(stringFromJNI());//調用native函數

            }
        });
    }

    /* A native method that is implemented by the
     * 'hello-jni' native library, which is packaged
     * with this application.
     * 聲明含有native關鍵詞的函數,就能夠在類中使用了。
     */
    public native String  stringFromJNI();

    /*
     * 該函數並無在共享庫中實現,可是仍然能夠聲明。
     * 沒有實現的native函數也能夠在類中聲明,native方法僅在首次調用時纔開始搜索。
     * 若沒有找到該方法,會拋出java.lang.UnsatisfiedLinkError異常
     */
    public native String  unimplementedStringFromJNI();

    /* this is used to load the 'hello-jni' library on application
     * startup. The library has already been unpacked into
     * /data/data/com.example.HelloJni/lib/libhello-jni.so at
     * installation time by the package manager.
     * 使用靜態方式再建立類時就載入共享庫,該共享庫(後面會介紹)在程序安裝後
     * 位於/data/data/com.example.HelloJni/lib/libhello-jni.so
     */
    static {
        System.loadLibrary("hello-jni");
    }
}

Java代碼中調用native函數很簡單。大體分爲如下幾步:app

  • 調用=System.loadLibrary=方法載入共享庫
  • 聲明native方法
  • 調用native方法

JNI的使用的一個關鍵點是 1) 如何找到共享庫 2)如何將Java代碼中的聲明的native方法和實際的C/C++共享庫中的代碼相關聯,即JNI函數註冊。 第一個問題能夠交給NDK構建工具 ndk-build 解決:一般是將編譯好的so共享庫放在 libs/armeabi/libXXX.so 以後會有更詳細的介紹。第二個問題能夠將在第二節中系統講述,如今咱們只簡單的說一下如何作。

利用javah生成目標頭文件

簡易實用的方法是經過利用Java提供的 javah 工具生成和聲明的native函數對應的頭文件。具體操做是以下:

  1. 命令行進入到你的項目目錄中
  2. 確認你的android項目的java代碼已經編譯,若是存在 bin/ 目錄,應該是編譯好的。
  3. 確認你的android項目目錄中存在 jni 子目錄,若是沒有則建立一個(咱們如今使用的自帶的實例代碼,所以能夠)。
  4. 在項目根目錄下執行命令: javah -jni com.example.hellojni.HelloJNI -classpath bin/classes -o jni/hello-jni.h 確認javah所在路徑已經在的$PATH路徑下
  5. 若上一命令執行成功,則會在 jni 目錄下生成一個名爲 my_jni_header.h 的頭文件。

編寫C/C++共享庫代碼

上一步驟咱們獲得了與Java源文件對應的頭文件,所以只要編寫 my_jni_header.c ,實現頭文件裏面的聲明的源代碼。生成的內容以下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_hellojni_HelloJni */

#ifndef _Included_com_example_hellojni_HelloJni
#define _Included_com_example_hellojni_HelloJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_hellojni_HelloJni
 * Method:    stringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI
  (JNIEnv *, jobject);

/*
 * Class:     com_example_hellojni_HelloJni
 * Method:    unimplementedStringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_unimplementedStringFromJNI
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

能夠看到生成的頭文件中的函數和示例項目 hello-jni 中的 hello-jni.c 正好對應。據此也可知咱們生成的頭文件是正確的。 hello-jni.c 源代碼以下:

#include <string.h>
#include <jni.h>
#include <stdio.h>

/* This is a trivial JNI example where we use a native method
 * to return a new VM String. See the corresponding Java source
 * file located at:
 *
 *   apps/samples/hello-jni/project/src/com/example/HelloJni/HelloJni.java
 */
jstring
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
    char msg[100];
    sprintf(msg,"Hello from JNI.");
    return (*env)->NewStringUTF(env, msg);
}

使用NDK提供的工具編譯生成共享庫

通過以上兩步,咱們已經獲得了C/C++共享庫的源代碼,如今須要使用交叉編譯工具將其編譯成目標機器上的二進制共享庫。NDK工具提供了一個簡單的構建系統,開發者之須要編寫 Android.mk ,以後在項目根目錄下執行命令 ndk-build 就能夠完成交叉編譯過程。

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni2.c

include $(BUILD_SHARED_LIBRARY)

Android.mk 能夠看做是小型的makefile,關於 Android.mk 的更多細節,限於篇幅,這裏不作詳細介紹請參考NDK自帶文檔,裏面有完整的介紹。 輸出的信息相似下面:

Gdbserver      : [arm-linux-androideabi-4.4.3] libs/armeabi/gdbserver
Gdbsetup       : libs/armeabi/gdb.setup
Compile thumb  : hello-jni <= hello-jni.c
SharedLibrary  : libhello-jni.so
Install        : libhello-jni.so => libs/armeabi/libhello-jni.so

上面的信息告訴咱們生成好的so文件路徑爲 libs/armeabi/libhello-jni.so 。至此一個簡單的NDK程序的已經制做完成。 總結一下大體過程是:

  • 編寫好Java源文件,使用靜態代碼段載入共享庫,並聲明native函數。以後編譯android項目
  • 使用 javah 工具生成頭文件
  • 根據頭文件編寫native函數
  • 利用 ndk-build 完成共享庫的編譯

native函數的動態註冊方法

上一節咱們經過一個簡單的實例,對NDK開發有了一個感性的認識。可是你也許會發現Java層上聲明的native函數與native上面的實現之間的關聯是經過javah生成頭文件完成的,這個方法顯得很笨拙。 實際上這種靜態註冊的方法是經過函數名(Java_com_example_hellojni_HelloJni_stringFromJNI )來創建聯繫。這種作法有諸多弊端:

  • 名字很長,沒有可讀性。
  • 每一個聲明瞭native函數的類都要生成一個對應的頭文件,對於真實的應用程序,類文件不少時不現實。
  • 每次載入都須要查詢native函數,效率低。

Android內部實現上,在使用JNI時很顯然並無這樣作,它採用了更加規範的 動態註冊的方法進行兩個層次上的關聯。

動態註冊版Hello-Jni

如下代碼是上面的 hell-jni.c 的動態註冊版,代碼中使用的是自定義的native函數名稱。

#include <string.h>
#include <jni.h>
#include <stdio.h>

jstring getHelloString();

static JNINativeMethod gMethods[] = {
    {
    "stringFromJNI",
    "()Ljava/lang/String;",
    (void *)getHelloString
    }
};

static int nMethods = 1;
static JNIEnv *env = NULL;

jstring getHelloString()
{
    char msg[100];
    sprintf(msg,"Hello from JNI.");
    return (*env)->NewStringUTF(env, msg);
}

jint JNI_OnLoad(JavaVM *vm,void *reserved){

    jint result = -1;
    jclass clz = NULL;
    if ((*vm)->GetEnv(vm,(void**) &env, JNI_VERSION_1_4) != JNI_OK){
        return -1;
    }
    clz = (*env)->FindClass(env,"com/example/hellojni/HelloJni");
    if((*env)->RegisterNatives(env,clz,gMethods,nMethods) < 0) {
        return -1;
    }
    return JNI_VERSION_1_4;//根據JNI規範,JNI_OnLoad必須返回版本號常量不然出錯。
}

根據Java的官方文檔1,當VM載入共享庫時,會尋找 jint JNI_OnLoad(JavaVM *vm, void *reserved) 函數,若是存在則再載入共享庫以後調用該函數。所以咱們能夠在該函數中完成native函數的註冊工做。 JNI_OnLoad 函數的參數有兩個,最主要就是JavaVM 結構。 JavaVM 是存儲VM信息的數據結構。更多信息將在後面講到,這裏咱們只須要知道,經過JavaVM指針咱們能夠獲得另外一個JNI核心結構—— JNIEnv , JNIEnv表明了整個JNI環境的數據結構,實際是一個函數表,其中存儲了JNI的各類相關操做的函數指針,後文會詳細介紹,在這裏咱們只須要知道在JNIEnv結構有如下的方法,經過調用就能夠實現動態註冊。

  • jclass FindClass(JNIEnv *env, const char *name) 傳入JNIEnv指針和類名稱返回表明這個類的結構2
  • jint RegisterNatives(JNIEnv *env, jclass clazz,const JNINativeMethod *methods, jint nMethods) 註冊native函數的函數3

JNINativeMethod結構

RegisterNatives 用來註冊一組native函數,其中使用到了 JNINativeMethod 結構,具體定義以下3

typedef struct { 

    char *name; //Java代碼中聲明的native函數的名稱

    char *signature; //對應Java代碼層native函數的簽名,下面會介紹

    void *fnPtr; //共享庫中函數指針

} JNINativeMethod;

這裏就涉及到了 函數簽名

函數簽名

Java容許函數重載,所以在註冊時就要具體區分出來,不然會出現混亂,於是這裏就要使用一種方法將每一個Java中的方法標上惟一的標記。這種方法就是 函數簽名 。函數簽名應該屬於JVM內部的規範,不具備可讀性。規定4以下:

類型標識 JAVA類型
Z boolean
B byte
C char
S short
I int
J long
F float
D double
L/java/lang/String; String
[I int[]
[L/java/lang/object; Object[]
V void

表1 類型標示對應表

每一個函數簽名大體格式 (<參數簽名>)返回值類型簽名 引用類型的參數簽名形式爲 L<包名>

JAVA函數 函數簽名
String f() ()L/java/lang/String;
void f(String s,AClass cls,long l) (L/java/lang/String;L/com/example/AClass;J)V
String f(byte[]) ([B)V

表2 一些簽名示例 函數看起來很難懂,咱們能夠利用 javap 工具查看類中的函數簽名那個信息,具體用法:

  1. 命令行轉到 $PROJECT/bin/classes 下($PROJECT表明Android程序項目根目錄,並假定java文件已經編譯好,存在bin目錄)
  2. 執行命令 javap -s com.example.helljni.HelloJni 其中com.example.hellojni.HelloJni 是類的完整名稱

小結

這一節中,經過動態註冊版的hello-jni代碼示例,簡要介紹如何在JNI中實現更靈活的動態註冊方法關聯Java層中native函數和Native層中的實現函數。JNI規範中規定VM在載入共享庫以後,要調用 JNI_OnLoad 函數,通常能夠在共享庫中實現該方法並完成動態註冊。 初步接觸了 JavaVM 結構和 JNIEnv 結構,並瞭解了 JNIEnv 的兩個「函數成員」FindClass 和 registerNatives 。以後還看到了JNI中保存關聯信息的JNINativeMethod 結構以及瞭解了Java的 函數簽名 。

兩個世界的數據互換

Java層和Native層之間如同兩個說着不一樣語言的國家同樣,若是要互相交流就必需要懂得對方的語言。在Native層中是如何表示Java層的數據類型呢?

基本數據類型和引用數據類型

JAVA數據類型 NATIVE層數據類型 符號屬性(UNSIGNED/SIGNED) 長度(BIT)
boolean jboolean unsigned 8
byte jbyte unsigned 8
char jchar unsigned 16
short jshort signed 16
int jint signed 32
long jlong signed 64
float jfloat signed 32
double jdouble signed 64

表3 基本數據類型轉換表

JAVA引用類型 NATIVE類型
全部object jobject
java.lang.Class jclass
java.lang.String jstring
Object[] jobjectArray
boolean[] jbooleanArray
byte[] jbyteArray
char[] jcharArray
short[] jshortArray
int[] jintArray
long[] jlongArray
float[] jfloatArray
double[] jdoubleArray
java.lang.Throwable jthrowable

表4 引用數據類型轉換表 Native層中將除String之外的類都做爲 jobject 處理,對於數組類型,只有基本數據類型的數組是單獨表示,其餘類型的都以 jobjectArray 類型存儲。

JavaVM

http://java.sun.com/docs/books/jni/html/functions.html JavaVM指針指向了一個表明整個VM的實例,同時對全部native線程都有效。主要有如下幾個接口可使用5

  • DestroyJavaVM 卸載整個VM實例
  • AttachCurrentThread 將當前的native線程attach到VM實例中,當線程加入到VM線程後,該線程就能夠調用諸如訪問Java對象、調用Java方法等JNI函數
  • DetachCurrentThread 與 AttachCurrentThread 相反
  • GetEnv 既能夠用來檢查當前線程是否已經attach到VM實例,還能夠獲得當前線程的JNIEnv結構。

JNIEnv

JNIEnv接口包含了JNI的主要功能的函數接口,注意JNIEnv是與線程相關的結構,JNIEnv接口實際是指向內部的一個函數集合,要在Native層操縱某個具體的類,或者調用方法,則須要 JNIEnv 。在native函數的動態註冊方法這一節就使用 JNIEnv 的函數進行了native函數的註冊。 JNIEnv 是指向一個函數表的指針的指針。 其具體定義以下6

typedef const struct JNINativeInterface *JNIEnv;
const struct JNINativeInterface ... = {

    NULL,
    NULL,
    NULL,
    NULL,
    GetVersion,

    DefineClass,
    FindClass,

    FromReflectedMethod,
    FromReflectedField,
    ToReflectedMethod,

    GetSuperclass,
    IsAssignableFrom,

    ToReflectedField,
    ....//還有不少,這裏略去
};

下圖是 JNIEnv 的一個簡單圖示7

http://liuyix.com/wordpress/wp-content/uploads/2012/07/wpid-jnienv.gif

JNIEnv能提供的功能很是多,大致能夠分爲如下幾類5

  • 取得JavaVM實例
  • Java對象實例的操做
    • 成員訪問
    • 方法調用
  • 靜態成員的訪問
  • String操做
  • Object操做
  • Array操做
  • Native方法的註冊,前文介紹過。
  • Global Reference & Local Reference
  • 提供VM版本信息
  • JNI的Exception
  • 對Java反射的支持

限於篇幅,在此沒法一一講解用法。僅說明較經常使用的幾個。更多詳細信息請參考Sun出版的JNI開發者指南(地址

經過JNIEnv在Native層對Java對象進行訪問和調用

經過JNIEnv提供的如下方法就能夠調用對象的方法

//調用對象方法的函數原型
NativeType Call<type>Method(JNIEnv *env, jobject obj,jmethodID methodID, ...);
NativeType Call<type>MethodA(JNIEnv *env, jobject obj,jmethodID methodID, jvalue *args);
NativeType Call<type>MethodV(JNIEnv *env, jobject obj,jmethodID methodID, va_list args);
//對對象成員操做的函數原型
NativeType Get<type>Field(JNIEnv *env, jobject obj,jfieldID fieldID);
void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID,NativeType value);
//取得methodID,fieldId的函數原型
jmethodID GetMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig);
jfieldID GetFieldID(JNIEnv *env, jclass clazz,const char *name, const char *sig);

前三個函數爲一組調用對象方法的函數,區別僅在於傳遞參數的方式不一樣。其中NativeType 表示Java方法返回值對應的Native類型,具體轉換見表3,表4。 <type> 是Void / Boolean / Int / Long / Object 等Java基本數據類型。調用這一組函數時,既須要傳遞對象的信息,還要傳遞方法的標識以及Java類中的方法的參數。 jobject 變量既能夠經過在Native層中調用 CallObjectMethod 獲得,也能夠經過後面提到的建立對象實例獲得。 methodId 則能夠經過 GetMethodID 取得。 jclass 參數能夠由前文提到的env->FindClass 函數取得。 相似地,還有 CallStatic<type>Method 、GetStatic<type>Field 、 SetStatic<type>Field 在此再也不贅述。

jstring

因爲String特別經常使用,且存在比較複雜的編碼問題,JNI特地將String類做爲一個獨立的Native層中的數據類型jstring處理。同其餘Object操做相似,jstring也是經過 JNIEnv 來管理的。主要的操做函數有:

jstring NewString(JNIEnv *env, const jchar *unicodeChars,jsize len);
void ReleaseStringChars(JNIEnv *env, jstring string,const jchar *chars);
const jchar * GetStringChars(JNIEnv *env, jstring string,jboolean *isCopy);
jsize GetStringLength(JNIEnv *env, jstring string);

jstring NewStringUTF(JNIEnv *env, const char *bytes);
void ReleaseStringUTFChars(JNIEnv *env, jstring string,const char *utf);
const char * GetStringUTFChars(JNIEnv *env, jstring string,jboolean *isCopy);
jsize GetStringUTFLength(JNIEnv *env, jstring string);

函數的功能能夠從名稱大體瞭解到,其中 New 開頭的都是將JNI中將String按照編碼分爲兩種,一種是Unicode編碼(UTF-16),一種是UTF-8編碼 須要注意的是Native層中並無垃圾自動回收機制,所以申請字符串資源,用完以後要進行釋放操做,不然會引發內存泄露。 使用過程當中還要注意:Unicode字符串不是「0結尾」的,所以不要依賴\u0000 進行字符串的操做。 常見的錯誤還包括調用 NewStringUTF 傳入的參數 bytes必須是 Modified UTF-8 格式的,不然會出現亂碼。8

jarray

Native層能夠經過操做jarray數據來處理Java層的數組類型。JNI中將基本類型Java數組和引用類型數組分開處理。 下面是幾個Java數組的例子。

int[] iarr; //基本類型數組
float[] farr;//基本類型數組
Object[] oarr;//引用類型數組,數組元素是Object
int[][] arr2;//引用類型數組,數組元素是 int[]

基本類型數組的操做

下表是基本類型數組操做的函數小結

JNI函數 描述
Get<Type>ArrayRegion 將基本類型數組的數據複製到預先申請好的C數組中或者反方向操做操做
Set<Type>ArrayRegion
Get<Type>ArrayElements 得到/釋放指向基本類型數組的數據的指針
Release<Type>ArrayElements
GetArrayLength 返回數組的長度
New<Type>Array 新建一個指定長度的數組
GetPrimitiveArrayCritical 得到/釋放指向基本類型數據的指針
ReleasePrimitiveArrayCritical

表5 基本數據類型數組的操做函數

引用類型數組的操做

下面以一個簡單的代碼片斷做爲說明9。假設某段Java代碼中聲明瞭如下的native函數

native int[][] get2DArray(int size);//返回 int[size][size]大小的二維數組

Native層能夠用如下代碼實現

jobjectArray get2DArray(jint size){
     jobjectArray result;
     int i;
     jclass intArrCls = (*env)->FindClass(env, "[I");
     if (intArrCls == NULL) {
         return NULL; /* exception thrown */
     }
     result = (*env)->NewObjectArray(env, size, intArrCls,
                                     NULL);
     if (result == NULL) {
         return NULL; /* out of memory error thrown 可能遇到空間不足*/
     }
     for (i = 0; i < size; i++) {
         jint tmp[256];  /* make sure it is large enough! */
         int j;
         jintArray iarr = (*env)->NewIntArray(env, size);
         if (iarr == NULL) {
             return NULL; /* out of memory error thrown */
         }
         for (j = 0; j < size; j++) {
             tmp[j] = i + j;
         }
         (*env)->SetIntArrayRegion(env, iarr, 0, size, tmp);
         (*env)->SetObjectArrayElement(env, result, i, iarr);
         (*env)->DeleteLocalRef(env, iarr);
     }
     return result;
}

上述代碼展現了 NewObjectArray 、 NewIntArray 、 SetObjectArrayElement 、SetIntArrayRegion 等函數的用法,代碼可讀性很高,這裏不作進一步解釋。

垃圾回收管理

Java做爲高級語言,具備垃圾自動回收管理機制,內存管理相對輕鬆。而C/C++則沒有這樣的機制,所以在Native層對象實例可能被垃圾回收。這裏就涉及到了JNI的對象引用的管理。 JNI支持三種引用類型—— LocalReference / GlobalReference /WeakGlobalReference ,每一種引用類型的生命週期是不一樣的。 大多數JNI函數使用的是 LocalReference ,即在函數中調用的」New」操做返回的都是對象的 LocalReference。 LocalReference 只在函數執行代碼範圍內有效,只要JNI函數一返回,引用就會被釋放。相對地, GlobalReference 能夠在多個函數之間共享,直到開發者本身調用釋放函數纔會被垃圾回收。另外一方面 WeakGlobalReference 則具備 引用緩存 功能——一方面它能夠像 GlobalReference 同樣跨函數共享引用,另外一方面它不會阻礙引用的垃圾回收過程。但JNI文檔中建議開發者使用 GlobalReference 和 LocalReference 替代WeakGlobalReference ,由於該引用隨時均可能會被垃圾回收,即便是在調用了IsSameObject 斷定引用有效以後仍然可能會失效10。 有關引用的操做有

//GlobalReference
jobject NewGlobalRef(JNIEnv *env, jobject obj);
void DeleteGlobalRef(JNIEnv *env, jobject globalRef);
//LocalReference
void DeleteLocalRef(JNIEnv *env, jobject localRef);
jobject NewLocalRef(JNIEnv *env, jobject ref);
//WeakLocalReference
jweak NewWeakGlobalRef(JNIEnv *env, jobject obj);
void DeleteWeakGlobalRef(JNIEnv *env, jweak obj);
//通用的引用操做
jobject AllocObject(JNIEnv *env, jclass clazz);
jobject NewObject(JNIEnv *env, jclass clazz,jmethodID methodID, ...);
jclass GetObjectClass(JNIEnv *env, jobject obj);
jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj);
jboolean IsSameObject(JNIEnv *env, jobject ref1,jobject ref2);

更多信息請參考官方文檔(地址)和JNI開發者指南(地址)

總結

本文大體介紹了Android NDK的相關技術以及NDK的基礎——JNI的使用,其中簡述了NDK的開發流程、函數註冊的兩種方式、JNI技術的基本內容,其中包括了Java層和Native層之間的數據轉換和互操做方法。不難發現,JNI技術擴展了原有Java技術的能力。

Footnotes:

1 Java Native Interface Specificationhttp://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/invocation.html

2http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/functions.html#wp16027

3http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/functions.html#wp17734

4 深刻理解Android:卷I pp28-29

5 Java Native Interface: Programmer’s Guide and Specificationhttp://java.sun.com/docs/books/jni/html/functions.html

6 JNIEnv定義http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/functions.html#wp23720

7 http://java.sun.com/docs/books/jni/html/objtypes.html#5190

8 Android Developers JNI Tipshttp://developer.android.com/guide/practices/design/jni.html#UTF\_8\_and\_UTF\_16\_strings

9 代碼改編自The Java Native Interface Programmer’s Guide and Specificationhttp://java.sun.com/docs/books/jni/html/objtypes.html#27791

10 http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/functions.html#weak

相關文章
相關標籤/搜索