JNI学习(二)之实例分析

上一篇我们讲解JNI技术时,说过通过这种技术可以做到以下两点:

  • Java程序中的函数可以调用Native语言写的函数,Native一般指的是C/C++编写的函数。
  • Native程序中的函数可以调用Java层的函数,也就是在C/C++程序中可以调用Java的函数。

本篇我们就来演示一下这两种情况的实现。

Java调用C/C++

其实JNI学习的第一篇文章设置编译环境实现的例子就是Java调用C++。
回顾一下流程吧:
1.Java中申明Native方法:

public class TestJNI {
    public native int add(int x, int y);
}

2.把装有native方法的类, 编出class文件。
3.将目标class文件编译成.h文件。

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_hx_testjni_TestJNI */

#ifndef _Included_com_hx_testjni_TestJNI
#define _Included_com_hx_testjni_TestJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_hx_testjni_TestJNI
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_hx_testjni_TestJNI_add
  (JNIEnv *, jobject, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

4.针对.h编写c/c++逻辑。
c++代码:

#include <stdio.h>
#include "com_hx_testjni_TestJNI.h"

JNIEXPORT jint JNICALL Java_com_hx_testjni_TestJNI_add(JNIEnv *env, jobject obj, jint x, jint y) {
    return x + y;
}

很多人在最终成功编译so并载入so后, 在java层调用native方法时会出现java.lang.UnsatisfiedLinkError这个异常. 原因是就在第二行, 这里c和c++是有些区别的, 如果用c实现的话, 只需要#include <\jni.h>即可, 但是如果用c++实现, 那么必须要include你刚刚生成的.h文件, 而不是jni.h. 虽然编译可以通过,但是调用时你会发现报了java.lang.UnsatisfiedLinkError这个异常. 原因就是java层没找到对应的方法. 还有就是c和c++语法上的一些小区别, 但这些错误是可以在编译so期间发现的。
c代码:

#include <stdio.h>
//#include "com_hx_testjni_TestJNI.h"
#include <jni.h>

JNIEXPORT jint JNICALL Java_com_hx_testjni_TestJNI_add(JNIEnv *env, jobject obj, jint x, jint y) {
    return x + y;
}

4.编写Android.mk文件

LOCAL_PATH := $(call my-dir) 
include $(CLEAR_VARS)   
LOCAL_MODULE    := TestJNI
//LOCAL_SRC_FILES := com_hx_testjni_TestJNI.cpp
LOCAL_SRC_FILES := com_hx_testjni_TestJNI.c  
include $(BUILD_SHARED_LIBRARY)

5.生成libTestJNI.so文件
6.java层loadLibrary调用native方法

static {
    System.loadLibrary("TestJNI");
}
...
TestJNI jni = new TestJNI();
sum = jni.add(x, y);
...

至此,Java调用C/C++编码流程介绍完毕。

C/C++调用Java

我们需要在JNI的C代码调用Java代码。实现原理:使用JNI提供的反射接口来反射得到Java方法,进行调用。
这里的例子是在上面例子的基础上扩展而成的,代码流程是Java通过Native方法sayHello调用到C++里面,再从C++反射调用Java中的signTest方法。
1.TestJNI.java中申明Native方法sayHello和写自己的方法signTest:

public class TestJNI {
    public String name = "Test";    
    public int number = 100;  

    public native int add(int x, int y); 
    public native void sayHello();

    public int signTest(int i,Date date,int[] arr){    
        MainActivity.showlog("Sign Test");
        MainActivity.showlog("(Java)i="+i);     
        return 0;    
    }   

}

2.编译生成TestJNI.class文件和com_hx_testjni_TestJNI.h头文件

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_hx_testjni_TestJNI */

#ifndef _Included_com_hx_testjni_TestJNI
#define _Included_com_hx_testjni_TestJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_hx_testjni_TestJNI
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_hx_testjni_TestJNI_add
  (JNIEnv *, jobject, jint, jint);

/*
 * Class:     com_hx_testjni_TestJNI
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_hx_testjni_TestJNI_sayHello
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

3.c++实现com_hx_testjni_TestJNI.cpp文件

#include <stdio.h>
#include "com_hx_testjni_TestJNI.h"
#include <android/log.h>
#define LOG_TAG "HuaXun"

#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##args)

JNIEXPORT jint JNICALL Java_com_hx_testjni_TestJNI_add(JNIEnv *env, jobject obj,
        jint x, jint y) {
    LOGI("(Native)x--->%d", x);
    LOGI("(Native)y--->%d", y);
    LOGI("(Native)sum--->%d", x + y);
    return x + y;
}

JNIEXPORT void JNICALL Java_com_hx_testjni_TestJNI_sayHello(JNIEnv * env, jobject obj) {
    LOGI("Hello Native Test !");
    //因为sayHello不是静态函数,所以传进来的就是调用这个函数的对象
    //否则就传入一个jclass对象表示native()方法所在的类
    jclass native_clazz = env->GetObjectClass(obj);

    //通过反射得到jmethodID
    jmethodID methodID_func = env->GetMethodID(native_clazz,"signTest","(ILjava/util/Date;[I)I");
    //调用Java方法signTest
    env->CallIntMethod(obj, methodID_func, 1L, NULL, NULL);
    //每次调用完后要删除local reference,释放内存
    env->DeleteLocalRef(obj);

    //通过反射得到jfieldID
    jfieldID fieldID_name = env->GetFieldID(native_clazz,"name","Ljava/lang/String;");
    jfieldID fieldID_num = env->GetFieldID(native_clazz,"number","I");

    //得到name属性
    jobject name = env->GetObjectField(obj, fieldID_name);
    //得到number属性
    jint number= env->GetIntField(obj,fieldID_num);

//  LOGI("(Native)name--->%s", name);
    LOGI("(Native)number--->%d", number);

    //修改number属性的值
    env->SetIntField(obj,fieldID_num,18880L);
    number= env->GetIntField(obj,fieldID_num);
    LOGI("(Native)number after modify--->%d", number);
}

这里有一点值得我们看一下:

//通过反射得到jmethodID
jmethodID methodID_func = env->GetMethodID(native_clazz,"signTest","(ILjava/util/Date;[I)I");

需要提供三个参数,第一个是获取的jclass对象,第二个是Java文件中的函数名,第三个是函数签名。函数签名的格式可以见上一篇博客,这里我们可以通过javap获取函数签名。

javap -p -s [class文件]

这里写图片描述

4.编写Android.mk文件

LOCAL_PATH := $(call my-dir) 
include $(CLEAR_VARS)  
LOCAL_MODULE    := TestJNI
LOCAL_SRC_FILES := com_hx_testjni_TestJNI.cpp
LOCAL_LDLIBS    := -lm -llog 
include $(BUILD_SHARED_LIBRARY)

5.生成libTestJNI.so文件
6.java层loadLibrary调用native方法

static {
    System.loadLibrary("TestJNI");
}
...
TestJNI jni = new TestJNI();
jni.sayHello();
...

安装APK,运行看Log输出:
这里写图片描述

可以看到C++已经调用到Java的signTest方法,并将i值(i=1)传上来了。而且C++访问到了Java的name和number两个属性,并成功修改number属性值。

Demo下载地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android JNI学习路线可以按照以下步骤进行: 1. 了解JNI的基本概念和作用:JNI(Java Native Interface)是Java提供的一种机制,用于实现Java与其他编程语言(如C、C++)之间的交互。它允许在Java代码中调用本地代码(Native Code),并且可以在本地代码中调用Java代码。 2. 学习JNI的基本语法和规则:JNI使用一组特定的函数和数据类型来实现Java与本地代码之间的交互。你需要学习如何声明本地方法、如何在Java代码中调用本地方法、如何在本地代码中调用Java方法等。 3. 学习JNI的数据类型映射:JNI提供了一套数据类型映射规则,用于将Java数据类型映射到本地代码中的数据类型。你需要学习如何处理基本数据类型、对象类型、数组类型等。 4. 学习JNI的异常处理:在JNI中,Java代码和本地代码之间的异常处理是非常重要的。你需要学习如何在本地代码中抛出异常、如何在Java代码中捕获异常等。 5. 学习JNI的线程处理:JNI允许在本地代码中创建和操作线程。你需要学习如何创建和销毁线程、如何在线程之间进行通信等。 6. 学习JNI的性能优化:JNI涉及到Java代码和本地代码之间的频繁切换,因此性能优化是非常重要的。你需要学习如何减少JNI调用的次数、如何避免不必要的数据拷贝等。 7. 学习JNI的调试和测试:在开发JNI程序时,调试和测试是非常重要的。你需要学习如何使用调试器调试本地代码、如何进行单元测试等。 8. 学习JNI的进阶主题:一旦掌握了基本的JNI知识,你可以进一步学习JNI的进阶主题,如JNI与Java虚拟机的交互、JNI与动态链接库的交互、JNI与多线程的交互等。 总结起来,Android JNI学习路线包括了基本概念、基本语法、数据类型映射、异常处理、线程处理、性能优化、调试和测试以及进阶主题等内容。通过系统地学习这些知识,你将能够更好地理解和应用JNI技术。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值