开始学习JNI开发技术,在网上看了很多文章,但讲解的都是基础或者过时的技术,没有系统的关于JNI和NDK的学习教程,现在我写《JNI和NDK开发》系列文章,主要是记录自己从零开始学习遇到的一些问题和知识点,希望对大家也有些帮助。对于文章,本人也是边学边写, 所以可能会更新的慢一点,大家有问题可以留言。
本系列文章主要解决的问题是:
- JNI和NDK开发常用API的使用
- JNI和NDK开发常见问题的解决
- JNI和NDK应用场景实践
JNI和NDK的概念
JNI
全称Java Native Interface
,意为java本地接口,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。
NDK
全称Native Development Kit
,是 Android的一个工具开发包 ,在Android开发中使用NDK编写JNI程序就能实现对C和C++代码的互相调用。
使用JNI和NDK的好处
- 效率
大家都知道,Android的底层是使用C&C++实现的,所以使用C&C++编写的程序,效率和性能上比java编写的程序高。 - 安全
应用层编写的java代码很容易被反编译,而通过JNi编译出的so库是一个二进制文件,对于反编译难度要大一点,如果在程序中再加上一些反调试代码和签名验证,又更提高了反编译的难度,所以一些核心加密的算法会使用JNI来编写。 - 调用Linux函数
众所周知,Android的核心是Linux系统,通过java是无法调用Linux系统函数的,而使用JNI编写C&C++代码却可以调用Linux的系统函数,例如通过调用fork
函数创建子进程来实现关键进程的保活,使用mmap映射文件来将日志写入到内存中等。 - 使用C&C++编译的开源库
使用C&C++编写的著名开源库有很多(例如音视频处理库FFmpeg
,图形图像处理库OpenCV
等),而Android应用层想要使用使用这些库就必须通过JNI程序调用。
JNI和NDK应用场景
- 音视频处理
像优酷、Bilibili、爱奇艺、抖音等这些视频播放和直播软件都是用的JNI程序调用FFmpeg
开源库。 - 图形图像处理
例如人脸识别、图像识别技术。 - 通信协议加密SDK
封装一个Http请求的通信加密的SDK,加密算法是在JNI层实现的。 - Android软件调用硬件设备
例如手机软件
创建第一个JNI程序
网上很多文章讲的都是Android Studio2.2之前通过Android.mk
来创建编译JNI程序,因为时间有限,我也没去学习和研究,所以直接使用Android Studio2.2之后通过CMake
来编译JNI程序,我当前的Android环境配置是:Android studio版本是3.0.1,gradle插件版本3.0.1,gradle版本是4.1,NDK版本是16。废话不多说,下面开始创建第一个JNI程序。
1、 NDK环境配置
在Android Studio中查看Android SDK的SDK Tools配置,如下图所示,保证这三个SDK要下载。
2、Include C++ support
打开Android Studio创建一个新工程,如下图所示,红框标注的意思是增加JNI的支持,这里如果选中就会创建一个JNI程序,点击执行下一步。
配置C++编译环境
C++ Standard
指定编译库的环境,其中Toolchain Default使用的是默认的CMake环境;C++ 11也就是C++环境。Exceptions Support
如果选中复选框,则表示当前项目支持C++异常处理,如果支持,在项目Module级别的build.gradle文件中会增加一个标识 -fexceptions到cppFlags属性中,并且在so库构建时,gradle会把该属性值传递给CMake进行构建。**Runtime Type Information Support
**
选中复选框,项目支持RTTI,属性cppFlags增加标识-frtti。
创建完成后运行程序, 就会在手机上显示Hello from C++
,从源码中可以看到这段文本是从native-lib.cpp
中返回的。效果看到了那就开始分析JNI的实现逻辑。
3、加载so库
在MainActivity中可以看到这段代码
static {
System.loadLibrary("native-lib");
}
这段代码的意思是加载名为native-lib
的so文件,那native-lib
这个名字是在哪定义的呢,其实是在CMakeLists.txt
中。
4、CMakeLists.txt
配置
打开app目录下的CMakeLists.txt
文件,可以看到很大一堆注释,让人看着就不简单,所以我把注释去掉,只留下有用的部分,无非就四行。
// 要求使用的cmake最小版本
cmake_minimum_required(VERSION 3.4.1)
// 添加共享库:命名为native-lib;设置为SHARED共享;文件路径是src/main/cpp/native-lib.cpp
add_library( native-lib SHARED src/main/cpp/native-lib.cpp )
// 找到Android自带的日志库log,并赋值给log-lib变量
find_library( log-lib log )
// 将native-lib库和log-lib库
target_link_libraries( native-lib ${log-lib} )
简单说一下CMake的这几条指令的意思:
add_library
将指定的文件生成与库链接的目标文件。
-find_library
查找库,并将库赋值给变量。target_link_libraries
将目标文件与生成的库文件链接。
5、build.gradle
中配置
在Android Studio中CMakeLists.txt
文件是怎么创建的呢?看一下app下的build.gradle文件,在android
节点下配置
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
这段代码的作用就是配置CMakeLists.txt
文件路径,Android Studio会根据这个路径找到并创建。
除了CMakeLists.txt
文件路径的配置,还有一段externalNativeBuild
的配置,在defaultConfig
节点下:
externalNativeBuild {
cmake {
cppFlags ""
}
}
这段意思是配置CMake编译环境,在app目录下可以看到.externalNativeBuild
编译文件。
6、C&C++
文件
在app
目录下,有一个cpp
目录,里面放的就是我们的JNI源程序,打开native-lib.cpp
文件,就可以看到JNI代码的语法结构。
#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring JNICALL
Java_com_rzr_jni_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
7、原生接口定义和调用
打开MainActivity
找到代码如下:
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
这段代码定义了原生与JNI的接口函数,我们可以通过调用这个接口来获取JNI返回的结果。
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
到此,我们就创建和分析了一个带JNI的Android工程。