欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-01 10:18:10
...

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菜单项
JNI的学习与实践所得(Android)

②在弹出的Settings窗口,依次选择Tools -> External Tools -> +
JNI的学习与实践所得(Android)

③在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按钮就完成了配置。
JNI的学习与实践所得(Android)

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

3. 定义JNI函数
在MainActivity里,我们定义一个具有一个整型参数和整型返回值的JNI native函数:
JNI的学习与实践所得(Android)

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

④此时会弹出一个输入框,要求填入头文件的名称,随便起,比如就叫MainActivity
JNI的学习与实践所得(Android)

⑤此时,下方会弹出Run控制台,如果最后一行最后边是exit code 0,就说明编译过程中没有错误,正常结束。 如果exit code不是0,就是异常退出,就要视情况而定了。
JNI的学习与实践所得(Android)

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

②找到头文件
依次打开目录app -> src - > main -> jni -> MainActivity
JNI的学习与实践所得(Android)

③函数头
函数头就是红色框里面的部分
JNI的学习与实践所得(Android)

④复制到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的学习与实践所得(Android)

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

⑤调用
调用的时候直接调用在Java类里调用我们之前声明的那个函数就可以了:
JNI的学习与实践所得(Android)


后记

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