Java 中的基本类型包括 boolean,byte,char,short,int,long,float,double 这样几种,本文主要介绍上层向底层传递基本类型数据,以及底层向上层返回基本数据类型的情况。
本文地址
动态注册见:Android JNI 编程之动态注册
一、新建 JNIWrapper 类
用于对 native 接口的声明和封装,如下先建好 JNIWrapper 类,用于对 native 接口的封装,当然也可以直接将 native 接口声明为 public,外面直接调用,或直接在需要调用的类里直接声明。但个人还是比较喜欢将其声明为 private 的,在 native 之上在封装一层 java 层接口,放在在单独的一个 Java 类里,对外只暴露 Java 接口。这样有两个好处:
1、可以 native 方法统一维护和管理;
2、外部调用进来时可以对传入的参数做合法性判断,非法参数不必继续往下层调;
3、设计 native 接口时,如果是传参或关键接口时一般都会设计返回值,返回错误码,用于反馈上层该接口是否调用成功,那么在这个封装类里可以截获返回值,如果底层出错向调用者抛出异常,这样外层只需要捕获异常就好了,不需要判断返回值,更符合 Java 编码风格。一般 Java 外部调用者很少会对一个函数做返回值判断的,调用者可能忽略返回值,从而使得底层已经返回该调用出错了,上层还继续往下调用,导致未知错误。
package com.alan.alanjni;
/**
* Author: AlanWang.
* Date: 18/3/26 15:18.
* Mail: alanwang6584@gmail.com
*/
public class JNIWrapper {
/**
* 底层的成功码,如果返回码不为该值,说明调用出错
*/
private static final int SUCCESS_CODE = 0;
// Used to load the 'alanjni' library on application startup.
static {
System.loadLibrary("alanjni");
}
/******************************************************************
* java 接口,调用底层接口向底层传递基本数据类型
*****************************************************************/
/**
* 调用底层 nativeSetArgBoolean 接口
* @param bRrg
* @return
*/
public boolean setArgBoolean(boolean bRrg) {
return nativeSetArgBoolean(bRrg);
}
/**
* 调用底层 nativeSetArgByte 接口
* @param bArg
* @return
*/
public byte setArgByte(byte bArg) {
return nativeSetArgByte(bArg);
}
/**
* 调用底层 nativeSetArgChar 接口
* @param cArg
* @return
*/
public char setArgChar(char cArg) {
return nativeSetArgChar(cArg);
}
/**
* 调用底层 nativeSetArgShort 接口
* @param sArg
* @return
*/
public short setArgShort(short sArg) {
return nativeSetArgShort(sArg);
}
/**
* 调用底层 nativeSetArgInt 接口,如果参数错误或执行错误,则向调用者抛出异常
* @param iArg
* @throws RuntimeException
*/
public void setArgInt(int iArg) throws RuntimeException {
if (iArg < 0) {
throw new IllegalArgumentException("Invalid argument when call setArgInt.");
}
int errCode = nativeSetArgInt(iArg);
if (SUCCESS_CODE != errCode) {
throw new RuntimeException("Error during setArgInt.");//一般这里可以是自定义异常
}
}
/**
* 调用底层 nativeSetArgShort 接口
* @param lArg
* @return
*/
public long setArgLong(long lArg) {
return nativeSetArgLong(lArg);
}
/**
* 调用底层 nativeSetArgFloat 接口
* @param fArg
* @return
*/
public float setArgFloat(float fArg) {
return nativeSetArgFloat(fArg);
}
/**
* 调用底层 nativeSetArgDouble 接口
* @param dArg
* @return
*/
public double setArgDouble(double dArg) {
return nativeSetArgDouble(dArg);
}
/******************************************************************
* 向底层传递基本数据类型,以及底层向上层返回基本数据类型
*****************************************************************/
/**
* 向底层传递 boolean 型参数,并返回 boolean 型返回值
* @param bArg
* @return
*/
private native boolean nativeSetArgBoolean(boolean bArg);
/**
* 向底层传递 byte 型参数,并返回 byte 型返回值
* @param bArg
* @return
*/
private native byte nativeSetArgByte(byte bArg);
/**
* 向底层传递 char 型参数,并返回 char 型返回值
* @param cArg
* @return
*/
private native char nativeSetArgChar(char cArg);
/**
* 向底层传递 short 型参数,并返回 short 型返回值
* @param sArg
* @return
*/
private native short nativeSetArgShort(short sArg);
/**
* 向底层传递 int 型参数,并返回 int 型返回值
* @param iArg
* @return
*/
private native int nativeSetArgInt(int iArg);
/**
* 向底层传递 long 型参数,并返回 long 型返回值
* @param lArg
* @return
*/
private native long nativeSetArgLong(long lArg);
/**
* 向底层传递 float 型参数,并返回 float 型返回值
* @param fArg
* @return
*/
private native float nativeSetArgFloat(float fArg);
/**
* 向底层传递 float 型参数,并返回 float 型返回值
* @param dArg
* @return
*/
private native double nativeSetArgDouble(double dArg);
//
}
二、生成 jni 头文件
建好 JNIWrapper 类后需要生成 JNI 方法声明的头文件,这个可以使用 java 命令生成。有两种方案:
1、先进入包的根目录及 com 上级目录,然后执行:javah com.alan.alanjni.JNIWrapper,然后在就好当前执行命令的目录下生成 com_alan_alanjni_JNIWrapper.h 文件;
2、另外一种就是使用javah 命令对 JNIWrapper.class 文件操作,如果编译过,class 文件在项目的 app/build/intermediates/classes/debug/com/alan/alanjni/JNIWrapper.class 可以把这个 JNIWrapper.class 拷贝到任意目录,然后执行 javah JNIWrapper.class ,就会在该目录下生成 com_alan_alanjni_JNIWrapper.h 文件。刚写好的 JNIWrapper 类是没有 JNIWrapper.class 文件的,在 Android Studio 的工具栏 Build—>Rebuild Project 再去看就有了。
然后把 com_alan_alanjni_JNIWrapper.h 文件拷贝到 cpp 目录下(或你想放的其他目录,只有后面在 CMakeLists 里把路径配置对就好),当然如果嫌文件名太长,可以修改文件名,比如我就改成 JNIWrapper.h 。
三、新建 jni 的实现文件并完成具体实现
新建同名的 .cpp 文件,然后在文件头,include jni.h 和 JNIWrapper.h 文件,然后将刚才的头文件中生成的所有函数复制到 .cpp 中,那么 cpp 目录有两个文件 JNIWrapper.h 和 JNIWrapper.cpp,然后我们需要在 JNIWrapper.cpp 中完成具体实现。如下:
JNIEXPORT jboolean JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgBoolean(JNIEnv *, jobject, jboolean);
首先我们需要将参数补全,改为如下:
JNIEXPORT jboolean JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgBoolean(JNIEnv *env, jobject obj, jboolean bArg) {
jboolean ret = !bArg; // 对传入的参数 bArg 取反
return ret;
}
记得函数声明有返回值的一定要 return 返回值,否则尽管编译过了,但运行的时候会报错崩溃,其他函数也类似,并实现具体逻辑。整体实现代码如下:
#include <android/log.h>
#include "JNIWrapper.h"
#define LOGD(TAG, ...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define TAG_JNI "TAG_JNI"
/*
* Class: com_alan_alanjni_JNIWrapper
* Method: nativeSetArgBoolean
* Signature: (Z)Z
*/
JNIEXPORT jboolean JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgBoolean
(JNIEnv *env, jobject obj, jboolean bArg) {
jboolean ret = !bArg; // 对传入的参数 bArg 取反
LOGD(TAG_JNI, "nativeSetArgBoolean()--->bArg = %d, ret = %d", bArg, ret);
return ret;
}
/*
* Class: com_alan_alanjni_JNIWrapper
* Method: nativeSetArgByte
* Signature: (B)B
*/
JNIEXPORT jbyte JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgByte
(JNIEnv *env, jobject obj, jbyte bArg) {
jbyte ret = bArg << 2; // 对传入的 bArg 左移两位(即乘 2 )
LOGD(TAG_JNI, "nativeSetArgByte()--->bArg = %d, ret = %d", bArg, ret);
return ret;
}
/*
* Class: com_alan_alanjni_JNIWrapper
* Method: nativeSetArgChar
* Signature: (C)C
*/
JNIEXPORT jchar JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgChar
(JNIEnv *env, jobject obj, jchar cArg) {
jchar ret = cArg + 1; // 对传入的 cArg 加 1,即其下一个字符
LOGD(TAG_JNI, "nativeSetArgChar()--->cArg = %c, ret = %c", cArg, ret);
return ret;
}
/*
* Class: com_alan_alanjni_JNIWrapper
* Method: nativeSetArgShort
* Signature: (S)S
*/
JNIEXPORT jshort JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgShort
(JNIEnv *env, jobject obj, jshort sArg) {
jshort ret = sArg + 2; // 对传入的参数 sArg 加 2
LOGD(TAG_JNI, "nativeSetArgShort()--->sArg = %d, ret = %d", sArg, ret);
return ret;
}
/*
* Class: com_alan_alanjni_JNIWrapper
* Method: nativeSetArgInt
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgInt
(JNIEnv *env, jobject obj, jint iArg) {
jint ret;
if (iArg < 10) {// 如果传入的参数 iArg 小于 10,则返回成功,否则返回失败
ret = 0;
} else {
ret = -1;
}
LOGD(TAG_JNI, "nativeSetArgInt()--->iArg = %d, ret = %d", iArg, ret);
return ret;
}
/*
* Class: com_alan_alanjni_JNIWrapper
* Method: nativeSetArgLong
* Signature: (J)J
*/
JNIEXPORT jlong JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgLong
(JNIEnv *env, jobject obj, jlong lArg) {
jlong ret = lArg + 1 * 1000 * 1000; // 对传入的 lArg 做运算
LOGD(TAG_JNI, "nativeSetArgLong()--->lArg = %ld, ret = %ld", lArg, ret);
return ret;
}
/*
* Class: com_alan_alanjni_JNIWrapper
* Method: nativeSetArgFloat
* Signature: (F)D
*/
JNIEXPORT jfloat JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgFloat
(JNIEnv *env, jobject obj, jfloat fArg) {
jfloat ret = fArg + 1.0f; // 对传入的 fArg 做运算
LOGD(TAG_JNI, "nativeSetArgFloat()--->fArg = %f, ret = %f", fArg, ret);
return ret;
}
/*
* Class: com_alan_alanjni_JNIWrapper
* Method: nativeSetArgDouble
* Signature: (D)D
*/
JNIEXPORT jdouble JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgDouble
(JNIEnv *env, jobject obj, jdouble dArg) {
jdouble ret = dArg + 2.0; // 对传入的 dArg 做运算
LOGD(TAG_JNI, "nativeSetArgDouble()--->dArg = %lf, ret = %lf", dArg, ret);
return ret;
}
四、将文件加入编译,并在上层调用测试
打开 src 目录下的 CMakeLists.txt 文件,然后在 add_libraty 中加入 src/main/cpp/JNIWrapper.cpp,然后在 上层的 java 代码中调用 JNIWrapper 封装的接口进行调用测试。
五、总结
我们可以看到,Java中的基本类型 boolean,byte,char,short,int,long,float,double,在 jin 中分别对应的类型是 jboolean,jbyte,jchar,jshort,jint,jlong,afloat,jdouble,这几种类型都可以当成对应的C++类型来用。Java 和 Native 类型对应关系如下表:
Java 类型 | Native 类型 | 描述 |
boolean | jboolean | C/C++8位整型 |
byte | jbyte | C/C++带符号的8位整型 |
char | jchar | C/C++无符号的16位整型 |
short | jshort | C/C++带符号的16位整型 |
int | jint | C/C++带符号的32位整型 |
long | jlong | C/C++带符号的64位整型e |
float | jfloat | C/C++32位浮点型 |
double | jdouble | C/C++64位浮点型 |
Object | jobject | 任何Java对象,或者没有对应java类型的对象 |
Class | jclass | Class对象 |
String | jstring | 字符串对象 |
Object[] | jobjectArray | 任何对象的数组 |
boolean[] | jbooleanArray | 布尔型数组 |
byte[] | jbyteArray | 比特型数组 |
char[] | jcharArray | 字符型数组 |
short[] | jshortArray | 短整型数组 |
int[] | jintArray | 整型数组 |
long[] | jlongArray | 长整型数组 |
float[] | jfloatArray | 浮点型数组 |
double[] | jdoubleArray | 双浮点型数组 |
代码见 AlanJNI
Demo下载地址 (github 会一直更新,下载地址是只包含到本文时的代码)