在上一篇文章中介绍了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