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



  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值