JNI的学习与实践所得(Android)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Mr_Megamind/article/details/79202484

Motivation

又快到毕业季了,实验室研三的的学长学姐们开始赶自己的毕业论文,写论文自然少不了做实验。因此就被派了这么个活儿,“制作”包含JNI调用的APP供他们做实验用。


需要做的准备

1. Android Studio 的安装与配置(包括SDK)
这个不用多说,选择Android Studio 3.0.1作为IDE,Gradle和SDK没问题就好。

2. JDK的安装与配置(包含系统环境变量)
这个就更不用多说,首先我们要做的是JNI,其次Java没配好,AS就配不好。


实践部分

1. javah外部工具的配置
Java类中我们定义的JNI native函数形如:

public static native int jniFunction(int parameter);

与之链接的native层的函数头形如:

extern "C"    //注①
/*
 * Class:     com_implementist_jnitest_MainActivity    //注②
 * Method:    jniFunction    //注③
 * Signature: (I)I    //注④
 */
JNIEXPORT jboolean JNICALL Java_com_implementist_jnitest_MainActivity_jniFunction    //注⑤
        (JNIEnv *env, jclass _this, jint parameter) {    //注⑥

**注释①:**每一个JNI函数都需要写这一句,不然运行的时候会报错说找不到相应的函数。
注释②:
注释③:
**注释④:**这三个注释里的东西可有可无,毕竟是一个注释,后面我会讲如何自动生成它们。
**注释⑤:**函数头,包含了返回值类型,包名,类名,函数名
**注释⑥:**参数部分,你可能会想,我定义的是一个参数,怎么这里有三个参数?解释如下:
前两个参数是每个JNI native函数都有的,且是必要的。

JNIEnv:是JNI的环境指针变量,用env->的方式可以调用很多必要的JNI函数,后面我们会用到一些。

jclass:定义的是定义这个JNI native函数的Java类的实例,一般用于在native里回调Java函数时(用class A的实例调用class B的成员函数是要报错的!调用前考虑清楚)

jint:这个参数才是我们自己声明的参数,需要注意的是在这里它变成了jint,因为这里(cpp文件中)是C语言的环境,所以需要把Java int 和 C int区分开来(自然还有jstring、jboolean等对应的类型存在)

说了这么多,想手动把这些东西写对,对于新手来说还是很难的,况且JNI的报错信息让人调试起来很抓狂。因此,我们可以用javah命令来帮我们编译出这些东西。

但是无论cmd还是Android Studio里的Terminal,不但不好用每次都需要敲一大串路径进去,繁琐耗时。

我们可以预先配置Android Studio里的外部工具,然后每次一键编译,重点终于来了!

①依次点击IDE左上方的File->Settrings菜单项
Settings菜单项

②在弹出的Settings窗口,依次选择Tools -> External Tools -> +
External Tools

③在Name, Program, Parameters和Working directory这四个输入框填入以下指令:

Name:javah
Program:$JDKPath$\bin\javah.exe 
Parameters: -classpath . -jni -o $ModuleFileDir$/src/main/jni/$Prompt$ $FileClass$ 
Working directory:$ModuleFileDir$\src\main\java

填好的情形如下图,点击OK按钮就完成了配置。
Edit Tool

2. 创建一个支持JNI的Android工程
对已经创建好的Android工程添加JNI支持是很麻烦的,因此我们在创建工程的时候让AS帮我们直接配置好。
和创建普通的Android工程基本类似,填好工程的名称,包名等,关键的不同在于,选中C++ Support复选框。
创建支持JNI的Android工程
后面一路Next就可以了。

3. 定义JNI函数
在MainActivity里,我们定义一个具有一个整型参数和整型返回值的JNI native函数:
定义JNI函数

4. 用javah外部工具编译MainActivity
①在IDE左边的工程树上选中要编译的Java类(MainActivity);
②右键唤出菜单;
③在菜单的最下方,依次选择External Tool -> javah
javah菜单项

④此时会弹出一个输入框,要求填入头文件的名称,随便起,比如就叫MainActivity
输入头文件名称

⑤此时,下方会弹出Run控制台,如果最后一行最后边是exit code 0,就说明编译过程中没有错误,正常结束。 如果exit code不是0,就是异常退出,就要视情况而定了。
Exit Code 0

5. 取得函数头
前面做了那么多工作,全都是为这里做准备。
①切换工程树模式
首先把工程树切换到project模式,依次点击左上方的下三角 -> project
切换工程树到project

②找到头文件
依次打开目录app -> src - > main -> jni -> MainActivity
头文件

③函数头
函数头就是红色框里面的部分
函数头

④复制到native-lib.cpp中
native-lib.cpp是我们实际编写JNI程序的地方,我们需要把上述函数头复制到这个文件中来使用,但是在使用之前,需要做一些简单的修改。

extern "C" {    //注①
#endif    //注②
/*
 * Class:     com_implementist_jnitest_MainActivity
 * Method:    jniFunction
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_com_implementist_jnitest_MainActivity_jniFunction
  (JNIEnv *, jclass, jint);    //注③

#ifdef __cplusplus    //注④
}    //注⑤

注释①:extern "C"必须写在每一个JNI函数前面,否则会报找不到JNI函数的异常,左大括号删掉。
注释②:这行删掉
注释③:编译的时候只给了参数类型,没给参数名,填上,然后删掉分号,加上左右大括号。
注释④:
注释⑤:这两行删掉。

修改后的函数头在native-lib.cpp里如下所示:
修改后的函数头

现在,就可以在这个函数里编写JNI程序了。

⑤调用
调用的时候直接调用在Java类里调用我们之前声明的那个函数就可以了:
调用native函数


后记

到目前为止,这只是学习JNI编程之前的各项准备,相当于我们准备好了工作台和工具。今天就先写到这。对于数组操作、字符串操作和Java函数回调我会在之后分几篇帖子写出来,敬请期待。
——2018.01.30

猜你喜欢

转载自blog.csdn.net/Mr_Megamind/article/details/79202484
今日推荐