For begin Android的so开发中,其他基本与C/C++一致,而与Java交互需要用到jni,也主要掌握jni的内容
jni,Java native interface,允许Java代码和其他语言进行交互
NDK,交叉编译工具链(PC开发,Android使用,就是交叉编译),AS里装NDK和Cmake就可以,如果使用ndk-build的话不需要cmake(一般不这样做),LLDB是调试用的(AS4.0之后不需要装)
cmakelist.txt里,配置了编译成哪个so、so的类型、引用了哪些库、哪些cpp编译成so
ABI,就是平台,包括armeabi-v7a、arm64-v8a、x86、x86_64,现在不支持mips
第一个NDK工程 NDK工程与纯Java工程的区别 创建NDK工程
build.gradle中与Java工程的区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 defaultConfig { ... externalNativeBuild { cmake { // 指定C++的版本 cppFlags "-std=c++11" } } // 指定编译成哪些平台的so,可以省略,默认就是编译全部 ndk { abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64' } } externalNativeBuild { cmake { // 指定CMakeLists.txt的路径 path "src/main/cpp/CMakeLists.txt" version "3.10.2" } }
New Project -> Native C++,C++标准选C++11
可以看到MainActivity.java里多出来了一些代码
1 2 3 4 5 6 7 8 static { System.loadLibrary("native-lib" ); } ... public native String stringFromJNI () ;
CMakeList.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 cmake_minimum_required (VERSION 3.10 .2 )project ("testcplus" )add_library ( native-lib SHARED native-lib.cpp ) find_library ( log-lib log ) target_link_libraries ( native-lib ${log-lib} )
JNI函数的静态注册规则 当MainActivity.java里调用stringFromJNI()
函数的时候,怎么知道去哪个so文件里找这个函数呢?
1 public native String stringFromJNI () ;
一种方法是静态注册,静态注册下,对应so文件的函数命名需要遵守特定的规则
native-lib.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <jni.h> #include <string> extern "C" JNIEXPORT jstring JNICALL Java_com_yourfff_testcplus_MainActivity_stringFromJNI( JNIEnv* env, jobject ) { std ::string hello = "Hello from C++" ; return env->NewStringUTF(hello.c_str()); }
一般不会用静态注册
JNIEnv
和jobject
,也就是上面的Java_com_yourfff_testcplus_MainActivity_stringFromJNI(...)
的两个参数,这两个参数是每个和Java层关联的函数都要有的参数,并且必须是第0个和第1个
JNIEnv
是JNI环境,通过JNIEnv
可以去调用相关的函数来完成C层调用Java、Java数据类型和C数据类型之间的转换
jobject
代表这个函数是被哪个对象调用的,这里的参数不一定是jobject
,也有可能是jclass
(当函数是静态函数的时候)
so中常用的log输出 1 2 3 4 5 6 7 8 9 10 #include <android/log.h> #define TAG "thisisalog" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__); #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__); #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__); #define 中的...和__VA_ARGS__ ...表示接受任意个数参数 __VA_ARGS__表示...处不管接受多少参数,都会天道这个位置
当然反编译的时候是看不到这些define的,编译的时候都给替换成实际调用的__android_log_print
函数了,参数也一并编译之后填进去了
NDK多线程 简介 每个进程中只有一份JavaVM
每个线程中都有一份JNIEnv
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 pthread_t thread;pthread_create(&thread, nullptr, myThread, nullptr); pthread_join(thread, nullptr); pthread_exit(0 ); void * myThread (void * a) { ... }
传递单个参数 void* (*__start_routine)(void*)
里面的参数void*
其实是代表可以传任意参数
1 2 3 4 5 6 7 8 9 10 void * myThread (void * a) { int *num = static_cast<int *>(a); ... } ... int num = 1 ;pthread_create(&thread, nullptr, myThread, &num);
传递多个参数 就用指针就行,数组指针、结构体指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void * myThread (void * a) { test* abc = static_cast<test *>(a); abc->name...; abc->age...; ... } struct test { string name; int age; }; test abc; abc.name = "john" ; abc.age = "5" ; pthread_create(&thread, nullptr, myThread, &abc);
接收返回值 要取返回值的话,必须有这两句
1 2 3 4 pthread_join(thread, nullptr); pthread_exit(0 );
返回值就通过pthread_exit()
函数来取
1 2 int pthread_join (pthread_t __pthread, void ** __return_value_ptr) ;void pthread_exit (void * __return_value) __noreturn;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void * myThread (void * a) { ... pthread_exit((void *)"testttttttt" ); } main { void * resPtr; pthread_join(thread, &resPtr); LOGD("stringFromJNI: %s" , (char *)resPtr); }
JNI_OnLoad so中各种函数的执行时机 按先后顺序 init
、init_array
、JNI_OnLoad
JNI_OnLoad
的定义
1 2 3 4 5 6 7 8 9 10 11 JNIEXPORT jint JNI_OnLoad (JavaVM *vm, void *reserved) { JNIEnv *env = nullptr; if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) { LOGD("GetEnv failed" ); return -1 ; } return JNI_VERSION_1_6; }
注意事项 一个so中可以不定义JNI_OnLoad
一旦定义了JNI_OnLoad
,在so首次加载的时候会自动执行
通常执行成功必须返回JNI版本JNI_VERSION_1_6
JavaVM JavaVM实际上就是一个结构体,里面封装了各种方法
JavaVM中的常用方法
GetEnv
(用来获取JNIEnv的)
1 2 3 4 5 JNIExport jint JNI_OnLoad (JavaVM *vm, void *reserved) { JNIEnv *env = nullptr; if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) { ...
除此之外还有 AttachCurrentThread(用来在子线程中获取JNIEnv)、DetachCurrentThread
JavaVM的获取方法:JNI_OnLoad
的第一个参数、JNI_OnUnload
的第一个参数、env->GetJavaVM
,各种方式获取的JavaVM的地址应该一样,因为进程只有一份JavaVM
JNIEnv 也是一个结构体
获取方式
函数静态/动态注册,传入的第一个参数,如下
1 2 3 4 5 6 7 extern "C" JNIEXPORT jstring JNICALLJava_com_yourfff_testcplus_MainActivity_stringFromJNI ( JNIEnv* env, jobject ) { std ::string hello = "Hello from C++" ; return env->NewStringUTF(hello.c_str()); }
或者vm->GetEnv
、globalVM->AttachCurrentThread(globalVM是定义的全局JavaVM结构体)
demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <jni.h> #include <string> #include <android/log.h> #include <pthread.h> #define TAG "testttttttt" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__); #define LOGI(...) __android_log_print(ANDROID_LOG_INFO , TAG, __VA_ARGS__); #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__); JavaVM* globalVM = nullptr; void * myThread (void * a) { JNIEnv* env = nullptr; if (globalVM->AttachCurrentThread(reinterpret_cast<JNIEnv **>(&env), nullptr) != JNI_OK){ LOGD("myThread GetEnv failed" ); }else { LOGD("myThread JNIEnv: %p" , env); } pthread_exit(0 ); } extern "C" JNIEXPORT jstring JNICALLJava_com_testttttttt_ndkdemo_MainActivity_stringFromJNI ( JNIEnv* env, jobject ) { LOGD("stringFromJNI JNIEnv: %p" , env); std ::string hello = "Hello from C++" ; pthread_t thread; pthread_create(&thread, nullptr, myThread, nullptr); pthread_join(thread, nullptr); return env->NewStringUTF(hello.c_str()); } JNIEXPORT jint JNI_OnLoad (JavaVM *vm, void *reserved) { globalVM = vm; JNIEnv *env = nullptr; if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) { LOGD("GetEnv failed" ); return -1 ; } JavaVM *vm2; env->GetJavaVM(&vm2); LOGD("JNI_OnLoad JavaVM1: %p" , vm); LOGD("JNI_OnLoad JavaVM2: %p" , vm2); LOGD("JNI_OnLoad JNIEnv: %p" , env); return JNI_VERSION_1_6; }
so相关的一些概念 出现在导入导出表的函数,一般可以通过frida相关API直接得到函数地址,或者自己计算
没有出现在导入导出表的函数,都需要计算函数地址
要完成so层的hook,都需要函数的地址
Java层中的native函数,被调用之后会找到so中对应的函数,简单地说就是Java调用C需要先完成函数注册
so层函数注册 JNI函数的静态注册 必须遵循一定的命名规则,一般是Java_包名_类名_方法名
系统会通过dlopen加载对应的so,通过dlsym来获取指定名字的函数地址,然后调用
静态注册的JNI函数,必然出现在导出表里
JNI函数的动态注册 通过env->RegisterNatives
注册函数,通常在JNI_OnLoad
中注册,所以动态注册在so加载的时候就注册了,导出表里找不到,但是Java里又能逆向出来的native函数,就有可能是动态注册的函数,这个时候就需要定位RegisterNatives
函数
可以给同一个Java函数注册多个native函数,以最后一次为准
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 jclass MainActivityClazz = env->FindClass("com/john/ndkdemo/MainActivity" ); JNINativeMethod methods[] = { {"stringFromJNI2" , "(I[BLjava/lang/String;)Ljava/lang/String;" , (void *)encodeFromC}, {"stringFromJNI2" , "(I[BLjava/lang/String;)Ljava/lang/String;" , (void *)encodeFromC1}, }; env->RegisterNatives(MainActivityClazz, methods, sizeof (methods) / sizeof (JNINativeMethod)); -------------------------------------------------------- typedef struct { const char * name; const char * signature; void * fnPtr; } JNINativeMethod;
C代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 jstring encodeFromC (JNIEnv* env, jobject obj, jint a, jbyteArray b, jstring c) { return env->NewStringUTF("encodeFromC" ); } jstring encodeFromC1 (JNIEnv* env, jobject obj, jint a, jbyteArray b, jstring c) { return env->NewStringUTF("encodeFromC1" ); } JNIEXPORT jint JNI_OnLoad (JavaVM *vm, void *reserved) { globalVM = vm; JNIEnv *env = nullptr; if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) { LOGD("GetEnv failed" ); return -1 ; } jclass MainActivityClazz = env->FindClass("com/newdemo/ndkdemo/MainActivity" ); JNINativeMethod methods[] = { {"stringFromJNI2" , "(I[BLjava/lang/String;)Ljava/lang/String;" , (void *)encodeFromC}, {"stringFromJNI2" , "(I[BLjava/lang/String;)Ljava/lang/String;" , (void *)encodeFromC1}, }; env->RegisterNatives(MainActivityClazz, methods, sizeof (methods) / sizeof (JNINativeMethod)); JavaVM *vm2; env->GetJavaVM(&vm2); LOGD("JNI_OnLoad JavaVM1: %p" , vm); LOGD("JNI_OnLoad JavaVM2: %p" , vm2); LOGD("JNI_OnLoad JNIEnv: %p" , env); return JNI_VERSION_1_6; }
异常处理 1 2 env->ExceptionDescribe(); env->ExceptionClear();
子线程中的ClassLoader与主线程中的ClassLoader是不一样的
子线程中是找不到主线程中的类的,比如我们在子线程中调用FindClass去获取类,是获取不了的,jclass MainActivityClazz = env->FindClass("com/newdemo/ndkdemo/MainActivity");
so文件编译 多个cpp文件编译成一个so 改CMakeLists.txt里的add_library就行
不同的cpp之间互相调用函数,使用extern关键字声明之后调用
编译多个so 编写多个cpp文件
修改CMakeLists.txt,加上add_library块和target_link_libraries块就行
Java静态代码块加载多个so
so之间相互调用 使用dlopen
、dlsym
、dlclose
获取函数地址,然后调用,需要导入dlfcn.h
,dlopen
在Android 7.0和7.1使用不了,但是现在这个版本的Android很少了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 jstring encodeFromC1 (JNIEnv* env, jobject obj, jint a, jbyteArray b, jstring c) { const char * soPath = env->GetStringUTFChars(c, nullptr); void *soinfo = dlopen(soPath, RTLD_NOW); void (*def)() = nullptr; def = reinterpret_cast<void (*)()>(dlsym(soinfo, "_Z4testv" )); def(); return env->NewStringUTF("encodeFromC1" ); }
或者使用extern关键字声明之后调用,这个和多个cpp编译成一个so的调用方法不一样
如果是多个cpp编译成一个so的话,a.cpp调用b.cpp里面定义的test()
函数,只需要声明一下就可以了
但是不同的so之间调用,除了声明之外,还需要修改CMakeList.txt,修改target_link_libraries块,把两个so文件添加进去
1 2 3 4 5 6 7 target_link_libraries( # Specifies the target library. newdemoA newdemoB # Links the target library to the log library # included in the NDK. ${log-lib} )
so路径动态获取 32和64的so存放路径不一样,为了更加通用,可以使用代码动态获取so文件的路径
app使用的so文件都存放在/data/app/
包名下面
1 2 3 4 5 6 7 8 9 10 11 12 13 public String getPath (Context cxt) { PackageManager pm = cxt.getPackageManager(); List<PackageInfo> pkgList = pm.getInstalledPackages(0 ); if (pkgList == null || pkgList.size() == 0 ) return null ; for (PackageInfo pi : pkgList) { if (pi.applicationInfo.nativeLibraryDir.startsWith("/data/app/" ) && pi.packageName.startsWith("com.newdemo.ndkdemo" )) { Log.e("newdemo" , pi.applicationInfo.nativeLibraryDir); return pi.applicationInfo.nativeLibraryDir; } } return null ; }
JNI常用方法 本质都是通过JNI的函数去和Java进行交互
类的代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class NDKDemo { public static String publicStaticStringField = "this is publicStaticStringField" ; public String publicStringField = "this is publicStringField" ; private static String privateStaticStringField = "this is privateStaticStringField" ; private String privateStringField = "this is privateStringField" ; private byte [] byteArray = new byte []{1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 }; public NDKDemo () { Log.d("test" , "this is ReflectDemo()" ); } public NDKDemo (String str) { Log.d("test" , "this is ReflectDemo(String str)" ); } public NDKDemo (String str, int i) { Log.d("test" , i + " " + str); Log.d("test" , "this is ReflectDemo(String str, int i)" ); } public static void publicStaticFunc () { Log.d("johnTest" , "this is publicStaticFunc" ); } public void publicFunc () { Log.d("test" , "this is publicFunc" ); } private static int [] privateStaticFunc(String[] str){ StringBuilder retval = new StringBuilder (); for (String i : str) { retval.append(i); } Log.d("test" , "this is privateStaticFunc: " + retval.toString()); return new int []{0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 }; } private String privateFunc (String str, int i) { Log.d("test" , i + " this is privateFunc: " + str); return "this is from java" ; } }
通过jni创建Java对象 大致都是找到类、找到方法、调用方法,和反射的逻辑差不多
NewObject
创建对象
1 2 3 4 5 jclass clazz = env->FindClass("com/test/ndkdemo/NDKDemo" ); jmethod methodID = env->GetMethodID(clazz, "<init>" , "()V" ); jobject ReflectDemoObj = env->NewObject(clazz, methodID); LOGD("ReflectDemoObj %p" , ReflectDemoObj);
AllocObject
创建对象
1 2 3 4 5 6 7 jclass clazz = env->FindClass("com/test/ndkdemo/NDKDemo" ); jmethod methodID2 = env->GetMethodID(clazz, "<init>" , "(Ljava/lang/String;I)V" ); jobject ReflectDemoObj2 = env->AllocObject(clazz); jstring jstr = env->NewStringUTF("thisisatest" ); env->CallNonvirtualVoidMethod(ReflectDemoObj2, clazz, methodID2, jstr, 100 );
如果不想填函数的返回值类型,或者jclass
、jmethod
这些分不清的话,直接全部填auto
类型就行
通过JNI访问Java属性 不需要管属性的修饰符,public
static
private
都不影响
有很多方法,比如GetBooleanField
、GetIntField
,取决于属性的类型
获取静态字段
1 2 3 4 5 6 jfieldID privateStaticStringFieldID = env->GetStaticFieldID(clazz, "privateStaticStringField" , "Ljava/lang/String;" ); jstring privateStaticString = static_cast<jstring>(env->GetStaticObjectField(clazz,privateStaticStringFieldID)); const char * privatecstr = env->GetStringUTFChars(privateStaticString, nullptr);LOGD("privateStaticString %s" , privatecstr);
获取对象字段
1 2 3 4 5 6 jfieldID publicStringFieldID = env->GetFieldID(clazz, "publicStringField" , "Ljava/lang/String;" ); jstring publicString = static_cast<jstring>(env->GetObjectField(ndkobj, publicStringFieldID)); const char * publiccstr = env->GetStringUTFChars(publicString, nullptr);LOGD("publicString %s" , publiccstr);
设置字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 auto NDKDemoClazz = env->FindClass("com/johnTest/ndkdemo/NDKDemo" );auto init_MethodID = env->GetMethodID(NDKDemoClazz, "<init>" , "(Ljava/lang/String;I)V" );jobject ndkobj = env->AllocObject(NDKDemoClazz); LOGD("ndkobj %p" , ndkobj); jstring johnTest = env->NewStringUTF("johnTest" ); env->CallNonvirtualVoidMethod(ndkobj, NDKDemoClazz, init_MethodID, johnTest, 100 ); LOGD("ndkobj %p" , ndkobj); jfieldID privateStaticStringFieldID = env->GetStaticFieldID(NDKDemoClazz, "privateStaticStringField" , "Ljava/lang/String;" ); jstring jstr1 = static_cast<jstring>(env->GetStaticObjectField(NDKDemoClazz, privateStaticStringFieldID)); LOGD("jstr1 %p" , jstr1); const char * cstr1 = env->GetStringUTFChars(jstr1, nullptr);LOGD("cstr1 %s" , cstr1); env->ReleaseStringUTFChars(jstr1, cstr1); jfieldID privateStringFieldID = env->GetFieldID(NDKDemoClazz, "privateStringField" , "Ljava/lang/String;" ); env->SetObjectField(ndkobj, privateStringFieldID, env->NewStringUTF("johnTest" )); jstring jstr2 = static_cast<jstring>(env->GetObjectField(ndkobj, privateStringFieldID)); const char * cstr2 = env->GetStringUTFChars(jstr2, nullptr);LOGD("cstr2 %s" , cstr2); env->ReleaseStringUTFChars(jstr2, cstr2);
通过JNI访问Java数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 auto NDKDemoClazz = env->FindClass("com/johnTest/ndkdemo/NDKDemo" );auto init_MethodID = env->GetMethodID(NDKDemoClazz, "<init>" , "(Ljava/lang/String;I)V" );jobject ndkobj = env->AllocObject(NDKDemoClazz); LOGD("ndkobj %p" , ndkobj); jstring johnTest = env->NewStringUTF("johnTest" ); env->CallNonvirtualVoidMethod(ndkobj, NDKDemoClazz, init_MethodID, johnTest, 100 ); LOGD("ndkobj %p" , ndkobj); jfieldID byteArrayID = env->GetFieldID(NDKDemoClazz, "byteArray" , "[B" ); jbyteArray jbyteArray = static_cast<_jbyteArray *>(env->GetObjectField(ndkobj, byteArrayID)); jsize jbyteArrayLength = env->GetArrayLength(jbyteArray); jbyte charArray[jbyteArrayLength]; for (int i = 0 ; i < jbyteArrayLength; i++){ charArray[i] = i * 10 ; } env->SetByteArrayRegion(jbyteArray, 0 , jbyteArrayLength, reinterpret_cast<const jbyte *>(&charArray)); char * cbyteArray = reinterpret_cast<char *>(env->GetByteArrayElements(jbyteArray, nullptr));for (int i = 0 ; i < jbyteArrayLength; i++){ LOGD("jbyteArray[%d]=%d" , i, cbyteArray[i]); } env->ReleaseByteArrayElements(jbyteArray, reinterpret_cast<jbyte *>(cbyteArray), 0 );
通过JNI访问Java方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 auto NDKDemoClazz = env->FindClass("com/johnTest/ndkdemo/NDKDemo" );auto init_MethodID = env->GetMethodID(NDKDemoClazz, "<init>" , "(Ljava/lang/String;I)V" );jobject ndkobj = env->AllocObject(NDKDemoClazz); LOGD("ndkobj %p" , ndkobj); jstring johnTest = env->NewStringUTF("johnTest" ); env->CallNonvirtualVoidMethod(ndkobj, NDKDemoClazz, init_MethodID, johnTest, 100 ); LOGD("ndkobj %p" , ndkobj); jmethodID publicStaticFuncID = env->GetStaticMethodID(NDKDemoClazz, "publicStaticFunc" , "()V" ); env->CallStaticVoidMethod(NDKDemoClazz, publicStaticFuncID); jmethodID privateFuncID = env->GetMethodID(NDKDemoClazz, "privateFunc" , "(Ljava/lang/String;I)Ljava/lang/String;" ); jvalue args[2 ]; args[0 ].l = env->NewStringUTF("johnTest" ); args[1 ].i = 100 ; jstring jresult = static_cast<jstring>(env->CallObjectMethodA(ndkobj, privateFuncID, args)); char * cresult = const_cast<char *>(env->GetStringUTFChars(jresult, nullptr));LOGD("cresult: %s" , cresult); env->ReleaseStringUTFChars(jresult, cresult); jclass _jstring = env->FindClass("java/lang/String" ); jobjectArray _jstringArray = env->NewObjectArray(3 , _jstring, nullptr); for (int i = 0 ; i < 3 ; i++){ env->SetObjectArrayElement(_jstringArray, i, env->NewStringUTF("johnTest;" )); } jmethodID privateStaticFuncID = env->GetStaticMethodID(NDKDemoClazz, "privateStaticFunc" , "([Ljava/lang/String;)[I" ); jintArray _jintArray = static_cast<jintArray>(env->CallStaticObjectMethod(NDKDemoClazz, privateStaticFuncID, _jstringArray)); int *cintArr = env->GetIntArrayElements(_jintArray, nullptr);LOGD("cintArr[0]=%d" , cintArr[0 ]); env->ReleaseIntArrayElements(_jintArray, cintArr, JNI_ABORT);
通过JNI访问Java的父类方法 CallNonvirtualxxxx
指调用父类的该函数
可以尝试在父类调用Activity
的onCreate()
方法,重点是调用super.onCreate(savedInstanceState);
这一句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 extern "C" JNIEXPORT void JNICALL Java_com_johnTest_ndkdemo_MainActivity_onCreate (JNIEnv *env, jobject thiz, jobject saved_instance_state) { jclass AppCompatActivityClazz = env->FindClass("androidx/appcompat/app/AppCompatActivity" ); jmethodID onCreateID = env->GetMethodID(AppCompatActivityClazz, "onCreate" , "(Landroid/os/Bundle;)V" ); env->CallNonvirtualVoidMethod(thiz,AppCompatActivityClazz, onCreateID, saved_instance_state); }
也就是说,其实所有的Java层代码都可以放到so层去实现
JNI中的内存管理 局部引用 不能等价成局部变量和全局变量
1 2 3 比如在全局定义了NDKDemoClazz,并且在onLoad加载函数里调用JNI函数给这个变量赋值了 但是在别的函数里无法使用这个变量 因为JNI函数返回的结果都是局部引用,函数执行完返回就回收了
大多数JNI函数,调用以后返回的结果都是局部引用
因此,env->NewLocalRef
基本不用,这个函数就是创建局部引用的
一个函数内的局部变量引用数量是有限的,早期Android里更明显
当函数体内需要大量使用局部变量的时候,最后及时删掉不用的局部引用
可以使用env->DeleteLocalRef(变量名)
来删除局部引用
局部引用相关的其他函数
1 2 3 env->EnsureLocalcapacity(num) 判断是否有足够的局部变量可以使用,足够则返回0 需要大量使用局部引用时,可以使用env->PushLocalFrame(num)和env->PopLocalFrame(nullptr)来批量管理局部引用
全局引用 全局引用是不会被回收的,除非程序结束或者so文件卸载了
在JNI开发中,需要跨函数使用变量时,直接定义全局变量是没用的
需要使用以下两个方法来创建和删除全局引用
1 2 3 4 5 env->NewGlobalRef(jclass 变量名) jclass tmpClass = env->FindClass(...); NDKDemoClazz = static_cast<jclass>(env->NewGlobalRef(tmpClass)); env->DeleteGlobalRef()
弱全局引用 与全局引用基本相同,区别是弱全局引用有可能被回收
1 2 env->NewWeakGlobalRef env->DeleteWeakGlobalRef
子线程中获取Java类 在子线程中,findClass
可以直接获取系统类,但是直接findClass
获取不到自写的类比如MainActivity
这种,因为子线程的类加载器默认为系统加载器,只能找到系统类,主线程的类加载器是应用加载器,可以找到自写类,加载器就是classLoader
,不同的classLoader
即使加载相同的类名也会被视为不同的类
方法一,想要在子线程中使用主线程的类,定义全局引用,并在主线程中获取类,使用全局引用来传递到子线程中
方法二,在主线程中获取正确的ClassLoader
,在子线程中去加载类
在Java中,可以先获取类字节码,然后使用getClassLoader来获取
在Java里是这样实现的
1 2 DemoClass.class.getClassLoader() new DemoClass().getClass().getClassLoader()
转换成C++
1 2 3 4 5 6 7 8 9 10 11 12 13 jclass MainActivityClazz = env->FindClass("com/johnTest/ndkdemo/MainActivity" ); jclass classClazz = env->FindClass("java/lang/Class" ); jmethodID getClassLoaderID = env->GetMethodID(classClazz, "getClassLoader" , "()Ljava/lang/ClassLoader;" ); jobject tempClassLoaderObj = env->CallObjectMethod(MainActivityClazz, getClassLoaderID); ClassLoaderObj = env->NewGlobalRef(tempClassLoaderObj); jclass ClassLoaderClazz = env->FindClass("java/lang/ClassLoader" ); jmethodID loadClassID = env->GetMethodID(ClassLoaderClazz, "loadClass" , "(Ljava/lang/String;)Ljava/lang/Class;" ); jclass MainActivityClazz = static_cast<jclass>(env->CallObjectMethod(ClassLoaderObj, loadClassID, env->NewStringUTF("com.johnTest.ndkdemo.MainActivity" ))); LOGD("myThread MainActivityClazz: %p" , MainActivityClazz);
init与initarray so在执行JNI_Onload
之前,还会先后 执行两个构造函数init
和initarray
,并且initarray可以存放多个函数,且这些函数按序执行
so加固、so中字符串加密等等,一般会把相关代码放到这里,也就是说当系统执行init
和initarray
的时候,so里的代码部分可能是加密的
init
的定义及使用
1 2 3 extern "C" void _init() { // 函数名必须为 _init ... }
initarray
的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 __attribute__ ((constructor)) void initArrayTest1 () {...}; __attribute__ ((constructor(200 ))) void initArrayTest2 () {...}; __attribute__ ((constructor(101 ))) void initArrayTest3 () {...}; __attribute__ ((constructor, visibility("hidden" )) void initArrayTest4() {...}; demo: __attribute__ ((constructor, visibility("hidden" ))) void initArrayTest3(){ LOGD("initArrayTest3" ); } __attribute__ ((constructor(101 ))) void initArrayTest1(){ LOGD("initArrayTest1" ); } __attribute__ ((constructor(300 ))) void initArrayTest2(){ LOGD("initArrayTest2" ); } extern "C" void _init(){ LOGD("_init" ); }
constructor
代表构造函数,必须是俩括号,后面的值,较小的先执行,最好从100之后开始用
如果constructor
后面没有值,那么按定义的顺序,从上往下执行(就是写代码的顺序)
如果既有写了值的,还有没写值的,没写值的最后执行,写了值的按数值的顺序执行
PS:init函数在编译的时候会被重命名为 .init_proc
,在IDA里要搜的话得搜这个
onCreate方法Native化 java代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); TextView tv = binding.sampleText; tv.setText(stringFromJNI()); String soPath = new Utils ().getPath(getApplicationContext()) + "/libjohnTestB.so" ; Log.d("johnTest" , stringFromJNI2(1 , new byte []{1 }, soPath)); testJniFunc(); }
C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 extern "C" JNIEXPORT void JNICALL Java_com_johnTest_ndkdemo_MainActivity_onCreate (JNIEnv *env, jobject thiz, jobject saved_instance_state) { jclass AppCompatActivityClazz = env->FindClass("androidx/appcompat/app/AppCompatActivity" ); jmethodID onCreateID = env->GetMethodID(AppCompatActivityClazz, "onCreate" , "(Landroid/os/Bundle;)V" ); env->CallNonvirtualVoidMethod(thiz,AppCompatActivityClazz, onCreateID, saved_instance_state); jclass ActivityClazz = env->FindClass("android/app/Activity" ); jmethodID getLayoutInflaterID = env->GetMethodID(ActivityClazz, "getLayoutInflater" , "()Landroid/view/LayoutInflater;" ); jobject LayoutInflater = env->CallObjectMethod(thiz, getLayoutInflaterID); jclass ActivityMainBindingClazz = env->FindClass("com/johnTest/ndkdemo/databinding/ActivityMainBinding" ); jmethodID inflateID = env->GetStaticMethodID(ActivityMainBindingClazz, "inflate" , "(Landroid/view/LayoutInflater;)Lcom/johnTest/ndkdemo/databinding/ActivityMainBinding;" ); LOGD("ActivityMainBindingClazz %p" , ActivityMainBindingClazz); LOGD("inflateID %p" , inflateID); jobject ActivityMainBindingObj = env->CallStaticObjectMethod(ActivityMainBindingClazz, inflateID, LayoutInflater); jmethodID getRootID = env->GetMethodID(ActivityMainBindingClazz, "getRoot" , "()Landroidx/constraintlayout/widget/ConstraintLayout;" ); jobject ConstraintLayout = env->CallObjectMethod(ActivityMainBindingObj, getRootID); jmethodID setContentViewID = env->GetMethodID(AppCompatActivityClazz, "setContentView" , "(Landroid/view/View;)V" ); env->CallVoidMethod(thiz, setContentViewID, ConstraintLayout); }
函数签名怎么写,比如 inflate(LayoutInflater inflater)
, 签名 (Landroid/view/LayoutInflater;)Lcom/johnTest/ndkdemo/databinding/ActivityMainBinding;
,()括号里写参数,L代表对象,;代表参数或者返回值结束