本例demo下载地址:JniDemo下载
简介
上篇介绍了Android Studio中CMake简单配置jni开发。不清楚的可以看
Android Jni开发-基础配置篇(CMake)
如果不想麻烦的话直接在示例Jni代码native-lib.cpp中添加自己的业务逻辑即可。但是这样当代码一多了的时候不好扩展也不好管理,因此这次来实践添加自己的Jni代码。
目标:
实现一个简单的jni函数sum(int a, int b),返回a+b的结果。一个稍微复杂点的jni函数twoSum(int nums[], int target),实现的是leetcode上的一个题目TwoSum,内容是给定数组及目标数字,返回符合目标数字的2个数的下标(数组类型)。
1、首先建立一个demo.h
该文件作为c++的头文件,声明要实现的函数。
#ifndef JNIDEMO_DEMO_H
#define JNIDEMO_DEMO_H
#endif //JNIDEMO_DEMO_H
class Demo{
public:
Demo();
int sum(int a, int b);
int *twoSum(int nums[], int target);
};
2、建立demo.cpp文件实现算法。
该文件实现头文件声明的函数。
#include <cstring>
#include "demo.h"
Demo::Demo() {
}
int Demo::sum(int a, int b) {
return a + b;
}
int* Demo::twoSum(int nums[], int target) {
int length = sizeof(nums);
if (length < 2){
return nullptr;
}
int *result = nullptr;
for (int i = 0; i < length; i++){
for (int j = i + 1; j < length; j++){
if (nums[i] + nums[j] == target){
result = new int[2];
result[0] = i;
result[1] = j;
break;
}
}
}
return result;
}
3、创建demo-lib.cpp文件
这个文件里面放的是我们的jni代码,暂时还不用写先,因为现在不想手动写jni函数名,至于为什么叫demo-lib(.cpp文件名加-lib),一句话,官方推荐命名格式以及样例也是这样命名的。
4、修改CMakeLists.txt
跟之前native-lib文件一样,新加的库使用add_library指令。我们直接复制native-lib的add_library添加到下面,将native-lib改成我们的demo-lib.cpp(即第3点创建的jni代码文件)。
然后往连接库的指令添加这个库的声明。
至此,CMakeLists.txt的修改就完成了,就是这样简单。
5、添加java native方法接口
上篇示例中java native方法接口是直接声明在MainActivity中的,为方便管理,我们建立一个专门的Jni接口包用来管理所有的java native方法集合,为跟demo-lib.cpp对应,我们创建一个Java的Demo接口类。
这里使用Android Studio的好处就体现出来了,java native方法接口未有相应的jni函数接口是会标红提醒的,因为我们的demo-lib.cpp未添加任何代码,所以这里标红了。因此也可以根据这个来判断jni命名是否写正确,不正确的话会标红提醒。
6、编写jni函数名
为解决上面的标红提醒,我们来编写jni函数名。我们知道jni函数名是有一定格式的,不熟悉的朋友可能会觉得jni函数名很难写,但是如果熟悉了是可以直接对照java native方法手动编写对应jni代码函数,毕竟写错了也没关系,会标红提醒的。这里推荐使用java命令为我们自动生成jni代码,毕竟函数一多,手写很累的。
(1) 打开windows的cmd命令(当然也可以使用Android Studio自带的Terminal控制台),切换到Demo.java这个native 方法接口类的路径。然后使用javac Demo.java
将该类转换为.class文件。
可以发现,执行后报了一个错,这是由编码格式不对造成的,因为我们这个类的注释里面使用了中文。虽然可以更改编码格式解决,但是为了减少不必要的麻烦,最好在这个接口类中不要出现中文。我们把类里面的中文注释改成英文再试试。
可以发现,这次不报错了。
这时可以发现Demo.java的目录下多了个Demo.class文件。
(2)接着切换回工程的java目录下,使用javah -classpath . -jni + 包名 + 文件名
生成.h文件(命令中-jni前面的点是必要的)。windows切换上级目录使用cd…命令。
执行完成后,可以在java目录下看到一个以包名+文件名的.h文件,里面即写好了Demo.java对应的jni函数名称代码。
这个文件的内容如下所示:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class mhwang_com_jni_Demo */
#ifndef _Included_mhwang_com_jni_Demo
#define _Included_mhwang_com_jni_Demo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: mhwang_com_jni_Demo
* Method: sum
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_mhwang_com_jni_Demo_sum
(JNIEnv *, jobject, jint, jint);
/*
* Class: mhwang_com_jni_Demo
* Method: twoSum
* Signature: ([II)[I
*/
JNIEXPORT jintArray JNICALL Java_mhwang_com_jni_Demo_twoSum
(JNIEnv *, jobject, jintArray, jint);
#ifdef __cplusplus
}
#endif
#endif
可以看到,文件里面已经写好Demo.java里面方法相对应的jni函数名了。
(3) 这时把生成的.h文件里面的内容复制粘贴到我们之前创建的demo-lib.cpp文件里面,jni代码里面的命名部分就完成了。
当写好jni函数名后,你会发现Demo.java里面的代码也不再是红色了,说明Demo.java里面的方法能正确调用jni的函数了。
7、实现jni调用C++函数
先实现sum()这个比较简单的jni函数,我们先把函数的参数补上,然后调用demo.cpp里面的Demo对象的sum()方法进行计算。由于这里使用了demo.cpp里面的类Demo,因此需要在头文件引用demo.cpp文件。
在MainActivity调用一下,然后我们来运行一下看看:
调用成功了。接下来实现twoSum()调用。c++中的twoSum函数需要传递一个数组,而java的数组是不能直接作为参数传递给c++函数的,因此需要Jni作一层转换。具体的jni转换函数如下:
/***
* 将java的数组转成c++指针数组
* ***/
jint * getIntArrayFromJava(JNIEnv *env, jintArray j_array){
jint *c_array;
jint arr_len;
arr_len = (*env).GetArrayLength(j_array);
c_array= (jint*)malloc(sizeof(jint) * arr_len);
// 初始化
memset(c_array,0, sizeof(jint)*arr_len); // 此处留意
// 获取数组
c_array= (*env).GetIntArrayElements(j_array,NULL);
return c_array;
}
这里使用了一些C++内置函数,因此需要在jni头文件中引入。
#include <cstring>
#include <malloc.h>
然后在jni函数中直接调用该函数转换成指针形式传递给C++函数中:
在MainActivity中调用:
运行结果如下: