JNI開發之JNI實踐

  在上一篇文章中介紹了JNI開發的環境搭建,這篇文章將用兩個實例來介紹JNI開發。JNI開發大致可以分爲兩類:一類是Java調用本地代碼方法;另外一類是本地方法訪問Java成員。接下來將分別介紹這兩種情況。


一、Java代碼調用本地方法

  在JNI原理那篇文章中,介紹了Java代碼調用本地方法的一般步驟。接下來將以實際的例子來描述Java代碼調用本地方法的過程。在這個例子中,在Java代碼中調用本地方法的排序方法,並將排序後的結果返回到Java代碼中。然後再調用本地方法中的翻轉方法,將數組翻轉,但結果不傳回Java代碼中。首先在Java代碼中定義本地方法heapSortNative和reverseArrayNative,然後定義一個待排序的數組。具體的代碼如下:


package com.example.javatonative;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;


public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
//定義一個需要排序的數組
private int[] mArray = {8,2,9,6,10,7,20,37,13,15};
    static {
        System.loadLibrary("JavaToNative");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.e(TAG, "*******************before sort*************");
printArray(mArray);
heapSortNative(mArray);
Log.e(TAG, "*******************after sort*************");
printArray(mArray);
reverseArrayNative(mArray);
Log.e(TAG, "*******************reverse array*************");
printArray(mArray);
}

private void printArray(int[] array){
for (int i = 0; i < array.length; i++){
            Log.e(TAG,"array[" + i + "]: " + array[i]);
}
    }

/**
     * 堆排序,調用本地方法完成排序,並將排序後的結果通過array返回
* @param array 待排序的數組
*/
private native void heapSortNative(int[] array);

/**
     * 翻轉數組
* @param array 待翻轉的數組
*/
private native void reverseArrayNative(int[] array);
}


  接下來需要在C/C++文件中,實現在Java代碼中定義的本地方法。代碼如下:

#include "com_example_javatonative_MainActivity.h"
#include <stdio.h>
#include <stdlib.h>
#include <android/log.h>

#ifdef __cplusplus
extern "C" {
#endif
#define LOG_TAG "JavaToNative"

//堆調整
void heapAdjust(int a[],int n,int i){
int child;//保存左右節點中較大的節點
int temp;
    temp = a[i];
while(2*i + 1 < n){
        child = 2*i + 1;
if(child != n -1 && a[child] < a[child+1]){
            child++;//右節點較大
}

if(a[child] > temp){
            a[i] = a[child];
        }else {
break;
        }
        i = child;//更新下一個對象
}
    a[i] = temp;
}

//對排序
void HeapSort(int a[],int n){
int i;
for(i = n/2; i >= 0; i--){
        heapAdjust(a,n,i);
    }
int j;
for(j = n - 1; j > 0;j--){
int temp = a[j];
        a[j] = a[0];
        a[0] = temp;
        heapAdjust(a,j,0);
    }
}

//數組翻轉
void ReverseArray(int a[],int n){
int i = 0;
int j = n - 1;
int temp;
while(i<j){
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
        i++;
        j--;
    }
}

void PrintArray(int arr[],int n){
int i;
for(i = 0; i < n; i++){
        __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,"arr[%d]=%d",i,arr[i]);
    }
}
/*
 * Class:     com_example_javatonative_MainActivity
 * Method:    heapSort
 * Signature: ([I)V
 */
JNIEXPORT void JNICALL Java_com_example_javatonative_MainActivity_heapSortNative(JNIEnv *env,
    jobject obj, jintArray array){
    //獲取數組的長度
int length = (*env)->GetArrayLength(env,array);
    //將Java對象的數組轉換爲C/C++類型的數組,並返回一個指向Java端數組內存地址的指針
jint* arrp = (*env)->GetIntArrayElements(env,array,0);
    //堆排序
HeapSort(arrp,length);
    //釋放C/C++數組,並更新Java數組內容
(*env)->ReleaseIntArrayElements(env,array,arrp,0);
}

/*
 * Class:     com_example_javatonative_MainActivity
 * Method:    heapSort
 * Signature: ([I)V
 */
JNIEXPORT void JNICALL Java_com_example_javatonative_MainActivity_reverseArrayNative(JNIEnv *env,
    jobject obj, jintArray array){
    //獲取數組的長度
jsize length = (*env)->GetArrayLength(env,array);
int a[length];
    //直接將Java端的數組拷貝到本地的數組中,這樣修改的是本地的值,不會更新到Java端的數組
(*env)->GetIntArrayRegion(env,array,0,length,a);
    //翻轉數組
ReverseArray(a,length);
    //打印數組
PrintArray(a,length);
}

#ifdef __cplusplus
}
#endif

  

  在上述代碼中有詳細的註釋,就不再描述代碼的功能了。最後看下代碼執行的結果:

排序前的結果(Java日誌輸出)
05-19 14:16:27.933 E/MainActivity(15433): *******************before sort*************
05-19 14:16:27.934 E/MainActivity(15433): array[0]: 8
05-19 14:16:27.934 E/MainActivity(15433): array[1]: 2
05-19 14:16:27.934 E/MainActivity(15433): array[2]: 9
05-19 14:16:27.934 E/MainActivity(15433): array[3]: 6
05-19 14:16:27.934 E/MainActivity(15433): array[4]: 10
05-19 14:16:27.934 E/MainActivity(15433): array[5]: 7
05-19 14:16:27.934 E/MainActivity(15433): array[6]: 20
05-19 14:16:27.934 E/MainActivity(15433): array[7]: 37
05-19 14:16:27.934 E/MainActivity(15433): array[8]: 13
05-19 14:16:27.935 E/MainActivity(15433): array[9]: 15

排序後的結果(Java日誌輸出)
05-19 14:16:27.935 E/MainActivity(15433): *******************after sort*************
05-19 14:16:27.935 E/MainActivity(15433): array[0]: 2
05-19 14:16:27.935 E/MainActivity(15433): array[1]: 6
05-19 14:16:27.935 E/MainActivity(15433): array[2]: 7
05-19 14:16:27.935 E/MainActivity(15433): array[3]: 8
05-19 14:16:27.935 E/MainActivity(15433): array[4]: 9
05-19 14:16:27.935 E/MainActivity(15433): array[5]: 10
05-19 14:16:27.935 E/MainActivity(15433): array[6]: 13
05-19 14:16:27.935 E/MainActivity(15433): array[7]: 15
05-19 14:16:27.935 E/MainActivity(15433): array[8]: 20
05-19 14:16:27.935 E/MainActivity(15433): array[9]: 37
05-19 14:16:27.935 E/MainActivity(15433): *******************reverse array*************

翻轉數組(Native日誌輸出)
05-19 14:16:27.936 D/JavaToNative(15433): arr[0]=37
05-19 14:16:27.936 D/JavaToNative(15433): arr[1]=20
05-19 14:16:27.936 D/JavaToNative(15433): arr[2]=15
05-19 14:16:27.936 D/JavaToNative(15433): arr[3]=13
05-19 14:16:27.936 D/JavaToNative(15433): arr[4]=10
05-19 14:16:27.936 D/JavaToNative(15433): arr[5]=9
05-19 14:16:27.936 D/JavaToNative(15433): arr[6]=8
05-19 14:16:27.936 D/JavaToNative(15433): arr[7]=7
05-19 14:16:27.936 D/JavaToNative(15433): arr[8]=6
05-19 14:16:27.937 D/JavaToNative(15433): arr[9]=2

翻轉數組(Java日誌輸出)
05-19 14:16:27.937 E/MainActivity(15433): array[0]: 2
05-19 14:16:27.937 E/MainActivity(15433): array[1]: 6
05-19 14:16:27.937 E/MainActivity(15433): array[2]: 7
05-19 14:16:27.937 E/MainActivity(15433): array[3]: 8
05-19 14:16:27.937 E/MainActivity(15433): array[4]: 9
05-19 14:16:27.937 E/MainActivity(15433): array[5]: 10
05-19 14:16:27.937 E/MainActivity(15433): array[6]: 13
05-19 14:16:27.937 E/MainActivity(15433): array[7]: 15
05-19 14:16:27.937 E/MainActivity(15433): array[8]: 20
05-19 14:16:27.937 E/MainActivity(15433): array[9]: 37

  從代碼執行結果來看,Java調用本地方法完成了排序操作,並把結果返回到Java代碼中了。Java代碼調用了本地方法翻轉數組,但是結果並沒有同步到Java代碼中。

  完整代碼例子在:https://github.com/qiubing/JavaToNative



二、本地方法訪問Java成員

  在JNI原理那篇文章中,介紹了本地方法訪問Java成員的一些方法。接下來將以實際的例子講述本地方法訪問Java成員的過程。在這個例子,將通過本地方法來Java成員中的靜態屬性和非靜態屬性以及調用Java成員中的方法。首先在Java代碼中定義靜態屬性sNumber和非靜態屬性mName,以及Java方法showMessage(),最後定義一個本地方法,通過該本地方法來訪問Java成員。具體的代碼如下:

package com.example.nativetojava;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;


public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
    private String mName = "Java";
    private static int sNumber = 100;

    static {
        System.loadLibrary("NativeToJava");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String msg = "java to native now";
showMessage(msg);
callbackFromNative();
}

private void showMessage(String message){
        Log.e(TAG, "showMessage()....mName = " + mName + ",sNumber = " + sNumber
+ ",message: " + message);
Toast.makeText(this,message,Toast.LENGTH_LONG).show();
}

private native void callbackFromNative();
}


  在本地方法中,將獲取Java類對象,然後通過Java類對象獲取屬性ID和方法ID,最後通過屬性ID和方法ID來訪問Java的成員。本地代碼實現如下:

#include "com_example_nativetojava_MainActivity.h"
#include <android/log.h>
#include <stdlib.h>
#include <stdio.h>

#define LOG_TAG "NativeToJava"
/*
 * Class:     com_example_nativetojava_MainActivity
 * Method:    callbackFromNative
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_nativetojava_MainActivity_callbackFromNative
  (JNIEnv *env, jobject thiz){
      __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,"callbackFromNative()...");
      //獲取Java類對象
jclass clazz = env->FindClass("com/example/nativetojava/MainActivity");
      //獲取mName屬性ID
      jfieldID name = env->GetFieldID(clazz,"mName","Ljava/lang/String;");
      //獲取sNumber靜態屬性ID
      jfieldID number = env->GetStaticFieldID(clazz,"sNumber","I");
      //獲取showMessage方法ID
      jmethodID showMessage = env->GetMethodID(clazz,"showMessage","(Ljava/lang/String;)V");

      //獲取mName屬性的值
jstring nameStr = (jstring)env->GetObjectField(thiz,name);
      //將Java中的字符串轉換爲C/C++中的字符數組
const char *temp = env->GetStringUTFChars(nameStr,NULL);
      __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,"callbackFromNative()...name:%s",temp);
      //釋放C/C++數組
env->ReleaseStringUTFChars(nameStr,temp);
      //創建一個String對象
jstring nativeStr = env->NewStringUTF("Native");
      //更新Java中的mName屬性值
env->SetObjectField(thiz,name,nativeStr);

      //獲取sNumber靜態屬性的值
jint num = env->GetStaticIntField(clazz,number);
      __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,"callbackFromNative()...number:%d",num);
      //更新sNumber靜態屬性的值
env->SetStaticIntField(clazz,number,num+100);
      jstring message = env->NewStringUTF("native to java now");
      //調用Java中的showMessage方法
env->CallVoidMethod(thiz,showMessage,message);
 }

  需要注意的是在這裏的本地代碼實現採用的C++語言實現的,比C語言實現的方法要少一個參數JNIEnv。上面例子的本地代碼實現採用的是C語言實現,可以對比一下差異。

  代碼的執行結果如下:

05-19 13:59:05.940 E/MainActivity( 8026): showMessage()....mName = Java,sNumber = 100,message: java to native now  //調用本地方法前
05-19 13:59:05.957 D/NativeToJava( 8026): callbackFromNative()...  //本地方法調用開始
05-19 13:59:05.957 D/NativeToJava( 8026): callbackFromNative()...name:Java  //讀取Java成員mName的值
05-19 13:59:05.958 D/NativeToJava( 8026): callbackFromNative()...number:100  //讀取Java成員的sNumber的值
05-19 13:59:05.958 E/MainActivity( 8026): showMessage()....mName = Native,sNumber = 200,message: native to java now  //調用本地方法後

  從代碼執行結果來看,本地方法訪問到了Java的成員,並且修改了Java成員的值和調用了Java成員的方法。

  完整代碼例子在:https://github.com/qiubing/NativeToJava



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章