JNI学习三(基本使用)

前面两篇熟悉了AndroidStudio中的Jni开发流程,今天开始初步学习jni的相关知识。

先来看一下本地函数的组成:

JNIEXPORT jint JNICALL Java_com_pngfi_jnidemo_CUseJava_callAdd
        (JNIEnv *env, jobject this)
  • JNIEXPORT和JNICALL这两个宏(被定义在jni.h)确保这个函数在本地库外可见,并且C编译器会进行正确的调用转换
  • 方法名:Java_全类名_方法名
  • 第一个参数查阅jni.h可以知道
typedef const struct JNINativeInterface* JNIEnv;

而JNINativeInterface是一个结构体,里面定义许多jni相关的方法。在下面的示例中我们有用到,(*env)也就是结构体JNINativeInterface的指针

  • 第二个参数根据本地方法是一个静态方法还是实例方法而有所不同。本地方法是一个静态方法时,第二个参数代表本地方法所在的类;本地方法是一个实例方法时,第二个参数代表本地方法所在的对象。

基本调用,包括字符串和数组
新建Module,在其中新建一个类JavaUseC,这个类提供三个方法,如下:

package com.pngfi.jnidemo;

/**
 * Created by pngfi on 2016/3/28.
 */
public class JavaUseC {

    /**
     *加法
     * @param x
     * @param
     * @return
     */
    public native int sum(int x,int y);

    /**
     * 给字符串后添加一个串
     * @param str
     * @return
     */
    public native String strAppend(String str);

    /**
     *改变数组元素
     * @param arr
     * @return
     */
    public native int[] changeValue(int[] arr);
}

这个三个方法都是本地方法。
进入该类包目录之上,执行javah -jni 全类名,其中-jni可省略,即可生成对应头文件。
新建jni文件夹,在其中新建.c文件,引入刚刚生成的头文件,并把其中方法声明拷入.c文件中。

实现所有的方法:

#include "com_pngfi_jnidemo_JavaUseC.h"
#include <android/log.h>
#include <stdio.h>
#include <string.h>
#define TAG "clog"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__)


/*
 * Class:     com_pngfi_jnidemo_JavaUseC
 * Method:    sum
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_pngfi_jnidemo_JavaUseC_sum
        (JNIEnv *env, jobject obj, jint x, jint y) {
    LOGI("%d",x);
    //直接返回
    return x + y;
}

/*
 * Class:     com_pngfi_jnidemo_JavaUseC
 * Method:    strAppend
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_pngfi_jnidemo_JavaUseC_strAppend
        (JNIEnv *env, jobject obj, jstring str) {
    //把java语言中的字符串转换成c语言中的char数组
    char *s=(*env)->GetStringUTFChars(env,str,NULL);
    if (s == NULL) {
        return NULL; //OutOfMemoryError抛出
    }
    //用c代码操作
    strcat(s," hello");
    //释放
    (*env)->ReleaseStringUTFChars(env,str,s);
    //再把char*转换成java中的字符串
    return (*env)->NewStringUTF(env,s);
}


/*
 * Class:     com_pngfi_jnidemo_JavaUseC
 * Method:    changeValue
 * Signature: ([I)[I
 */
JNIEXPORT jintArray JNICALL Java_com_pngfi_jnidemo_JavaUseC_changeValue
        (JNIEnv *env, jobject obj, jintArray jarry) {
   JNIEXPORT jintArray JNICALL Java_com_pngfi_jnidemo_JavaUseC_changeValue
        (JNIEnv *env, jobject obj, jintArray jarry) {
    int len=(*env)->GetArrayLength(env,jarry);
    int *arrry=(*env)->GetIntArrayElements(env,jarry,0);
    if (arrry==NULL){
        return NULL;
    }
    int i;
    for (i=0;i<len;i++){
        *(arrry+i)+=10;
        LOGI("%d\n",*(arrry+i));
    }
    //要释放指针
    (*env)->ReleaseIntArrayElements(env,jarry,arrry,0);
    return jarry;
}

第一个方法没什么好说的,我们来看第二方法,因为我们不能在c代码中直接操作jstring,必须通过jni函数将其转换成c/c++中的字符串,通过GetStringUTFChars方法来实现。不要忘记检查GetStringUTFChars,因为JVM需要为新诞生的UTF-8字符串分配内存,这个操作有可能因为内存太少而失败。失败时,GetStringUTFChars会返回NULL,并抛出一个OutOfMemoryError异常。这些JNI抛出的异常与JAVA中的异常是不同的。一个由JNI抛出的未决的异常不会改变程序执行流,因此,我们需要一个显示的return语句来跳过C函数中的剩余语句。Java_com_pngfi_jnidemo_JavaUseC_strAppend函数返回后,异常会在java代码中strAppend方法的调用者中抛出。

从GetStringUTFChars中获取的UTF-8字符串在本地代码中使用完毕后,要使用ReleaseStringUTFChars告诉JVM这个UTF-8字符串不会被使用了,因为这个UTF-8字符串占用的内存会被回收。

第三个int型数组的也类似字符串。

配置bulid.gradle文件,并在java代码中引入库,即可使用函数。

在c代码中回调java
感觉在c代码中回调java,类似于java中的反射。

首先新建一个java类,如下

package com.pngfi.jnidemo;

/**
 * Created by pngfi on 2016/3/29.
 */
public class CUseJava {

    public int  add(int x,int y){
        return x+y;
    }

    public native int callAdd();
}

生成头文件,新建c源代码,实现如下

//
// Created by pngfi on 2016/3/29.
//

#include "com_pngfi_jnidemo_CUseJava.h"


JNIEXPORT jint JNICALL Java_com_pngfi_jnidemo_CUseJava_callAdd
        (JNIEnv *env, jobject this){
    //通过全类名获得需要的类
    jclass clazz=(*env)->FindClass(env,"com/pngfi/jnidemo/CUseJava");

    //后两个参数是方法名字和签名,签名通过对.class文件执行  javap -s 全类名   获得
    jint mId=(*env)->GetMethodID(env,clazz,"add","(II)I");

    //CallIntMethod中的Int对应调用方法的返回值,我们add方法返回int
    jint res=(*env)->CallIntMethod(env,this,mId,5,6);

    return res;
}

其中方法签名的获得需要先编译生成.class即字节码文件,然后进入如下图所示的目录下,执行javap -s 全类名

这里写图片描述

其中jnidemo是我的module名字。

关于CallIntMethod方法,如果你要调用的方法返回值是Void那么就使用CallVoidMethod,以此类推。还有该方法中依然使用了可变参数,前面三个参数是固定的,后面继续添加的即为我们要调用的java方法的参数。

  • 1
    点赞
  • 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、付费专栏及课程。

余额充值