调用原理
java代码编译后,运行时会去某个目录寻找so文件、并load,然后调用so文件中的方法,要保证这个流程准确无误,就必须约定好如下条件:
- java代码运行时,知道去哪里加载so库 —— so库存放目录;
- java代码运行时,知道加载哪个so文件 —— so库名称;
- java代码运行时,知道如何正确调用某个方法 —— so库的方法名、参数、返回值;
第1个条件是android系统帮我们完成的,apk安装时,会把so库解压到/data/app/com.example.test/lib/ABI/目录下,运行时也会自动去这个目录下加载so库;
第2、3个条件则是我们自己开发时需要约定好的。具体如何约定,后面会讲到。
实现步骤
知道了调用原理后,开发步骤就很容易了,大致分为如下几步:
- java代码加载so文件(约定so文件名),声明native方法(约定方法名、参数和返回值);
- 生成C/C++头文件,声明方法(方法名、参数和返回值与java代码约定的一致);
- 实现C/C++头文件中声明的方法;
- 将C/C++文件编译成Android平台下的so文件(so文件名与java代码约定的一致);
- 将编译好的so文件放入对应的jniLib目录下
java代码加载so文件
package com.example.mytest;
public class JniTest {
static {
// load名称为useffmpeg的so文件。最终so文件名会在前面加上lib、后面加上.so,即libuseffmptg.so
System.loadLibrary("useffmpeg");
}
// 声明本地方法名称、参数、返回值
public native String getFFMpegInfo();
}
生成C/C++头文件
通过javah命令,可以生成JniTest.java对应的C/C++头文件,不过我觉得用代码写还方便一点。
头文件命名为com_example_mytest_JniTest.h,命名规则就是包名+类名,用下划线代替.号,代码如下:
/* 引用jni库*/
#include <jni.h>
extern "C" {
#endif
/*
* 1.jstring是方法返回值,对应java代码中native方法的返回值String;
* 2.Java_com_example_mytest_JniTest_getFFMpegInfo就是声明的方法名,对应java代码中的包名、类名和方法名;
* 3.JNIEnv *, jobject是必带的参数,没有其它参数对应的就是java代码中的native方法没有参数;
*/
JNIEXPORT jstring JNICALL Java_com_example_mytest_JniTest_getFFMpegInfo (JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
实现C/C++头文件中的方法
具体实现类命名com_example_mytest_JniText.c,实现代码如下:
/* 引入上面的.h文件 */
#include "com_example_mytest_JniTest.h"
/**
* 实现上面头文件声明的方法
*/
JNIEXPORT jstring JNICALL Java_com_example_mytest_JniTest_getFFMpegInfo (JNIEnv *env, jobject obj) {
/* NewStringUTF是jni.h库中的方法。*/
/*至于本地方法如何用C代码实现,那就要有一定C语言功底了,这里只是简单地返回一个字符串 */
return (*env)->NewStringUTF(env, "jni测试");
}
编译so文件
编译Android平台的so文件需要四个文件:
- C/C++头文件。作用是约定方法名、参数和返回值;
- 上面声明的方法的具体实现类。用来编译成具有实际功能的二进制文件;
- Android.mk文件。作用是约定so文件名称,以及一些其它配置;
- Application.mk文件。作用是配置编译的ABI;
Android.mk文件如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
// 约定so文件的名称
LOCAL_MODULE := useffmpeg
// so文件的实现类
LOCAL_SRC_FILES := com_example_mytest_JniText.c
// 编译so的时候,记得把注释、中文、空行全删掉,有些情况下可能会编译不过
include $(BUILD_SHARED_LIBRARY)
Application.mk文件如下:
// 编译所有ABI的so库
APP_ABI := all
将这4个文件放在同一个目录下,打开命令行,进入到该目录下,使用命令“ndk-build”命令即可编译出各个ABI的so库。
使用so文件
将so文件放入jniLIbs目录下,在gradle文件中指定jniLibs目录即可。