一、写在前面
作为一个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(动态库)
在多个程序之间共享代码和数据,因此可以减少内存的使用,提高程序的效率。例如,在Linux系统中,许多系统库都是以共享库的形式提供的,如libc.so、libm.so等。STATIC library(静态库)
:静态库是在编译时被链接到可执行文件中的库,它的内容被复制到可执行文件中,因此可执行文件比较大。静态库适用于需要在多个程序中共享的代码和数据,因为它们不需要在每个程序中重新加载。MODULE library(模块库)
:模块库与共享库类似,但是它们的链接方式不同。模块库在运行时会被动态地加载,但是它们不会被链接到可执行文件中。模块库适用于需要动态加载和卸载的插件。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
文件通常会在程序的各个模块之间共享,以便在编译时进行链接。
将程序的实现和声明分开有以下好处:
- 增加代码的可读性和可维护性。将程序的实现和声明分开可以使代码更加清晰,易于理解和维护。
- 提高代码的复用性。将程序的声明和定义分开可以使代码更易于重用,因为不同的模块可以共享同一个头文件。
- 提高编译效率。将程序的实现和声明分开可以使编译器更容易地生成目标代码,因为编译器可以在编译时直接使用已经声明好的函数和变量。
总之,将程序的实现和声明分开是一种良好的编程习惯,可以使代码更加清晰、易于维护和重用,同时也可以提高编译效率。
上菜
因为我不是分析大佬开源库的实现逻辑,只是以这个例子来说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
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处理,否则是拿不到的,下面简单说下步骤:
-
确认 Android 设备已经连接到本地 PC,可以通过以下命令来查看设备是否已经连接:
复制代码
adb devices
-
在本地 PC 上使用
adb shell
命令进入 Android 设备的 shell 环境:复制代码
adb shell
-
使用
bashcd
命令进入到.so
文件所在的目录,例如/system/lib
或/system/lib64
目录:复制代码
cd /system/lib
-
使用
bashls
命令查看当前目录下的所有文件,找到需要复制的.so
文件:复制代码
ls
-
使用
bashchmod
命令修改目标.so
文件的权限为 777,如chmod 777 libhello.so
:复制代码
chmod 777 libhello.so
-
使用
bashexit
命令退出 Android 设备的 shell 环境,回到本地 PC 的命令行界面:复制代码
exit
-
在本地 PC 的命令行界面中,使用
bashadb 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
,安装之后需要配置环境变量,安装之后,终端窗口会提示你的直接复制粘贴即可),比如:
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->XXX
,env
是前面说的JNI环境指针,他提供了很多函数创建数据结构NewObjectArray
,NewCharArray
,NewObject
等等, 我们看下源码中新建一个记录的代码:
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对我们来说还是很有帮助的。