Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 使用Android Studio進行JNI開發 - Mac篇

使用Android Studio進行JNI開發 - Mac篇

日期:2017/2/22 16:40:23      編輯:關於Android編程

對於入門級Android菜鳥的我來說,從配置到開發JNI是一個煎熬的過程,但還是取得了最終的成功。這裡主要是整個過程進行了整理,讓其他跟我一樣受煎熬的人盡早跳出來,繼續向光明邁進…

環境配置

開發JNI項目前提是需要有NDK(Native Development Kit)的支持。因此,在開發前需要先安裝和配置NDK。步驟如下:

點擊菜單”Tools” -> “Android” -> “SDK Manager”打開SDK管理器。

選中右邊面板的”SDK Tools”頁簽,勾選”NDK”一欄,然後點擊”Apply”來下載並安裝NDK(如下圖)。

Android SDK面板

第一個JNI例子

1. 新建Android項目

點擊菜單“File”-“New”-“New Project…”打開新建項目界面, 輸入項目名稱:

新建項目

選擇支持的平台及最低支持的系統版本

選擇項目模版Basic Activity

添加Activity

設置Activity的命名

設置Activity

至此,創建項目完成。

2. 設置項目支持JNI

打開gradle.properties文件,在該文件下添加:

android.useDeprecatedNdk=true

打開local.properties文件,在該文件下添加:

ndk.dir=NDK的路徑

再打開模塊的build.gradle文件,在android/defaultConfig下面添加ndk節點,如下所示:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"

    defaultConfig {
        applicationId "vimfung.cn.jnisample"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"

        ndk {
            moduleName "JNISample"
            stl "stlport_static"
            ldLibs "log"
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.android.support:design:23.4.0'
}

其中ndk節點下面的字段說明如下:

名稱 說明 moduleName 模塊名稱,即編譯出來的.so的名字 stl 默認情況下JNI中是無法使用STL標准庫的,加入此字段表示使用STL標准庫。 ldLibs “log”表示加入Android的調試日志,只要在導入#include

3. 編寫JNI接口

創建一個叫JNIUtil的Java類,並聲明一個使用native關鍵字修飾的test方法,代碼如下:

package vimfung.cn.jnisample;

/**
 * Created by vimfung on 16/9/8.
 */
public class JNIUtil
{
    public native String test ();
}

* 注:聲明jni的方法必須帶有native關鍵字,否則將視為一般的方法。設置native的方法允許為靜態/非靜態方法(即加或不加static關鍵字)。*

Command+F9(或菜單“Build”-“Make Project”)進行編譯,成功後點擊界面最下面的Terminal按鈕打開終端面板(終端會自動定位到當前項目目錄非常方便^o^)。使用cd命令跳轉到app/build/intermediates/classes/debug/目錄下,輸入腳本如下:

$ cd app/build/intermediates/classes/debug/

使用javah命令生成剛才創建的JNIUtil類的JNI的頭文件(.h文件),如:

javah vimfung.cn.jnisample.JNIUtil

* 這裡要注意的是javah要根據包名來對應目錄路徑來查找對應的.class文件,所以定位的目錄必須要在包目錄結構的上一級(即這裡的debug目錄),否則會提示找不到對應的類。*

執行成功後(執行成功是不會輸出任何信息的,錯誤了才會提示-_-#)會在debug目錄下多出一個vimfung_cn_jnisample_JNIUtil.h的頭文件,如下所示:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class vimfung_cn_jnisample_JNIUtil */

#ifndef _Included_vimfung_cn_jnisample_JNIUtil
#define _Included_vimfung_cn_jnisample_JNIUtil
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     vimfung_cn_jnisample_JNIUtil
 * Method:    test
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_vimfung_cn_jnisample_JNIUtil_test
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

可以看到剛才定義的test方法在頭文件中被聲明為Java_vimfung_cn_jnisample_JNIUtil_test 方法(其命名規則我猜應該是Java+“”+“包名1”+“” +“包名2”+“”…+“”+“類名”+“_”+“方法名”)。其中有jstring、JNIEnv、jobject這些就是JNI提供給C/C++調用的類型和接口,利用這些東西可以跟Java層進行一些交互。

回到Android Studio中,右鍵app目錄,在彈出菜單中選擇“New”-“Folder”-“JNI Folder”建立一個JNI的目錄。如圖:

創建JNI目錄

然後把剛才生成的頭文件拷貝到這個目錄下,最終效果如圖:

拷貝頭文件到JNI目錄

創建一個c++的文件,名字叫vimfung_cn_jnisample_JNIUtil.cpp,然後Java_vimfung_cn_jnisample_JNIUtil_test方法進行實現,如:

//
// Created by vimfung on 16/9/8.
//
#include "vimfung_cn_jnisample_JNIUtil.h"

JNIEXPORT jstring JNICALL Java_vimfung_cn_jnisample_JNIUtil_test(JNIEnv *env, jobject obj)
{
    return env -> NewStringUTF("Hello World!");
}

該方法直接通過env構造一個Java的字符串返回值並賦值為“Hello World!”進行返回。關於JNI提供的接口功能在後面的章節會進行介紹,我們這裡只要知道是返回字符串就可以了。

現在再打開JNIUtil的Java類,讓外部一旦使用該類時即加載JNISample.so這個庫,修改如下:

package vimfung.cn.jnisample;

/**
 * Created by vimfung on 16/9/8.
 */
public class JNIUtil
{
    static
    {
        System.loadLibrary("JNISample");
    }

    public native String test ();
}

最後,打開MainActivity.java文件,添加一個JNIUtil的屬性,並且在onCreate的時候初始化並調用test方法。這一步驟主要是驗證我們的JNI接口是否正常運行,修改的代碼如下:

public class MainActivity extends AppCompatActivity {

    private JNIUtil jniUtil;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        //...此處忽略已有代碼

        jniUtil = new JNIUtil();
        Log.v("test", jniUtil.test());
    }
}

編譯運行App,如果正常編譯運行,可以看到logcat中輸出Hello World!信息。如圖:

logcat輸出

這是Demo的地址:http://git.oschina.net/vimfung/JNISample

JNI類型接口介紹

JNI中定義了一系列的以j開頭的類型,下面以表格形式對其進行說明:

類型 說明 jboolean 布爾類型,對應一個無符號的char類型 jbyte 字節類型,對應一個有符號的char類型 jchar 字符類型,對應一個16位無符號整型 jshort 16位整型 jint 32位整型 jlong 64位整型 jfloat 32位浮點型 jdouble 64位浮點型 jsize 等同jint,用於表示大小,長度。 jobject 表示Java中的Object類型 jclass 表示Java中的Class類型,可以通過env的FindClass方法取得。 jstring 表示Java中的String類型 jarray 表示Java中的數組類型 jobjectArray 表示Java中的對象數組,如:Object arr[]; jbooleanArray 表示Java中的布爾數組,如:boolean arr[]; jbyteArray 表示Java中的字節數組,如:byte arr[]; jshortArray 表示Java中16位整型數組,如:short arr[]; jintArray 表示Java中32位整型數組,如:int arr[]; jlongArray 表示Java中64位整型數組,如:long arr[]; jfloatArray 表示Java中32位浮點型數組,如:float arr[]; jdoubleArray 表示Java中64位浮點型數組,如:double arr[]; jthrowable 表示Java中的異常對象Exception jweak 表示Java中的弱引用對象 jfieldID 表示Java類中的屬性標識符,可以通過env的GetFieldID或GetStaticFieldID等系列方法取得。 jmethodID 表示Java類中的方法標識符,可以通過env的GetMethodID或者GetStaticMethodID方法取得

對於每個JNI方法來說通常會有兩個參數,第一個參數是JNIEnv類型,代表了VM裡面的環境,本地的代碼可以通過該參數與Java代碼進行操作。第二個參數是定義JNI方法的類的一個本地引用(this)。

接下來介紹一下JNIEnv會提供一些什麼樣的方法(這裡只列舉我用過的方法,往後再慢慢補充^o^):

jclass FindClass(const char* name)

說明:
查找Java的類型,該方法可以通過傳入一個類名稱來查找對應的類型。

參數:
name - 類名稱(名稱是以斜桿分隔包名的方式,如:vimfung/cn/jnisample/JNIUtil)

返回:
一個類型對象。使用該對象可以取得其屬性、方法。


jobject NewGlobalRef(jobject obj)

說明:
創建一個全局引用對象。一旦對象在全局引用,則可以在多個方法中使用。因為Java有其自己的回收機制,因此在JNI中不可以使用static來維持對象的生命周期,所以,需要使用該方法來變更對象的生命周期。如果要釋放全局對象請使用DeleteGlobalRef方法。

參數:
obj - 需要全局引用的對象。

返回:全局的對象引用


void DeleteGlobalRef(jobject globalRef)

說明:
刪除一個全局的對象引用。主要用於刪除NewGlobalRef創建的對象引用。

參數:
globalRef - 全局的對象引用


void DeleteLocalRef(jobject localRef)

說明:
刪除一個本地的對象引用。一般情況下env創建的對象都屬於本地引用,如果不調用該方法,在JNI接口執行完畢後也會被自動回收掉。

參數:
localRef - 本地對象引用。


jobject NewLocalRef(jobject ref)

說明:
創建一個新的本地對象引用。

參數:
ref - 對象引用返回:本地對象引用


jobject NewObject(jclass clazz, jmethodID methodID, ...)

說明:
創建一個新的Java對象實例。

參數:
clazz - 需要創建實例的類型
methodID - 這裡需要傳入構造方法的標志
… - 構造方法的一個或者多個參數

返回:
對象的實例


jclass GetObjectClass(jobject obj)

說明:
獲取Java對象所屬的類型。相當於Java中的getClass方法。

參數:
obj - 對象

返回:
返回對象的類型


jboolean IsInstanceOf(jobject obj, jclass clazz)

說明:
判斷對象是否是指定類型的實例

參數:
obj - 要判斷的對象引用
clazz - 要檢查的類型

返回:
1 表示對象為該類型的實例,0 表示對象不是該類型的實例


jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)

說明:
由於在調用Java類某個方法時需要傳遞方法標識,而該方法就是用於獲取指定類的方法的標識。

參數:
clazz - 類型
name - 方法名稱,如:value,注:構造方法用表示
sig - 方法的簽名,如:(Ljava/lang/Object;)V。
對於方法簽名,其組織形式如:(參數類型1參數類型2 …)返回值類型。其中的類型參考下面章節:JNI的方法/屬性簽名中的類型對照表

返回:
方法標識(jmethodID)


void CallVoidMethod(jobject obj, jmethodID methodID, ...)
void CallVoidMethodV(jobject obj, jmethodID methodID, va_list args)

說明:
調用無返回值的方法時使用該方法。
其中CallVoidMethodV與CallVoidMethod的區別是,前者只有三個參數,其第三個參數是一個va_list類型,用於接收一個參數列表,該方法常用於被不定項參數方法嵌套時使用(即調用它的方法定義了…參數)。而後者是一個不定項參數方法。

參數:
obj - 要調用方法的對象實例
methodID - 方法標識,通過GetMethodID獲得。
… - 方法的參數,可以傳入一個或多個。args - 參數列表


jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)

說明:
獲取類型的屬性標識。在獲取或設置某個屬性值前,需要先得到屬性的標識(jfieldID),就需要調用此方法獲取。

參數:
clazz - 類型
name - 屬性名稱
sig - 屬性簽名,其描述方法為:屬性類型。類型請參考下面章節:JNI的方法/屬性簽名中的類型對照表

返回:屬性標識


jobject GetObjectField(jobject obj, jfieldID fieldID)
jboolean GetBooleanField(jobject obj, jfieldID fieldID)
jbyte GetByteField(jobject obj, jfieldID fieldID)
jchar GetCharField(jobject obj, jfieldID fieldID)
jshort GetShortField(jobject obj, jfieldID fieldID)
jint GetIntField(jobject obj, jfieldID fieldID)
jlong GetLongField(jobject obj, jfieldID fieldID)
jfloat GetFloatField(jobject obj, jfieldID fieldID)
jdouble GetDoubleField(jobject obj, jfieldID fieldID)

說明:
獲取指定對象的屬性值。該系列方法的聲明形式遵循“Get+類型+Field”。可以根據在Java中的不同類型屬性,選擇不同的方法來獲取屬性值。

參數:
obj - 要獲取屬性值的對象實例
fieldID - 屬性標識,可通過GetFieldID獲得。

返回:
屬性值


void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value)
void SetByteField(jobject obj, jfieldID fieldID, jbyte value)
void SetCharField(jobject obj, jfieldID fieldID, jchar value)
void SetShortField(jobject obj, jfieldID fieldID, jshort value)
void SetIntField(jobject obj, jfieldID fieldID, jint value)
void SetLongField(jobject obj, jfieldID fieldID, jlong value)
void SetFloatField(jobject obj, jfieldID fieldID, jfloat value)
void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value)

說明:
設置指定對象的屬性值。該系列方法的聲明形式遵循“Set+類型+Field”方式,可以根據在Java中的不同類型,選擇不同的方法進行調用。

參數:
obj - 要設置屬性值的對象實例
fieldID - 屬性標識,可通過GetFieldID獲得。
value - 屬性值


jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)

說明:
獲取指定類型的靜態方法(即帶static關鍵字描述)的標識。

參數:
clazz - 要獲取方法標識的類型
name - 方法的名稱
sig - 方法的簽名,如:(Ljava/lang/Object;)V。組織形式跟GetMethodID相同。

返回:
方法標識(fmethodID)


void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...)
void CallStaticVoidMethodV(jclass clazz, jmethodID methodID, va_list args)

說明:
調用類型的靜態方法。所調用的靜態方法無返回值時使用。
其中CallStaticVoidMethodV與CallStaticVoidMethod的區別是,前者只有三個參數,其第三個參數是一個va_list類型,用於接收一個參數列表,該方法常用於被不定項參數方法嵌套時使用(即調用它的方法定義了…參數)。而後者是一個不定項參數方法。

參數:
clazz - 調用方法的類型
methodID - 方法標識
… - 方法的參數,可以是一個或者多個。
args - 參數列表。


jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)

說明:
獲取類型的靜態屬性(即帶static關鍵字描述)的標識。

參數:
clazz - 要獲取屬性標識的類型
name - 屬性名稱
sig - 屬性簽名,與GetFieldID相同。


jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)
jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID)
jbyte GetStaticByteField(jclass clazz, jfieldID fieldID)
jchar GetStaticCharField(jclass clazz, jfieldID fieldID)
jshort GetStaticShortField(jclass clazz, jfieldID fieldID)
jint GetStaticIntField(jclass clazz, jfieldID fieldID)
jlong GetStaticLongField(jclass clazz, jfieldID fieldID)
jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID)
jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID)

說明:
獲取指定類型的靜態屬性值。該系列方法的聲明形式遵循“GetStatic+類型+Field”。可以根據在Java中的不同類型屬性,選擇不同的方法來獲取屬性值。

參數:
clazz - 要獲取屬性的類型
fieldID - 屬性標識,可通過GetStaticFieldID方法獲得。

返回:
屬性值


void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value)
void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value)
void SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte value)
void SetStaticCharField(jclass clazz, jfieldID fieldID, jchar value)
void SetStaticShortField(jclass clazz, jfieldID fieldID, jshort value)
void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value)
void SetStaticLongField(jclass clazz, jfieldID fieldID, jlong value)
void SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat value)
void SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble value)

說明:
設置指定類型的屬性值。該系列方法的聲明形式遵循“SetStatic+類型+Field”方式,可以根據在Java中的不同類型,選擇不同的方法進行調用。

參數:
clazz - 要設置屬性的類型
fieldID - 屬性標識,可通過GetStaticFieldID方法獲得。
value - 屬性值


jstring NewString(const jchar* unicodeChars, jsize len)

說明:
創建一個Unicode格式的字符串

參數:
unicodeChars - 字符串內容
len - 字符串長度

返回:
字符串對象


jsize GetStringLength(jstring string)

說明:
獲取Unicode格式的字符串長度

參數:
string - 字符串

返回:
字符串長度


const jchar* GetStringChars(jstring string, jboolean* isCopy)

說明:
將jstring轉換成為Unicode格式的char*字符串

參數:
string - 要轉換的字符串
isCopy - 該參數用於獲取當前的char*字符串是否為字符串對象的一份拷貝。http://blog.sina.com.cn/s/blog_78eb91cd0102uzv6.html 這篇博文裡面對其有描述:

當從 JNI 函數 GetStringChars 中返回得到字符串B時,如果B是原始字符串java.lang.String 的拷貝,則isCopy被賦值為 JNI_TRUE。如果B和原始字符串指向的是JVM中的同一份數據,則 isCopy被賦值為 JNI_FALSE。當 isCopy值為JNI_FALSE時,本地代碼決不能修改字符串的內容,否則JVM中的原始字符串也會被修改,這會打破 JAVA語言中字符串不可變的規則。通常,因為你不必關心 JVM 是否會返回原始字符串的拷貝,你只需要為 isCopy傳遞NULL作為參數。

返回:
Unicode格式的char*字符串


void ReleaseStringChars(jstring string, const jchar* chars)

說明:
釋放指向Unicode格式字符串的char*字符串指針

參數:
string - 與char*關聯的Unicode格式字符串
chars - 要釋放的char *字符串


jstring NewStringUTF(const char* bytes)

說明:
創建一個UTF-8格式的字符串

參數:
bytes - char*字符串

返回:
UTF-8格式的字符串對象


jsize GetStringUTFLength(jstring string)

說明:
獲取UTF-8格式字符串的長度

參數:
string - UTF-8格式的字符串

返回:
字符串長度


const char* GetStringUTFChars(jstring string, jboolean* isCopy)

說明:
將jstring轉換成為UTF-8格式的char*字符串

參數:
string - 要轉換的字符串
isCopy - 該參數用於獲取當前的char*字符串是否為字符串對象的一份拷貝。

返回:
UTF-8格式的char*字符串


void ReleaseStringUTFChars(jstring string, const char* utf)

說明:
釋放指向UTF-8格式字符串的char*字符串指針

參數:
string - 與char*關聯的UTF-8格式字符串
utf - 要釋放的char *字符串


jsize GetArrayLength(jarray array)

說明:
獲取一個數組所包含的元素數量

參數:
array - 數組對象

返回:
數組長度


jobjectArray NewObjectArray(jsize length, jclass elementClass, > > > jobject initialElement)
jbooleanArray NewBooleanArray(jsize length)
jbyteArray NewByteArray(jsize length)
jcharArray NewCharArray(jsize length)
jshortArray NewShortArray(jsize length)
jintArray NewIntArray(jsize length)
jlongArray NewLongArray(jsize length)
jfloatArray NewFloatArray(jsize length)
jdoubleArray NewDoubleArray(jsize length)

說明:
創建一個對象數組,該系列方法聲明形式遵循“New+類型+Array”。可以根據自己需要保存的類型來調用不同的方法創建不同類型的數組。

參數:
length - 數組長度
elementClass - 元素類型,ObjectArray特有
initialElement - 元素的初始值,ObjectArray特有

返回:
數組對象


jobject GetObjectArrayElement(jobjectArray array, jsize index)

說明:
獲取對象數組的元素

參數:
array - 數組對象
index - 要獲取元素的下標索引

返回:
數組元素對象


void SetObjectArrayElement(jobjectArray array, jsize index, jobject value)

說明:
設置對象素組的元素

參數:
array - 數組對象
index - 插入元素的下標索引
value - 要設置的元素


jboolean* GetBooleanArrayElements(jbooleanArray array, jboolean* isCopy)
jbyte* GetByteArrayElements(jbyteArray array, jboolean* isCopy)
jchar* GetCharArrayElements(jcharArray array, jboolean* isCopy)
jshort* GetShortArrayElements(jshortArray array, jboolean* isCopy)
jint* GetIntArrayElements(jintArray array, jboolean* isCopy)
jlong* GetLongArrayElements(jlongArray array, jboolean* isCopy)
jfloat* GetFloatArrayElements(jfloatArray array, jboolean* isCopy)
jdouble* GetDoubleArrayElements(jdoubleArray array, jboolean* isCopy)

說明:
將基礎類型數組轉化為類型指針,返回的指針對象可以通過指針偏移來獲取不同下標索引的元素(如:(ptr+1)、(ptr+2)….)。該系列方法聲明遵循“Get+類型+ArrayElements”形式,可以根據數組的類型調用相對應的方法。

參數:
array - 基礎類型數組對象
isCopy - 該參數用於獲取當前的指針是否為數組的一份拷貝。

返回:
數組指針


void ReleaseBooleanArrayElements(jbooleanArray array, jboolean* elems, jint mode)
void ReleaseByteArrayElements(jbyteArray array, jbyte* elems, jint mode)
void ReleaseCharArrayElements(jcharArray array, jchar* elems, jint mode)
void ReleaseShortArrayElements(jshortArray array, jshort* elems, jint mode)
void ReleaseIntArrayElements(jintArray array, jint* elems, jint mode)
void ReleaseLongArrayElements(jlongArray array, jlong* elems, jint mode)
void ReleaseFloatArrayElements(jfloatArray array, jfloat* elems, jint mode)
void ReleaseDoubleArrayElements(jdoubleArray array, jdouble* elems, jint mode)

說明:
釋放指向基礎類型數組的數組指針。該系列方法聲明遵循“Release+類型+ArrayElements”形式,可以根據數組的類型調用相對應的方法。

參數:
array - 與指針關聯的基礎類型數組
elems - 數組指針
mode - 釋放模式,官網對其的說明如下:

The last argument to the ReleaseByteArrayElements function above can have the following values:

0: Updates to the array from within the C code are reflected in the Java language copy.

JNI_COMMIT: The Java language copy is updated, but the local jbyteArray is not freed.

JNI_ABORT: Changes are not copied back, but the jbyteArray is freed. The value is used only if the array is obtained with a get mode of JNI_TRUE meaning the array is a copy.


void GetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, jboolean* buf)
void GetByteArrayRegion(jbyteArray array, jsize start, jsize len, jbyte* buf)
void GetCharArrayRegion(jcharArray array, jsize start, jsize len, jchar* buf)
void GetShortArrayRegion(jshortArray array, jsize start, jsize len, jshort* buf)
void GetIntArrayRegion(jintArray array, jsize start, jsize len, jint* buf)
void GetLongArrayRegion(jlongArray array, jsize start, jsize len, jlong* buf)
void GetFloatArrayRegion(jfloatArray array, jsize start, jsize len, jfloat* buf)
void GetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, jdouble* buf)

說明:
獲取指定基礎類型數組的多個元素。該系列方法聲明遵循“Get+類型+ArrayRegion”形式,可以根據數組的類型選擇對應的方法進行調用。取得元素後可以從buf中獲取相應的元素。

參數:
array - 基礎類型數組
start - 要獲取元素的起始下標索引
len - 要獲取元素的個數
buf - 用來存儲元素的指針變量,必須要為指針變量申請內存並且內存長度要大於獲取內容的長度。


void SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, const jboolean* buf)
void SetByteArrayRegion(jbyteArray array, jsize start, jsize len, const jbyte* buf)
void SetCharArrayRegion(jcharArray array, jsize start, jsize len, const jchar* buf)
void SetShortArrayRegion(jshortArray array, jsize start, jsize len, const jshort* buf)
void SetIntArrayRegion(jintArray array, jsize start, jsize len, const jint* buf)
void SetLongArrayRegion(jlongArray array, jsize start, jsize len, const jlong* buf)
void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len, const jfloat* buf)
void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, const jdouble* buf)

說明:
設置基礎類型數組的多個元素值。該系列方法聲明遵循“Set+類型+ArrayRegion”形式。可以根據數組的類型選擇對應的方法進行調用。

參數:
array - 基礎類型數組
start - 要替換元素對應在array中的起始下標索引
len - 替換元素的數量
buf - 替換元素的指針變量,從該指針中讀取輸入放入數組中。


jweak NewWeakGlobalRef(jobject obj)

說明:
創建一個全局的弱引用對象

參數:
obj - 需要創建弱引用的對象引用

返回:
弱引用對象


void DeleteWeakGlobalRef(jweak obj)

說明:
刪除一個全局弱引用

參數:
obj - 全局的弱引用對象

JNI的方法/屬性簽名中的類型對照表

標記 說明 V 無類型,用於表示返回值 B 字節類型(byte) D 雙精度浮點型(double) Z 布爾類型(boolean) I 整型(int) L類型(斜桿分隔包名); 引用類型,需要以L開頭;結尾,如:Ljava/lang/Object; [類型(包括基礎類型和引用類型) 數組類型,需要以[開頭,如:[B或者[Ljava/lang/Object;

給Demo增加一個操作Java對象的接口

一般情況下,JNI接口都會與Java層進行一些交互,那麼,就必須要用到env中提供的一些方法實現這類的需求。下面我再添加一個JNI的接口,該接口主要功能是實現在原生代碼中創建一個HashMap並填充數據,最後返回給Java層。

先打開JNIUtil定義一個新的方法:

public native HashMap createHashMap (String key1, String value1, String key2, String value2);

Command+F9編譯項目,成功後再次使用Terminal對新編譯的JNIUtil生成頭文件(是的,每次添加新接口都需要這樣做),然後拷貝到JNI目錄下覆蓋原有文件。生成的頭文件會多出一個新的接口聲明,如下所示:

JNIEXPORT jobject JNICALL Java_vimfung_cn_jnisample_JNIUtil_createHashMap (JNIEnv *, jobject, jstring, jstring, jstring, jstring);

然後再vimfung_cn_jnisample_JNIUtil.cpp中寫入它的方法實現,代碼如下:

JNIEXPORT jobject JNICALL Java_vimfung_cn_jnisample_JNIUtil_createHashMap
        (JNIEnv *env, jobject thiz, jstring key1, jstring value1, jstring key2, jstring value2)
{
    //為了方便日後獲取其他地方需要使用HashMap的類型,在這裡可以定義為static,然後對jclass創建全局的內存引用。
    static jclass hashMapClass = NULL;
    if (hashMapClass == NULL)
    {
        jclass hashMapClassLocal = env -> FindClass("java/util/HashMap");
        hashMapClass = (jclass)env -> NewGlobalRef(hashMapClassLocal);
        env->DeleteLocalRef(hashMapClassLocal);
    }

    //獲取構造方法標識和插入數據方法標識
    static jmethodID initMethodId = env -> GetMethodID(hashMapClass, "", "()V");
    static jmethodID putMethodId = env -> GetMethodID(hashMapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");

    //創建HashMap對象
    jobject hashMapInstance= env -> NewObject(hashMapClass, initMethodId);

    //放入對象到HashMap中,相當於調用HashMap的put方法
    env -> CallObjectMethod(hashMapInstance, putMethodId, key1, value1);
    env -> CallObjectMethod(hashMapInstance, putMethodId, key2, value2);

    return hashMapInstance;
}

上面的代碼完整地演示了如何在JNI中操作一個HashMap,從通過FindClass找到HashMap的類型,再到GetMethodID獲取HashMap的方法,最後通過CallObjectMethod來調用HashMap的方法來創建對象並放入數據,這些過程對於操作每個Java類都是必須的。

這個方法同時也演示了如何使用NewGlobalRef來創建可跨方法的訪問的對象,其中的hashMapClass正是這樣的處理(只要把hashMapClass放到方法外面定義就可以實現跨方法訪問了)。這裡要謹記的一點是,只有static描述一個變量是不足以維持jobject的生命周期的,因為JVM會管理這些對象的生命周期,因此,需要使用NewGlobalRef來把一個本地的對象引用提升到全局的對象引用,就可以實現跨方法訪問對象了。

最後再MainActivity的onCreate中調用createHashMap方法並輸出它的內容:

Log.v("create HashMap = %s", jniUtil.createHashMap("Fist Name", "Vim", "Last Name", "Fung").toString());

最後編譯運行,如果一切順利的話可以在logcat中看到下面的信息:

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved