Android Hook native初探

一、写在前面

作为一个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)相对。静态库在编译时被链接到可执行文件中,因此可执行文件比较大,但是静态库的加载速度较快。动态库则可以在程序运行时动态地加载和卸载,因此可执行文件较小,但是加载速度较慢。

关于库类型:

  1. SHARED library(动态库) 在多个程序之间共享代码和数据,因此可以减少内存的使用,提高程序的效率。例如,在Linux系统中,许多系统库都是以共享库的形式提供的,如libc.so、libm.so等。
  2. STATIC library(静态库) :静态库是在编译时被链接到可执行文件中的库,它的内容被复制到可执行文件中,因此可执行文件比较大。静态库适用于需要在多个程序中共享的代码和数据,因为它们不需要在每个程序中重新加载。
  3. MODULE library(模块库) :模块库与共享库类似,但是它们的链接方式不同。模块库在运行时会被动态地加载,但是它们不会被链接到可执行文件中。模块库适用于需要动态加载和卸载的插件。
  4. OBJECT library(对象库) :对象库是一组编译好的对象文件,它们可以被多个目标文件或库共享。对象库可以看作是静态库和共享库的折中方案,它可以在编译时链接到可执行文件中,也可以在运行时动态加载。

SHARED library以外的创建方式基本相同,只需将add_library()命令的第二个参数改为相应的类型即可。例如:

 
 

scss

复制代码

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

需要根据具体的需求选择合适的库类型,以达到最佳的性能和灵活性。

 
 

lua

复制代码

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文件通常会在程序的各个模块之间共享,以便在编译时进行链接。

将程序的实现和声明分开有以下好处:

  1. 增加代码的可读性和可维护性。将程序的实现和声明分开可以使代码更加清晰,易于理解和维护。
  2. 提高代码的复用性。将程序的声明和定义分开可以使代码更易于重用,因为不同的模块可以共享同一个头文件。
  3. 提高编译效率。将程序的实现和声明分开可以使编译器更容易地生成目标代码,因为编译器可以在编译时直接使用已经声明好的函数和变量。

总之,将程序的实现和声明分开是一种良好的编程习惯,可以使代码更加清晰、易于维护和重用,同时也可以提高编译效率。

上菜

因为我不是分析大佬开源库的实现逻辑,只是以这个例子来说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提供了一系列的函数,例如CallVoidMethodCallObjectMethodGetFieldIDGetObjectField等,用于在Native代码中调用Java代码、获取Java对象的字段、方法等信息。这些函数都是通过JNIEnv指针来调用的。

需要注意的是,JNIEnv是与线程相关的,因此在使用JNIEnv之前,必须先使用AttachCurrentThread函数将当前线程附加到Java虚拟机中。在使用完JNIEnv之后,还需要使用DetachCurrentThread函数将当前线程与Java虚拟机分离,以避免资源泄漏和内存泄漏等问题,在此过程中需要用到JavaVM*,这个在JNI_OnLoad中会传入,我们保存下来即可。

综上所述,JNIEnv是JNI环境的一个指针,提供了一系列的函数和操作,用于在Native代码和Java代码之间进行通信。

涉及文件

与其他三方库一样,作者使用了ContentProvider,在里面registerActivityLifecycleCallbacks,但是Hook的初始化写在了Application中,因为有自定义的配置这个不好自动初始化。关于Jni部分主要是两个文件BitmapMonitor.javabitmap_monitor.cpp 一个java层 一个native层

1、BitmapMonitor.java

 
 

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

 
 

kotlin

复制代码

// 自定义数据类型 作者把临时变量类名以及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。

 
 

ini

复制代码

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参数,做了判断

 
 

arduino

复制代码

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处理,否则是拿不到的,下面简单说下步骤:

  1. 确认 Android 设备已经连接到本地 PC,可以通过以下命令来查看设备是否已经连接:

     

    复制代码

    adb devices
  2. 在本地 PC 上使用 adb shell 命令进入 Android 设备的 shell 环境:

     

    复制代码

    adb shell
  3. 使用 cd 命令进入到 .so 文件所在的目录,例如 /system/lib/system/lib64 目录:

     bash 

    复制代码

    cd /system/lib
  4. 使用 ls 命令查看当前目录下的所有文件,找到需要复制的 .so 文件:

     bash 

    复制代码

    ls
  5. 使用 chmod 命令修改目标 .so 文件的权限为 777,如 chmod 777 libhello.so

     bash 

    复制代码

    chmod 777 libhello.so
  6. 使用 exit 命令退出 Android 设备的 shell 环境,回到本地 PC 的命令行界面:

     bash 

    复制代码

    exit
  7. 在本地 PC 的命令行界面中,使用 adb pull 命令将目标 .so 文件复制到本地 PC,例如:

     bash 

    复制代码

    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,安装之后需要配置环境变量,安装之后,终端窗口会提示你的直接复制粘贴即可),比如:

 
 

arduino

复制代码

// 我只是举个例子 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文件

 
 

scss

复制代码

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的时候填充的

 
 

ini

复制代码

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->XXXenv是前面说的JNI环境指针,他提供了很多函数创建数据结构NewObjectArrayNewCharArrayNewObject等等, 我们看下源码中新建一个记录的代码:

 
 

csharp

复制代码

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的来源如下:

 
 

ini

复制代码

jclass bitmap_record_clz = env->FindClass("top/shixinzhang/bitmapmonitor/BitmapRecord"); g_ctx.bitmap_record_class = (jclass)env->NewGlobalRef(bitmap_record_clz);

 
 

bash

复制代码

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代码

 
 

ini

复制代码

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对我们来说还是很有帮助的。

猜你喜欢

转载自blog.csdn.net/BASK2312/article/details/131499409