一、写在前面
作为一个Android UI Api调用工程师,在日常中我们接触的更多是java层,除非特殊需求,不然很少直接接触Native层,昨天逛社区的时候看到一篇大佬的文章,就点开看了下,发现监控图片不是hook Java层,是hook了native层,然后就pull了源码,顿时发现没接触过C++的人看起来真费劲啊,于是想写篇文章记录下。
申明一下本文只是针对怎么理解JNI的调用流程,做下梳理,适合初学者,大佬不要喷我。下面就开始吧!!!
二、餐前小吃
实战前先来个简单的例子,熟悉一下基本流程,下面是使用Jni的一个简单例子:
1、首先创建一个MainActivity:
scala
复制代码
public class MainActivity extends AppCompatActivity { static { System.loadLibrary("native-lib"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tv = findViewById(R.id.sample_text); tv.setText(stringFromJNI()); } public native String stringFromJNI(); }
MainActivity内部先试用静态代码块调用System.loadLibrary("native-lib"),
目的是先加载资源库,其中的“native-lib
”是在CMakeLists文件中命名的。然后就是定义了一个native的stringFromJNI()
方法。
2、创建native-lib.cpp:
c
复制代码
#include <jni.h> #include <string> extern "C" JNIEXPORT jstring JNICALL Java_com_example_jniexample_MainActivity_stringFromJNI(JNIEnv* env, jclass clazz) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); }
此文件就是上述stringFromJNI()
的native实现,可以看到方法如下:
Java_com_example_jniexample_MainActivity_stringFromJNI(Java_包名_java类名_方法名),
入参有(JNIEnv* env, jclass clazz) ,首先java层没有定义入参,native这个是必有得,一个是Jni环境的指针,一个是java层的类名,如果java层有其他的参数,native层也加在后面即可。
关于extern "C" JNIEXPORT jstring JNICALL:
-
extern "C" JNIEXPORT jstring JNICALL 是JNI接口函数的声明,它告诉编译器如何将C/C++代码编译成可以被Java调用的本地库。
-
在C++中,函数名称是由函数名和参数列表组成的,编译器会将函数名称转换为一种叫做mangled name的符号表示方式。但是,在JNI中,Java需要能够通过函数名称来调用本地库中的函数,Java不支持mangled name,因此需要通过extern "C"来指定函数按照C语言方式进行编译,避免C++编译器将函数名称转换为mangled name。
-
JNIEXPORT指定了函数的链接属性,告诉编译器应该导出函数,以便在本地库中可以被其他模块或程序使用。
-
jstring指定了函数的返回类型,表示返回一个Java的String类型。
-
JNICALL指定了函数调用约定,在Windows平台上,JNI函数的调用约定为__stdcall,而在其他平台上通常是__cdecl,JNICALL会根据平台自动选择正确的调用约定。
因此,extern "C" JNIEXPORT jstring JNICALL是JNI接口函数的标准声明方式,它告诉编译器如何将C/C++代码编译成Java可调用的本地库。
3、创建CMakeLists
scss
复制代码
# 指定cmake的最小版本 cmake_minimum_required(VERSION 3.4.1) # 创建一个库 add_library( native-lib SHARED native-lib.cpp ) # 查找log库 find_library( log-lib log ) # 链接库到目标库 target_link_libraries( native-lib ${log-lib} )
关于CMakeLists:
CMakeLists是CMake构建系统时必需的文件,用于指定信息和依赖关系,文件由一系列命令组成,每个命令都由一个调用和一组参数组成。以下是一些常用的命令:
cmake_minimum_required(VERSION x)
:指定CMake的最低版本,x为版本号。project(name)
:指定项目名称,可以包含项目的版本、描述等信息。add_library(name type source1 source2 ...)
:创建一个库,其中name为库的名称,type为库的类型(STATIC、SHARED或MODULE),source为库的源文件。target_link_libraries(target library1 library2 ...)
:将目标库与指定的库链接起来。target是目标库的名称,library是需要链接的库名称。find_library(name path)
:查找指定名称的库,并将其路径存储在变量中。name是库的名称,path是查找的路径。include_directories(directory)
:添加一个目录到包含路径中。add_definitions(definition)
:添加一个编译定义。
在Android JNI项目中,我们可以使用add_library命令创建一个动态库,使用find_library命令查找需要链接的库,使用target_link_libraries命令将它们链接到目标库中。我们还可以使用include_directories命令添加包含路径和add_definitions命令添加编译定义。
除了上述命令,还有其他命令可用于指定编译选项、设置环境变量、生成可执行文件等。CMakeLists文件的语法和命令非常灵活,开发者可以根据自己的需求和项目特点进行自定义。
我们创建的库是add_library(native-lib SHARED native-lib.cpp)
名称是“native-lib”,这个就跟MainActivity中 System.loadLibrary("native-lib")
对应。然后我们创建的是SHARED library,也称为动态库或共享库,是一种在程序运行时被动态加载的库,它与静态库(Static library)相对。静态库在编译时被链接到可执行文件中,因此可执行文件比较大,但是静态库的加载速度较快。动态库则可以在程序运行时动态地加载和卸载,因此可执行文件较小,但是加载速度较慢。
关于库类型:
SHARED library(动态库)
在多个程序之间共享代码和数据ÿ