一、写在前面
作为一个Android UI Api调用工程师,在日常中咱们触摸的更多是java层,除非特别需求,不然很少直接触摸Native层,昨天逛社区的时分看到一篇大佬的文章,就点开看了下,发现逻辑是经过hook技能完成的,并且不是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文件的途径指定为外部构建途径。
这样一个简略的比方就说完了。
三、开饭
关于Hook
Native 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
文件一般会在程序的各个模块之间同享,以便在编译时进行链接。
将程序的完成和声明分隔有以下优点:
- 增加代码的可读性和可维护性。将程序的完成和声明分隔能够使代码更加明晰,易于了解和维护。
- 进步代码的复用性。将程序的声明和界说分隔能够使代码更易于重用,由于不同的模块能够同享同一个头文件。
- 进步编译功率。将程序的完成和声明分隔能够使编译器更容易地生成方针代码,由于编译器能够在编译时直接运用现已声明好的函数和变量。
总之,将程序的完成和声明分隔是一种良好的编程习气,能够使代码更加明晰、易于维护和重用,一起也能够进步编译功率。
上菜
首要贴上大佬的源码github.com/shixinzhang… 。 由于我不是分析大佬开源库的完成逻辑,仅仅以这个比方来说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、详细函数
1、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的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"
关于shadowhook_hook_sym_name(so, symbol)的入参
1、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
是要仿制到本地的目录途径。
2、symbol
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"
是过滤不出来的。所以菜鸟用菜办法:
i、先打印过滤出一切的原函数
readelf -WsC libart-64-Q.so | grep 'art::ArtMethod::Invoke'
ii、打印一切的函数符号名,经过上面的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);
}
2、shadowhook_unhook
这个便是直接运用Shadowhook的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;
...
}
除了shadowhook_hook_sym_name以外,Shadowhook还供给其他几个函数
#include "shadowhook.h"
void *shadowhook_hook_func_addr(
void *func_addr,
void *new_addr,
void **orig_addr);
void *shadowhook_hook_sym_addr(
void *sym_addr,
void *new_addr,
void **orig_addr);
void *shadowhook_hook_sym_name(
const char *lib_name,
const char *sym_name,
void *new_addr,
void **orig_addr);
typedef void (*shadowhook_hooked_t)(
int error_number,
const char *lib_name,
const char *sym_name,
void *sym_addr,
void *new_addr,
void *orig_addr,
void *arg);
void *shadowhook_hook_sym_name_callback(
const char *lib_name,
const char *sym_name,
void *new_addr,
void **orig_addr,
shadowhook_hooked_t hooked,
void *hooked_arg);
int shadowhook_unhook(void *stub);
详细怎样运用能够去gitHub检查。
3、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对咱们来说仍是很有协助的。