在上一篇文章中介紹了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);
}
#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