NDK開發 - JNI數組數據處理

不少時候利用 NDK 開發都是爲了對數據進行加密操做,由於單純的 Java 太容易被反編譯了,加密算法也就很容易被破解,而利用 C/C++ 開發能夠加大破解難度。文件的數據加密就須要經過 byte 數組傳給 JNI。java

傳送門:NDK開發 - JNI數組數據處理c++

JNI 中的數組分爲基本類型數組和對象數組,它們的處理方式是不同的,基本類型數組中的全部元素都是 JNI 的基本數據類型,能夠直接訪問。而對象數組中的全部元素是一個類的實例或其它數組的引用,和字符串操做同樣,不能直接訪問 Java 傳遞給 JNI 層的數組,必須選擇合適的 JNI 函數來訪問和設置 Java 層的數組對象。算法

訪問基本類型數組

Java 代碼:shell

//調用數組
byte array[] = {'A', 'B', 'C', 'D', 'E'};
byte[] resutl = NativeMethod.getByteArray(array);
for (int i = 0; i < array.length; i++) {
    Log.d(TAG, "ARRAY : " + array[i] + "->" + resutl[i]);
}

native 代碼:數組

JNIEXPORT jbyteArray JNICALL Java_com_example_gnaix_ndk_NativeMethod_getByteArray
        (JNIEnv *env, jclass object, jbyteArray j_array){
    //1. 獲取數組指針和長度
    jbyte *c_array = env->GetByteArrayElements(j_array, 0);
    int len_arr = env->GetArrayLength(j_array);

    //2. 具體處理
    jbyteArray c_result = env->NewByteArray(len_arr);
    jbyte buf[len_arr];
    for(int i=0; i<len_arr; i++){
        buf[i] = c_array[i] + 1;
    }

    //3. 釋放內存
    env->ReleaseByteArrayElements(j_array, c_array, 0);

    //4. 賦值
    env->SetByteArrayRegion(c_result, 0, len_arr, buf);
    return c_result;
}

運行結果:app

5.png

  示例中,從 Java 層中傳進去了一個數組,參數類型是 byte[], 對應 JNI 中 jbyteArray 類型。利用 GetByteArrayElements 函數獲取數組指針,第二個參數返回的數組指針是原始數組,仍是拷貝原始數據到臨時緩衝區的指針,若是是 JNI_TRUE:表示臨時緩衝區數組指針,JNI_FALSE:表示臨時原始數組指針。開發當中,咱們並不關心它從哪裏返回的數組指針,這個參數填 NULL 便可,但在獲取到的指針必須作校驗。
相似的函數還有 GetIntArrayElements , GetFloatArrayElements , GetDoubleArrayElements 等。
  而後對數據進行了處理,而且釋放了數組內存。ReleaseByteArrayElements 是與 GetByteArrayElements 對應使用的。函數

  除了上述方法還能夠用 JNI 中的 GetXXArrayRegion 函數進行實現數據操做, 對應的 SetXXArrayRegion 上述也有提到過。ui

JNIEXPORT jint JNICALL Java_com_example_gnaix_ndk_NativeMethod_getByteArray
        (JNIEnv *env, jclass object, jbyteArray j_array){
    jbyte *c_array;
    jint len_arr;

    //1. 獲取數組長度
    len_arr = env->GetArrayLength(j_array);

    //2. 申請緩衝區
    c_array = (jbyte *) malloc(sizeof(jbyte) * len_arr);

    //3. 初始化緩衝區
    memset(c_array, 0, sizeof(jbyte)*len_arr);

    //4. 拷貝java數組數據
    env->GetByteArrayRegion(j_array, 0, len_arr, c_array);

    //5. 計算總和
    int sum = 0;
    for(int i=0; i<len_arr; i++){
        sum += c_array[i];
    }

    //6. 釋放緩衝區
    free(c_array);
    return sum;
}

訪問引用類型數組

  JNI 提供了兩個函數來訪問對象數組,GetObjectArrayElement 返回數組中指定位置的元素,SetObjectArrayElement 修改數組中指定位置的元素。與基本類型不一樣的是,咱們不能一次獲得數據中的全部對象元素或者一次複製多個對象元素到緩衝區。由於字符串和數組都是引用類型,只能經過 Get/SetObjectArrayElement 這樣的 JNI 函數來訪問字符串數組或者數組中的數組元素。
  下面的例子同調用 Native 方法 建立一個對象數組,返回後打印這個數組內容:this

//返回對象數組
Person persons[] =  NativeMethod.getPersons();
for(Person person : persons){
    person.toString();
}

Person 類:加密

/**
 * 名稱: Person
 * 描述:
 *
 * @author xiangqing.xue
 * @date 16/3/10
 */
public class Person {
    private String TAG = "Person";

    public String name;
    public int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String toString(){
        String result =  "Name: " + name + ", age: " + age;
        Log.d(TAG, result);
        return result;
    }
}

Native 方法:

JNIEXPORT jobjectArray JNICALL Java_com_example_gnaix_ndk_NativeMethod_getPersons
        (JNIEnv *env, jclass object){
    jclass clazz = NULL;
    jobject jobj = NULL;
    jmethodID mid_construct = NULL;
    jfieldID fid_age = NULL;
    jfieldID fid_name = NULL;
    jstring j_name;

    //1. 獲取Person類的Class引用
    clazz = env->FindClass("com/example/gnaix/ndk/Person");
    if(clazz == NULL){
        LOGD("clazz null");
        return NULL;
    }

    //2. 獲取類的默認構造函數ID
    mid_construct = env->GetMethodID(clazz, "<init>", "()V");
    if(mid_construct == NULL){
        LOGD("construct null");
        return NULL;
    }

    //3. 獲取實例方法ID和變量ID
    fid_name = env->GetFieldID(clazz, "name", "Ljava/lang/String;");
    fid_age = env->GetFieldID(clazz, "age", "I");
    if(fid_age==NULL || fid_name==NULL){
        LOGD("age|name null");
        return NULL;
    }

    //4. 處理單個對象並添加到數組
    int size = 5;
    jobjectArray obj_array = env->NewObjectArray(size, clazz, 0);
    for(int i=0; i<size; i++){
        jobj = env->NewObject(clazz, mid_construct);
        if(jobj == NULL){
            LOGD("jobject null");
            return NULL;
        }
        env->SetIntField(jobj, fid_age, 23 + i);
        j_name = env->NewStringUTF("gnaix");
        env->SetObjectField(jobj, fid_name, j_name);
        env->SetObjectArrayElement(obj_array, i, jobj);
    }


    //5. 釋放局部變量
    env->DeleteLocalRef(clazz);
    env->DeleteLocalRef(jobj);
    return obj_array;
}

運行結果:

  Native 方法首先調用 JNI 函數 FindClass 獲取 Person 類的引用,若是 Person 類加載失敗的話, FindClass 會返回 NULL, 而後拋出一個 java.lang.NoClassDefFoundError 異常。
  接下來經過 GetMethodID 獲取了類的默認構造函數的ID(下一節會介紹)。而且經過 GetFieldId 獲取了Person變量的ID,用於後面的賦值。
  調用 NewObjectArray 建立一個數組,在for循環中 NewObject 實例化 Person類,並經過 SetXXField 函數給實例變量賦值。SetObjectArrayElement 將實例化對象插入數組。
  最後調用 DeleteLocalRef 方法釋放局部變量。 DeleteLocalRef 將新建立的 引用從引用表中移除。在 JNI 中,只有 jobject 以及子類屬於引用變量,會佔用引用表的空間,jint,jfloat,jboolean 等都是基本類型變量,不會佔用引用表空間,即不須要釋放。引用表最大空間爲 512 個,若是超出這個範圍,JVM 就會掛掉。

方法簽名

  在上面的的例子中,在調用實例變量或者方法,都必須傳入一個 jmethodID 的參數。由於在 Java 中存在方法重載(方法名相同,參數列表不一樣),因此要明確告訴 JVM 調用的是類或實例中的哪個方法。調用 JNI 的 GetMethodID 函數獲取一個 jmethodID 時,須要傳入一個方法名稱和方法簽名,方法名稱就是在 Java 中定義的方法名,方法簽名的格式爲:(形參參數類型列表)返回值。形參參數列表中,引用類型以 L 開頭,後面緊跟類的全路徑名(需將.所有替換成/),以分號結尾。
  能夠經過 javap 命令獲取類的簽名,以 Person 爲例:

javap -s -p app.build.intermediates.classes.all.debug.com.example.gnaix.ndk.Person

參數說明:

  • -s: 輸出內部類型簽名

  • -p: 顯示全部類和成員

Java 基本類型與方法簽名中參數類型和返回值類型的映射關係以下:

Field Descriptor Java Language Type
Z boolean
B byte
C char
S short
I int
J long
F float
D double

  好比,String fun(int a, float b, boolean c, String d) 對應的 JNI 方法簽名爲:(IFZLjava/lang/String;)Ljava/lang/String;

相關文章
相關標籤/搜索