Android Hook native初探 🤓
一、写在前面
作为一个Android UI Api调用工程师,在日常中我们接触的更多是java层,除非特殊需求,不然很少直接接触Native层,昨天逛社区的时候看到一篇大佬的文章,就点开看了下,发现监控图片不是hook Java层,是hook了native层,然后就pull了源码,顿时发现没接触过C++的人看起来真费劲啊,于是想写篇文章记录下。
申明一下本文只是针对怎么理解JNI的调用流程,做下梳理,适合初学者,大佬不要喷我。下面就开始吧!!!
二、餐前小吃
实战前先来个简单的例子,熟悉一下基本流程,下面是使用Jni的一个简单例子:
1、首先创建一个MainActivity:
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:
#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
# 指定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(动态库)
在多个程序之间共享代码和数据,因此可以减少内存的使用,提高程序的效率。例如,在Linux系统中,许多系统库都是以共享库的形式提供的,如libc.so、libm.so等。STATIC library(静态库)
:静态库是在编译时被链接到可执行文件中的库,它的内容被复制到可执行文件中,因此可执行文件比较大。静态库适用于需要在多个程序中共享的代码和数据,因为它们不需要在每个程序中重新加载。MODULE library(模块库)
:模块库与共享库类似,但是它们的链接方式不同。模块库在运行时会被动态地加载,但是它们不会被链接到可执行文件中。模块库适用于需要动态加载和卸载的插件。OBJECT library(对象库)
:对象库是一组编译好的对象文件,它们可以被多个目标文件或库共享。对象库可以看作是静态库和共享库的折中方案,它可以在编译时链接到可执行文件中,也可以在运行时动态加载。
SHARED library以外的创建方式基本相同,只需将add_library()命令的第二个参数改为相应的类型即可。例如:
add_library(my_static_lib STATIC my_static_lib.cpp)
add_library(my_module_lib MODULE my_module_lib.cpp)
add_library(my_object_lib OBJECT my_object_lib.cpp)
4、在gradle中配置cmake
需要根据具体的需求选择合适的库类型,以达到最佳的性能和灵活性。
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
cppFlags "-frtti -fexceptions"
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
在build.gradle中配置NDK。在Android Studio中,我们需要在build.gradle文件中配置NDK以使用JNI。在这个示例项目中,我们需要将以下代码添加到build.gradle文件中。
在这个代码中,我们指定了编译选项(RTTI和异常),并选择了支持的ABI(armeabi-v7a、arm64-v8a、x86和x86_64)。我们还指定了使用CMake来构建JNI代码,并将CMakeLists.txt文件的路径指定为外部构建路径。
这样一个简单的例子就说完了。
三、开饭
首先贴上大佬的源码github.com/shixinzhang… 。
关于Hook
大佬hook用库的是字节的ShadowHook,字节还有个库是ByteHook,这俩一个是Android inline hook 一个是 Android PLT hook,简单说下区别:
1. 实现方式不同
Inline Hook是通过修改目标函数的前几条汇编指令,将其跳转到Hook函数的代码中,从而实现Hook的目的。这种方式需要对目标函数的汇编代码非常熟悉,并且需要处理一些细节问题,比较复杂。
PLT Hook(也称为“Got Hook”)是通过修改PLT(Procedure Linkage Table)中的地址,将其指向Hook函数的代码地址,从而实现Hook的目的。这种方式相对简单,不需要对目标函数的汇编代码进行处理,只需要对PLT表进行修改即可。
2. Hook方式不同
Inline Hook是在目标函数被调用时,将其跳转到Hook函数中执行,并在Hook函数中处理完毕后再跳回到原函数继续执行。这种方式需要复制一份原函数的代码并进行修改,会占用一定的内存空间。
PLT Hook是在目标函数被调用时,将其直接跳转到Hook函数中执行,并不会再跳回到原函数继续执行。这种方式不需要复制原函数的代码,因此不会占用额外的内存空间。
3. 兼容性不同
Inline Hook的兼容性相对较差,因为它需要修改目标函数的汇编代码,而目标函数的汇编代码随着系统版本和架构的变化而不同,因此需要针对不同的系统版本和架构进行不同的处理。
PLT Hook的兼容性相对较好,因为它只需要修改PLT表中的地址,而PLT表的结构相对稳定,不会随着系统版本和架构的变化而变化。
总的来说,Inline Hook和PLT Hook各有优缺点,具体使用哪种方式取决于具体的应用场景和需求。如果对Hook的性能和兼容性要求较高,可以考虑使用PLT Hook。如果需要Hook的函数比较特殊,无法使用PLT Hook实现,可以考虑使用Inline Hook。
然后关于ShadowHook,它提供以下函数:
shadowhook_hook_func_addr
: 通过绝对地址 hook 一个在 ELF 中没有符号信息的函数。shadowhook_hook_sym_addr
:通过绝对地址 hook 一个在 ELF 中有符号信息的函数。shadowhook_hook_sym_name
:通过符号名和 ELF 的文件名或路径名 hook 一个函数。shadowhook_hook_sym_name_callback
:和shadowhook_hook_sym_name
类似,但是会在 hook 完成后调用指定的回调函数。shadowhook_unhook
:unhook。
.h 与.cpp
我们知道在C++中,通常将程序的实现和声明分开,实现通常放在.cpp
文件中,而声明通常放在.h
文件中。
.cpp
文件(即源文件)包含了程序的具体实现,通常包含各种函数实现、类定义、全局变量等内容。每个.cpp
文件通常都要包含对应的.h
文件,以便在编译时将各个模块的代码整合成一个可执行文件。
.h
文件(即头文件)包含了程序的各种声明和定义,通常包含函数声明、类定义、常量定义、宏定义等内容。.h
文件通常会在程序的各个模块之间共享,以便在编译时进行链接。
将程序的实现和声明分开有以下好处:
- 增加代码的可读性和可维护性。将程序的实现和声明分开可以使代码更加清晰,易于理解和维护。
- 提高代码的复用性。将程序的声明和定义分开可以使代码更易于重用,因为不同的模块可以共享同一个头文件。
- 提高编译效率。将程序的实现和声明分开可以使编译器更容易地生成目标代码,因为编译器可以在编译时直接使用已经声明好的函数和变量。
总之,将程序的实现和声明分开是一种良好的编程习惯,可以使代码更加清晰、易于维护和重用,同时也可以提高编译效率。
上菜
因为我不是分析大佬开源库的实现逻辑,只是以这个例子来说JNI的使用,所以不会贴出所有的代码,只挑关键的说了。
关于JNIEnv *env
在Android Native Development Kit(NDK)中,JNIEnv是一个指向JNI环境的指针,用于在Native代码和Java代码之间进行通信。JNIEnv是由JNI提供的一组API函数的集合,可以在Native代码中使用这些函数来调用Java代码、操作Java对象等。
JNIEnv是Java Native Interface(JNI)规范中定义的一部分,它是JNI的一种实现。在使用JNIEnv之前,需要将当前线程附加到Java虚拟机中,以便JNIEnv可以正确地操作Java对象,然后才能使用JNIEnv来调用Java方法、获取Java对象的字段、创建Java对象等。
JNIEnv提供了一系列的函数,例如CallVoidMethod
、CallObjectMethod
、GetFieldID
、GetObjectField
等,用于在Native代码中调用Java代码、获取Java对象的字段、方法等信息。这些函数都是通过JNIEnv指针来调用的。
需要注意的是,JNIEnv是与线程相关的,因此在使用JNIEnv之前,必须先使用AttachCurrentThread
函数将当前线程附加到Java虚拟机中。在使用完JNIEnv之后,还需要使用DetachCurrentThread
函数将当前线程与Java虚拟机分离,以避免资源泄漏和内存泄漏等问题,在此过程中需要用到JavaVM*
,这个在JNI_OnLoad
中会传入,我们保存下来即可。
综上所述,JNIEnv是JNI环境的一个指针,提供了一系列的函数和操作,用于在Native代码和Java代码之间进行通信。
涉及文件
与其他三方库一样,作者使用了ContentProvider,在里面registerActivityLifecycleCallbacks,但是Hook的初始化写在了Application中,因为有自定义的配置这个不好自动初始化。关于Jni部分主要是两个文件BitmapMonitor.java
和 bitmap_monitor.cpp
一个java层 一个native层
1、BitmapMonitor.java
@Keep
private static native int hookBitmapNative(long checkRecycleInterval, long getStackThreshold,
long copyLocalThreshold, String copyDir, boolean clearFileWhenOutOfThreshold);
@Keep
private static native void stopHookBitmapNative();
/**
* 仅仅获取数量
*
* @return
*/
@Keep
private static native BitmapMonitorData dumpBitmapCountNative();
/**
* Get all bitmap info
* @param ensureRestoreImage whether need check and restore again
* @return
*/
@Keep
private static native BitmapMonitorData dumpBitmapInfoNative(boolean ensureRestoreImage);
2、bitmap_monitor.cpp
// 自定义数据类型 作者把临时变量类名以及JNI环境都收拢在这里,方便后续使用
static struct BitmapMonitorContext g_ctx;
// 这是启动时会调用的
extern "C"
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
g_ctx.java_vm = vm;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
...
return JNI_VERSION_1_6;
}
// 以下是对应Java中的native函数
extern "C"
JNIEXPORT jint JNICALL
Java_top_shixinzhang_bitmapmonitor_BitmapMonitor_hookBitmapNative(JNIEnv *env, jclass clazz,
jlong check_recycle_interval,
jlong get_stack_threshold,
jlong restore_image_threshold,
jstring restore_image_dir,
jboolean notify_check_local_image_size) {
const char* dir = env->GetStringUTFChars(restore_image_dir, 0);
return do_hook_bitmap(check_recycle_interval, get_stack_threshold, restore_image_threshold, dir, notify_check_local_image_size);
}
extern "C"
JNIEXPORT void JNICALL
Java_top_shixinzhang_bitmapmonitor_BitmapMonitor_stopHookBitmapNative(JNIEnv *env, jclass clazz) {
g_ctx.open_hook = false;
if (g_ctx.shadowhook_stub != nullptr) {
shadowhook_unhook(g_ctx.shadowhook_stub);
}
}
extern "C"
JNIEXPORT jobject JNICALL
Java_top_shixinzhang_bitmapmonitor_BitmapMonitor_dumpBitmapCountNative(JNIEnv *env, jclass clazz) {
if (!g_ctx.open_hook) {
return nullptr;
}
return do_dump_info(env, true, false);
}
extern "C"
JNIEXPORT jobject JNICALL
Java_top_shixinzhang_bitmapmonitor_BitmapMonitor_dumpBitmapInfoNative(JNIEnv *env, jclass clazz, jboolean ensureRestoreImage) {
return do_dump_info(env, false, ensureRestoreImage);
}
以上是贴了java与native调用的方法定义,Java层的就不看了,直接看cpp的。
3、具体函数
do_hook_bitmap
hook函数Java_top_shixinzhang_bitmapmonitor_BitmapMonitor_hookBitmapNative调用了do_hook_bitmap。
jint do_hook_bitmap(long bitmap_recycle_check_interval,
long get_stack_threshold,
long restore_image_threshold,
const char *restore_image_dir,
bool notify_check_local_image_size) {
g_recycle_check_interval_second = bitmap_recycle_check_interval;
g_get_stack_threshold = get_stack_threshold;
g_restore_image_threshold = restore_image_threshold;
g_restore_image_dir = restore_image_dir;
g_notify_check_local_image_size = notify_check_local_image_size;
int api_level = get_api_level();
if (api_level > 33) {
return -2;
}
LOGI("hookBitmapNative called, printStackThreshold: %ld, restore_image_threshold: %ld, api_level: %d",
get_stack_threshold, restore_image_threshold, api_level);
auto so = api_level > API_LEVEL_10_0 ? BITMAP_CREATE_SYMBOL_SO_RUNTIME_AFTER_10 : BITMAP_CREATE_SYMBOL_SO_RUNTIME;
auto symbol = api_level >= API_LEVEL_8_0 ? BITMAP_CREATE_SYMBOL_RUNTIME : BITMAP_CREATE_SYMBOL_BEFORE_8;
auto stub = shadowhook_hook_sym_name(so, symbol, (void *) create_bitmap_proxy,nullptr);
if (stub != nullptr) {
g_ctx.open_hook = true;
g_ctx.shadowhook_stub = stub;
JNIEnv *jni_env;
if (g_ctx.java_vm->AttachCurrentThread(&jni_env, nullptr) == JNI_OK) {
jclass bitmap_java_class = jni_env->FindClass("android/graphics/Bitmap");
g_ctx.bitmap_recycled_method = jni_env->GetMethodID(bitmap_java_class, "isRecycled",
"()Z");
jclass bitmap_info_jobject = jni_env->FindClass(
"top/shixinzhang/bitmapmonitor/BitmapMonitorData");
g_ctx.bitmap_info_jclass = static_cast<jclass>(jni_env->NewGlobalRef(
bitmap_info_jobject));
g_ctx.report_bitmap_data_method = jni_env->GetStaticMethodID(g_ctx.bitmap_monitor_jclass,
"reportBitmapInfo",
"(Ltop/shixinzhang/bitmapmonitor/BitmapMonitorData;)V");
g_ctx.report_bitmap_file_method = jni_env->GetStaticMethodID(g_ctx.bitmap_monitor_jclass,
"reportBitmapFile",
"(Ljava/lang/String;)V");
}
//hook 成功后,开启一个线程,定时轮训当前保存的数据,如果发现有被 recycle 的,移出去,更新总体数据
start_loop_check_recycle_thread();
return 0;
}
g_ctx.open_hook = false;
g_ctx.shadowhook_stub = nullptr;
return -1;
}
上述代码中是用于hook native的逻辑,使用的是shadowhook_hook_sym_name(so, symbol, (void* )
函数,关于so和symbol参数,做了判断
auto so = api_level > API_LEVEL_10_0 ? BITMAP_CREATE_SYMBOL_SO_RUNTIME_AFTER_10 : BITMAP_CREATE_SYMBOL_SO_RUNTIME;
auto symbol = api_level >= API_LEVEL_8_0 ? BITMAP_CREATE_SYMBOL_RUNTIME : BITMAP_CREATE_SYMBOL_BEFORE_8;
#define BITMAP_CREATE_SYMBOL_SO_RUNTIME_AFTER_10 "libhwui.so"
#define BITMAP_CREATE_SYMBOL_SO_RUNTIME "libandroid_runtime.so"
#define BITMAP_CREATE_SYMBOL_RUNTIME "_ZN7android6bitmap12createBitmapEP7_JNIEnvPNS_6BitmapEiP11_jbyteArrayP8_jobjecti"
#define BITMAP_CREATE_SYMBOL_BEFORE_8 "_ZN11GraphicsJNI12createBitmapEP7_JNIEnvPN7android6BitmapEiP11_jbyteArrayP8_jobjecti"
so即需要被hook的.so文件
这个so文件是系统的,那怎么拿到这个so文件呢,换句话说,我要查看下面的函数符号名,需要拿到这个so文件。我们知道这些so文件是系统的,要获取它,先要把我们的手机进行root处理,否则是拿不到的,下面简单说下步骤:
-
确认 Android 设备已经连接到本地 PC,可以通过以下命令来查看设备是否已经连接:
adb devices
-
在本地 PC 上使用
adb shell
命令进入 Android 设备的 shell 环境:adb shell
-
使用
cd
命令进入到.so
文件所在的目录,例如/system/lib
或/system/lib64
目录:cd /system/lib
-
使用
ls
命令查看当前目录下的所有文件,找到需要复制的.so
文件:ls
-
使用
chmod
命令修改目标.so
文件的权限为 777,如chmod 777 libhello.so
:chmod 777 libhello.so
-
使用
exit
命令退出 Android 设备的 shell 环境,回到本地 PC 的命令行界面:exit
-
在本地 PC 的命令行界面中,使用
adb pull
命令将目标.so
文件复制到本地 PC,例如:adb pull /system/lib/libhello.so /path/to/local/folder
其中
/system/lib/libhello.so
是要复制的.so
文件的路径,/path/to/local/folder
是要复制到本地的目录路径。
symbol是函数符号名
关于查看这个名称可以通过readelf(本地需安装,brew update && brew install binutils
,安装之后需要配置环境变量,安装之后,终端窗口会提示你的直接复制粘贴即可),比如:
// 我只是举个例子 libmyLib不存在
readelf -Ws --wide libmyLib.so | grep "art::ArtMethod::Invoke"
这里我发现个问题,本人太菜,理论想通过上述命名直接过滤出来,但是readelf -Ws --wide libmyLib.so
的打印结果如下:
只有经过 C++ Name Mangler 处理后的函数符号名,所以加
grep "art::ArtMethod::Invoke"
是过滤不出来的。所以菜鸟用菜办法:
1、先打印过滤出所有的原函数
readelf -WsC libart-64-Q.so | grep 'art::ArtMethod::Invoke'
2、打印所有的函数符号名,通过上面的id,找到目标
命令为readelf -Ws --wide libart-64-Q.so
,以上面的id 5264
为例,搜索后:
可知
art::ArtMethod::Invoke
对应的函数符号名为_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc
。
接着就是填充变量了,比如report_bitmap_data_method后面会用到。最后是调用 start_loop_check_recycle_thread
启动一个线程,启动线程使用pthread_create
,更多的函数可以查看pthread.h
文件
void start_loop_check_recycle_thread() {
pthread_t thread;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
/*
* pthread_create (thread, attr, start_routine, arg)
*
* thread: 指向线程标识符指针。
* attr: 一个不透明的属性对象,可以被用来设置线程属性。您可以指定线程属性对象,也可以使用默认值 NULL。
* start_routine: 线程运行函数起始地址,一旦线程被创建就会执行。
* arg: 运行函数的参数。它必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数,则使用 NULL。
*/
// 开启线程
pthread_create(&thread, &attr, thread_routine, nullptr);
}
shadowhook_unhook
这个就是直接使用框架的shadowhook_unhook
参数的g_ctx.shadowhook_stub
是在上面hook的时候填充的
auto stub = shadowhook_hook_sym_name(so, symbol, (void *) create_bitmap_proxy,nullptr);
if (stub != nullptr) {
g_ctx.open_hook = true;
g_ctx.shadowhook_stub = stub;
...
}
do_dump_info
这个就是具体的组装数据回传了,业务逻辑不说了,就说说数据是怎么组装且返回的,
跟java一样我们也需要拼装我们需要的结构,这时候会用到env->XXX
,env
是前面说的JNI环境指针,他提供了很多函数创建数据结构NewObjectArray
,NewCharArray
,NewObject
等等,
我们看下源码中新建一个记录的代码:
jobject java_record = env->NewObject(
g_ctx.bitmap_record_class,
g_ctx.bitmap_record_constructor_method,
(jlong) record.native_ptr,
(jint) record.width,
(jint) record.height,
(jint) (record.stride / record.width),
(jint) record.format,
record.time,
save_path,
stacks,
current_scene
);
g_ctx.bitmap_record_class和g_ctx.bitmap_record_constructor_method的来源如下:
jclass bitmap_record_clz = env->FindClass("top/shixinzhang/bitmapmonitor/BitmapRecord");
g_ctx.bitmap_record_class = (jclass)env->NewGlobalRef(bitmap_record_clz);
g_ctx.bitmap_record_constructor_method = env->GetMethodID(g_ctx.bitmap_record_class,
"<init>",
"(JIIIIJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
一个是java中的类,一个是类对应的构造函数名,解释下"(JIIIIJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"
(JIIIIJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
,可以按照以下方式解读:
(JIIIIJ
:表示方法参数列表,依次为long
类型、int
类型、int
类型、int
类型、int
类型、long
类型。Ljava/lang/String;
:表示一个String
对象。Ljava/lang/String;
:表示一个String
对象。Ljava/lang/String;
:表示一个String
对象。)V
:表示方法返回值类型为void
。
也就是说这个是规定构造函数入参的结构类型以及参数顺序的。
对比下Java代码
public BitmapRecord(long nativePtr, int width, int height, int bitsPerPixel,
int format, long time, String pictureExplorePath,
String createStack, String currentScene) {
this.nativePtr = nativePtr;
this.width = width;
this.height = height;
this.bitsPerPixel = bitsPerPixel;
this.format = format;
this.time = time;
this.pictureExplorePath = pictureExplorePath;
this.createStack = createStack;
this.currentScene = currentScene;
}
是不是就对上了,哈哈哈,好了到这基本就是记录完了,回过头说,本文还是对使用JNI的基本介绍,顺便拿大佬的代码作为例子。
四、最后
最后就是没有啥了,这篇文章只是用来熟悉JNI的,因为很多功能我们很难再java层实现,所以学习一下native对我们来说还是很有帮助的。
转载自:https://juejin.cn/post/7250002639768141880