1、NDK介绍
1.1、app为什么把代码放到so中
a) C语言历史悠久,有很多现成的代码可用
b) C代码执行效率比Java高
c) Java代码很容易被反编译,而且反编译以后的逻辑很清晰
1.2、为什么要学习NDK开发
在安卓的so开发中,基本与C/C++开发一致,而与Java交互需要用到JNI
在本部分的NDK开发讲解中,主要就是介绍JNI相关内容,so中会接触的:系统库函数、jni调用、加密算法、魔改算法、系统调用、自定义算法
1.3、什么是JNI
JNI是Java Native Interface的缩写。从Java1.1开始,JNI标准成为Java平台的一部分,允许Java代码和其他语言写的代码进行交互
1.4、什么是NDK
NDK是一套工具,使我们能够在 Android 应用中使用 C 和 C++ 代码
交叉编译工具链
NDK的配置
NDK、CMake、LLDB的作用 https://developer.android.com/ndk/guides
1.5、ABI与指令集
不同的 Android 设备使用不同的 CPU,而不同的 CPU 支持不同的指令集。CPU 与指令集的每种组合都有专属的应用二进制接口 (ABI)。
https://developer.android.com/ndk/guides/abis
2、NDK与Java工程的区别
1、Java代码中多了加载so和声明所需使用的so中的函数代码
2、编写CMakeLists.txt和C文件
3、build.gradle中添加一些代码
3、第一个NDK工程
1、CMakeLists介绍
2、so的加载
static {
System.loadLibrary("javaandso");
}
3、native函数的声明
public native String stringFromJNI();
4、JNI函数的静态注册规则
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_javaandso_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
Java_包名_类_方法名
5、JNIEnv、jobject/jclass
jobject 在native函数的声明改为static时,使用jclass,因为静态方法可以通过类直接调用
6、NewstringUTF
Java的数据和so的数据不互通,如果so的数据最后要转到java层处理就需要NewstringUTF,因此NewstringUTF可以成为一个hook点。
7、在NDK开发中,一定要注意哪些是Java的类型,哪些是C/C++的类型,在适当的时候需要转换
hello.c_str()获取C字符串的类型,jstring是JAVA字符串类型
8、extern “C” JNIEXPORT jstring JNICALL
JNICALL是空的,JNIEXPORT代表把函数导出,给函数添加了默认的可见属性
指定只编译arm64的so
ndk { abiFilters 'arm64-v8a' }
指定编译后的so名字
修改地方如下
4、so中常用的Log输出
#include <jni.h>
#include <string>
#include <android/log.h>
#define TAG "whitebird"
#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__);
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_javaandso_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
LOGD("LOGD test%d",1);
LOGI("LOGI test%d,%d",1,2);
LOGE("LOGE test%d,%d,%d",1,2,3);
return env->NewStringUTF(hello.c_str());
}
5、NDK多线程
int pthread_create(pthread_t* __pthread_ptr, pthread_attr_t const* __attr, void* (*__start_routine)(void*), void*);
第一个是指向pthread的指针,也是线程id,第二个是线程属性,第三个是线程执行的函数,第四个是函数参数
#include <jni.h>
#include <string>
#include <android/log.h>
#define TAG "whitebird"
#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__);
void myThread(){
std::string test="this is a thread function";
LOGD(test.c_str());
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_javaandso_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
pthread_t pthread;
pthread_create(&pthread, nullptr, reinterpret_cast<void *(*)(void *)>(myThread), nullptr);
return env->NewStringUTF(hello.c_str());
}
默认的线程属性是joinable 随着主线程结束而结束
#include <jni.h>
#include <string>
#include <android/log.h>
#define TAG "whitebird"
#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__);
void myThread(){
std::string test="this is a thread function";
LOGD(test.c_str());
//子线程中使用它来退出线程
pthread_exit(0);
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_javaandso_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
pthread_t pthread;
pthread_create(&pthread, nullptr, reinterpret_cast<void *(*)(void *)>(myThread), nullptr);
//线程属性是dettach,可以分离执行
pthread_join(pthread, nullptr);
return env->NewStringUTF(hello.c_str());
}
6、JNI_OnLoad
在使用native方法前都会先加载该native方法的so文件,通常在一个类的静态代码块中进行加载,当然也可以在构造函数,或者调用前加载。jvm在加载so时都会先调用so中的JNI_OnLoad函数,如果你没有重写该方法,那么系统会给你自动生成一个。我们先来测试一下JNI_OnLoad的调用时机
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()函数,VM会默认该*.so档是使用最老的JNI 1.1版本。
}
注意事项
1、一个so中可以不定义JNI_OnLoad,一旦定义了JNI_OnLoad,在so被加载的时候会自动执行,必须返回JNI版本 JNI_VERSION_1_6
2、在so被成功卸载时,会回调另一个JNI方法:JNI_UnOnLoad。这两个方法声明如下:
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved);
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved);
其中第一个参数vm表示DVM虚拟机,该vm在应用进程中仅有一个,可以保存在native的静态变量中,供其他函数或其他线程使用。其返回值表示当前需要native library需要的版本。
7、JavaVM
7.1、JavaVM是什么
JavaVM是虚拟机在JNI中的表示,一个JVM中只有一个JavaVM实例,这个实例是线程共享的。通过JNIEnv可以获取一个Java虚拟机实例,其函数如下:
jint GetJavaVM(JNIEnv *env, JavaVM **vm);
vm用来存放获得的虚拟机指针的指针,成功返回0,失败返回其它值。
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
如果是C++,则用_JavaVM定义,如果是C,就用JNIInvokeInterface定义。
_JavaVM和JNIInvokeInterface分别如下:
struct JNIInvokeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
jint (*DestroyJavaVM)(JavaVM*);
jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);
jint (*DetachCurrentThread)(JavaVM*);
jint (*GetEnv)(JavaVM*, void**, jint);
jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
};
struct _JavaVM {
const struct JNIInvokeInterface* functions;
#if defined(__cplusplus)
jint DestroyJavaVM()
{ return functions->DestroyJavaVM(this); }
jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThread(this, p_env, thr_args); }
jint DetachCurrentThread()
{ return functions->DetachCurrentThread(this); }
jint GetEnv(void** env, jint version)
{ return functions->GetEnv(this, env, version); }
jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};
_JavaVM其实是对JNIInvokeInterface的封装,底层还是调用JNIInvokeInterface的函数。
JavaVM中的常用方法:GetEnv和AttachCurrentThread(在子线程中获取JNIEnv)
7.2、JavaVM的获取方式
JNI_OnLoad的第一个参数
JNI_OnUnload的第一个参数
env->GetJavaVM
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;
}
LOGD("JavaVM %p",vm);
JavaVM** vm1;
env->GetJavaVM(vm1);
LOGD("env->GetJavaVM %p",*vm1);
return JNI_VERSION_1_6;
}
8、JNIEnv
8.1、JNIEnv是什么
JNIEnv一般是是由虚拟机传入,而且与线程相关的变量,也就说线程A不能使用线程B的JNIEnv。而作为一个结构体,它里面定义了JNI系统操作函数。JNIEnv的定义如下:
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
#else
typedef const struct JNINativeInterface* JNIEnv;
#endif
JNIEnv在C语言环境和C++语言环境中的实现是不一样。在C中定义为JNINativeInterface,在C++中定义为_JNIEnv。
_JNIEnv结构体的定义如下:
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
#if defined(__cplusplus)
jint GetVersion()
{ return functions->GetVersion(this); }
jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
jsize bufLen)
{ return functions->DefineClass(this, name, loader, buf, bufLen); }
jclass FindClass(const char* name)
{ return functions->FindClass(this, name); }
...
...
#endif
}
在_JNIEnv中定义了一个functions变量,这个变量是指向JNINativeInterface的指针。所以如果我们在写native函数时,当接收到类型为JNIEnv*的变量env时,可以使用如下方式调用JNIEnv中的函数(准确说是通过函数指针来调用函数,因为JNIEnv的数据结构聚合了所有 JNI 函数的函数指针),我们可在C++中通过如下方式调用:
env->FindClass("java/lang/String")
而在C中可以通过如下方式调用:
(*env)->FindClass(env, "java/lang/String") // C中的写法
由于变量functions是定义在结构体_JNIEnv的第1个变量,所以我们通过*env就能获取到functions变量的值,然后通过JNINativeInterface中的函数指针来调用对应的函数。
JNINativeInterface结构体的定义如下
struct JNINativeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
void* reserved3;
jint (*GetVersion)(JNIEnv *);
jclass (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,
jsize);
jclass (*FindClass)(JNIEnv*, const char*);
...
}
8.2、JNIEnv的获取方式
1、函数静态/动态注册,传的第一个参数
2、vm->GetEnv
3、globalVM->AttachCurrentThread
用于子线程中获取Env
JNIEnv是每个线程单独拥有一个,对于JNI_OnLoad和Java_com_example_javaandso_MainActivity_stringFromJNI都在主线程中,所以他们的值相等,myThread是一个新开的线程,所以打印出来的JNIEnv值不一样。
9、JNI函数的注册
9.1、静态注册
必须遵循一定的命名规则,一般是Java_包名_类名_方法名
系统会通过dlopen加载对应的so,通过dlsym来获取指定名字的函数地址,然后调用静态注册的jni函数,必然在导出表里
9.2、动态注册
通过env->RegisterNatives注册函数,通常在JNI_OnLoad中注册
JNINativeMethod是一个结构体,第一个成员是Java层的函数名,第二个是签名,通常格式为(参数类型)返回值类型,第三个参数是Native层的函数指针。
jstring FunctionTest(JNIEnv* env,jobject,int a,jstring b, jobjectArray c){
return env->NewStringUTF("this is FunctionTest");
}
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = nullptr;
globalvm =vm;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
LOGD("GetEnv failed");
return -1;
}
jclass MainActivityClazz = env->FindClass("com/example/javaandso/MainActivity");
JNINativeMethod methods[] = {
{"stringFromJNI1", "(ILjava/lang/String;[Ljava/lang/Byte;)Ljava/lang/String;", (void *)FunctionTest},
};
env->RegisterNatives(MainActivityClazz, methods, sizeof(methods)/sizeof(JNINativeMethod));
return JNI_VERSION_1_6;
}
可以给同一个Java函数注册多个native函数,以最后一次为准
10、多个cpp文件编译成一个so
新建了一个cpp文件
如果想要把多个cpp文件编译成一个so,就需要修改CMakeLists.txt文件
然后我们在JNI_OnLoad中调用这个函数
11、编译多个so
1、编写多个cpp文件
2、修改CMakeLists.txt
3、Java静态代码块加载多个so
# 编译so的CMAKE的最低版本
cmake_minimum_required(VERSION 3.22.1)
# Declares and names the project.
project("javaandso")
# 设置so的名字,属性为共享库,so的源文件为native-lib.cpp
add_library( # Sets the name of the library.
whitebirdA
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp
)
add_library( # Sets the name of the library.
whitebirdB
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
test.cpp
)
#查找依赖库,并且设置依赖库的名字log-lib
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
#链接依赖库,名字就是上面定义的log-lib
target_link_libraries( # Specifies the target library.
whitebirdA
# Links the target library to the log library
# included in the NDK.
${log-lib})
target_link_libraries( # Specifies the target library.
whitebirdB
# Links the target library to the log library
# included in the NDK.
${log-lib})
12、so路径的动态获取
先来探讨一下apk中的so文件将来要放在安卓哪一个目录下
放在/data/app/包名/lib下
public String getPath(Context cxt){
//包管理器
PackageManager pm = cxt.getPackageManager();
//得到已经安装的APP的信息
List<PackageInfo> pkgList = pm.getInstalledPackages(0);
if (pkgList == null || pkgList.size() == 0) return null;
//遍历
for (PackageInfo pi : pkgList) {
//判断是否为/data/app/开头
if (pi.applicationInfo.nativeLibraryDir.startsWith("/data/app/")
&& pi.packageName.startsWith("com.example.javaandso")) {
Log.e("whitetird", pi.applicationInfo.nativeLibraryDir);
return pi.applicationInfo.nativeLibraryDir;
}
}
return null;
}
13、so之间相互调用
需要用到dlopen函数,需要导入dlfcn.h
两个参数,第一个是so的路径,第二个是下面定义的常量,表示解析符号,常用RTLD_NOW
void *soinfo = dlopen(nativePath, RTLD_NOW);//加载打开so
void (*funptr)() = nullptr;//定义函数指针
funptr = reinterpret_cast<void (*)()>(dlsym(soinfo,"TestMain"));
funptr();
另外一种方法就是通过link so实现
然后声明后就可以调用了
打印了两次this is TestMain for many cpp
14、通过JNI创建Java对象
有两种方法可以实现
NewObject创建对象
jclass clazz = env->FindClass("com/xiaojianbang/ndk/NDKDemo");
jmethodID methodID = env->GetMethodID(clazz, "<init>", "()V");
jobject ReflectDemoObj = env->NewObject(clazz, methodID);
LOGD("ReflectDemoObj %p", ReflectDemoObj);
AllocObject创建对象
jclass clazz = env->FindClass("com/xiaojianbang/ndk/NDKDemo");
jmethodID methodID2 = env->GetMethodID(clazz, "<init>", "(Ljava/lang/String;I)V");
jobject ReflectDemoObj2 = env->AllocObject(clazz);
jstring jstr = env->NewStringUTF("from jni str");
env->CallNonvirtualVoidMethod(ReflectDemoObj2, clazz, methodID2, jstr, 100);
15、通过JNI访问Java属性
a、获取静态字段
1、先调用获取静态字段ID的方法:GetStaticFieldID
jfieldID privateStaticStringField =
env->GetStaticFieldID(clazz, "privateStaticStringField", "Ljava/lang/String;");
2、根据字段属性选择对应的Get方法
// env->GetStaticBooleanField();
// env->GetStaticIntField();
// env->GetStaticShortField();
// env->GetStaticByteField();
// env->GetStaticCharField();
// env->GetStaticFloatField();
// env->GetStaticDoubleField();
// env->GetStaticLongField();
// env->GetStaticObjectField();
我们的privateStaticStringField是String类型,其实也就是Object,所以选择env->GetStaticObjectField()
3、获取jstring的静态字段privateStaticString
jstring privateStaticString =
static_cast<jstring>(env->GetStaticObjectField(clazz, privateStaticStringField));
这里把jobject强转jstring类型
4、由于我们在so层,所以要转化成C语言类型
const char* privatecstr =
env->GetStringUTFChars(privateStaticString, nullptr);
5、打印
LOGD("privateStaticString: %s", privatecstr);
6、释放
env->ReleaseStringUTFChars(privateStaticString, privatecstr);
b、获取对象字段
1、先获取FieldId
jfieldID publicStringField =
env->GetFieldID(clazz, "publicStringField", "Ljava/lang/String;");
2、获取jstring
jclass clazz = env->FindClass("com/example/javaandso/NDKDemo");
jmethodID methodID = env->GetMethodID(clazz, "<init>", "()V");
jobject ReflectDemoObj = env->NewObject(clazz, methodID);
jstring publicString =
static_cast<jstring>(env->GetObjectField(ReflectDemoObj, publicStringField));
由于我们是获取对象字段,所以一定要有个对象,因此GetObjectField的第一个参数是对象
3、转化成c语言字符串
const char* publiccstr =
env->GetStringUTFChars(publicString, nullptr);
4、打印输出
LOGD("publicStringField: %s", publiccstr);
5、对字符串进行释放
env->ReleaseStringUTFChars(publicString, publiccstr);
c、设置对象字段
env->SetObjectField(ReflectDemoObj, privateStringFieldID, env->NewStringUTF("whitebird"));
通过SetObjectField就行了,第一个参数是对象,第二个参数是字段ID,第三个参数是要修改的值
进行修改前后对比
jfieldID privateStringFieldID =
env->GetFieldID(clazz, "privateStringField", "Ljava/lang/String;");
jstring privateString =
static_cast<jstring>(env->GetObjectField(ReflectDemoObj, privateStringFieldID));
const char* privateCstr =
env->GetStringUTFChars(privateString, nullptr);
LOGD("privateStringField old: %s", privateCstr);
env->ReleaseStringUTFChars(privateString, privateCstr);
env->SetObjectField(ReflectDemoObj, privateStringFieldID, env->NewStringUTF("whitebird"));
privateString = static_cast<jstring>(env->GetObjectField(ReflectDemoObj, privateStringFieldID));
privateCstr = env->GetStringUTFChars(privateString, nullptr);
LOGD("privateStringField new: %s", privateCstr);
env->ReleaseStringUTFChars(privateString, privateCstr);
同时我们也能知道不管是public还是private我们都能获取
16、通过JNI访问和修改Java数组
//获取Java对象的class和byte数组的字段ID。
jfieldID byteArrayID = env->GetFieldID(clazz, "byteArray", "[B");
//通过GetObjectField获取byte数组对象,并且通过GetArrayLength获取byte数组的长度。
jbyteArray byteArray = static_cast<jbyteArray>(env->GetObjectField(ReflectDemoObj, byteArrayID));
int _byteArrayLength = env->GetArrayLength(byteArray);
//在C++中创建一个char类型的数组cByteArray,并且为其赋值。
char cByteArray[10];
for(int i = 0; i < 10; i++){
cByteArray[i] = static_cast<char>(100 - i);
}
//将cByteArray中的元素转换成jbyte类型的数组。
const jbyte *java_array = reinterpret_cast<const jbyte *>(cByteArray);
//使用SetByteArrayRegion函数将java数组元素设置为cByteArray中的元素。
env->SetByteArrayRegion(byteArray, 0, _byteArrayLength, java_array);
//再次获取Java对象的byte数组,并且获取byte数组的元素。
byteArray = static_cast<jbyteArray>(env->GetObjectField(ReflectDemoObj, byteArrayID));
_byteArrayLength = env->GetArrayLength(byteArray);
//使用GetByteArrayElements函数将byte数组的元素转换到C++中的char数组CBytes中。
char* CBytes = reinterpret_cast<char *>(env->GetByteArrayElements(byteArray, nullptr));
//遍历CBytes数组元素,输出数组内容。
for(int i = 0; i < _byteArrayLength; i++) {
LOGD("CArray: %d", CBytes[i]);
}
//释放CBytes数组元素,将其转移回byte数组中。
env->ReleaseByteArrayElements(byteArray, (jbyte*)CBytes, 0);
17、通过jni访问Java方法
1、调用静态函数
// env->CallBooleanMethod();
// env->CallVoidMethod();
// env->CallByteMethod();
// env->CallShortMethod();
// env->CallIntMethod();
// env->CallCharMethod();
// env->CallDoubleMethod();
// env->CallLongMethod();
// env->CallFloatMethod();
// env->CallObjectMethod();
jmethodID publicStaticFuncID =
env->GetStaticMethodID(clazz, "publicStaticFunc", "()V");
env->CallStaticVoidMethod(clazz, publicStaticFuncID);
静态方法很简单,只要调用CallStaticVoidMethod就行了
2、调用对象函数
jmethodID privateFuncID =
env->GetMethodID(clazz,"privateFunc","(Ljava/lang/String;I)Ljava/lang/String;");
jstring str1 = env->NewStringUTF("this is from JNI");
jstring retval_jstring = static_cast<jstring>(env->CallObjectMethod(ReflectDemoObj, privateFuncID, str1, 1000));
const char* retval_cstr = env->GetStringUTFChars(retval_jstring, nullptr);
LOGD("privateStaticString: %s", retval_cstr);
env->ReleaseStringUTFChars(retval_jstring, retval_cstr);
通过CallObjectMethod调用对象函数,需要先创建一个对象
18、CallVoidMethod、A、V版本区别
可以看到CallVoidMethod底层调用的是CallVoidMethodV,他们两个的区别在于CallVoidMethod会帮我们封装参数,而CallVoidMethodV需要我们自己封装参数,可以看成它只能有一个参数。而对于CallVoidMethodA,它的参数是jvalue*
jvalue是一个联合体
jmethodID privateFuncID =
env->GetMethodID(clazz,"privateFunc","(Ljava/lang/String;I)Ljava/lang/String;");
jstring str2 = env->NewStringUTF("this is from JNI2");
jvalue args[2];
args[0].l = str2;
args[1].i = 1000;
jstring retval = static_cast<jstring>(env->CallObjectMethodA(ReflectDemoObj, privateFuncID, args));
const char* cpp_retval = env->GetStringUTFChars(retval, nullptr);
LOGD("cpp_retval: %s", cpp_retval);
env->ReleaseStringUTFChars(retval, cpp_retval);
通过jvalue里面放参数,然后进行调用
19、通过jni访问Java父类方法
这里的onCreate就是调用父类方法,所以我们现在尝试把onCreate Native化
直接快捷操作生成JNI函数
我们需要用到这个JNI函数帮我们调用父类方法
extern "C"
JNIEXPORT void JNICALL
Java_com_example_javaandso_MainActivity_onCreate(JNIEnv *env, jobject thiz,
jobject saved_instance_state) {
// TODO: implement onCreate()
jclass ComponentActivityzz=env->FindClass("androidx/activity/ComponentActivity");
jmethodID onCreateMethodId= env->GetMethodID(ComponentActivityzz,"onCreate", "(Landroid/os/Bundle;)V");
env->CallNonvirtualVoidMethod(thiz,ComponentActivityzz,onCreateMethodId,saved_instance_state);
}
可以正常运行
20、内存管理
1、局部引用
1、大多数的jni函数,调用以后返回的结果都是局部引用,因此,env->NewLocalRef 基本不用
2、一个函数内的局部引用数量是有限制的,在早期的安卓系统中,体现的更为明显
3、当函数体内需要大量使用局部引用时,比如大循环中,最好及时删除不用的局部引用,可以使用 env->DeleteLocalRef 来删除局部引用
4、局部引用和局部变量不同
2、局部引用相关的其他函数
env->EnsureLocalCapacity(num) 判断是否有足够的局部引用可以使用,足够则返回0,需大量使用局部引用时,手动删除太过麻烦,可使用以下两个函数来批量管理局部引用
env->PushLocalFrame(num)
env->PopLocalFrame(nullptr)
env->PushLocalFrame(100);
if(env->EnsureLocalCapacity(100) == 0) {
for(int i = 0; i < 3; i++){
jstring tempString = env->NewStringUTF("whitebird");
env->SetObjectArrayElement(_jstringArray, i, tempString);
//env->DeleteLocalRef(tempString);
sleep(1);
LOGD("env->EnsureLocalCapacity");
}
}
env->PopLocalFrame(nullptr);
3、全局引用
在jni开发中,需要跨函数使用变量时,直接定义全局变量是没用的,需要使用以下两个方法,来创建和删除全局引用
env->NewGlobalRef
jobject tempClassLoaderObj = env->CallObjectMethod(MainActivityClazz, getClassLoaderID);
ClassLoaderObj = env->NewGlobalRef(tempClassLoaderObj);
env->DeleteGlobalRef
4、弱全局引用
与全局引用基本相同,区别是弱全局引用有可能会被回收
env->NewWeakGlobalRef
env->DeleteWeakGlobalRef
21、子线程中获取Java类
- 在子线程中,findClass可以直接获取系统类
jclass ClassLoaderClazz = env->FindClass("java/lang/ClassLoader");
LOGD("myThread ClassLoaderClazz: %p", ClassLoaderClazz);
jclass StringClasszz=env->FindClass("java/lang/String");
LOGD("myThread StringClasszz: %p", StringClasszz);
可以看到系统类是有值的,但是自己的类就没有值
- 在主线程中获取类,使用全局引用来传递到子线程中
jclass MainActivityClazz = env->FindClass("com/example/javaandso/MainActivity");
globalClass= static_cast<jclass>(env->NewGlobalRef(MainActivityClazz));
子线程中调用
LOGD("myThread globalClass: %p", globalClass);
3、在主线程中获取正确的ClassLoader,在子线程中去加载类
在Java中,可以先获取类字节码,然后使用getClassLoader()来获取ClassLoader
Demo.class.getClassLoader()
new Demo().getClass().getClassLoader()
Class.forName(...).getClassLoader()
在native层进行转换,获取ClassLoader
//MainActivity.class.getClassLoader
jclass MainActivityClazz = env->FindClass("com/example/javaandso/MainActivity");
jclass classClasszz=env->FindClass("java/lang/Class");
jmethodID getClassLoaderMethoid=env->GetMethodID(classClasszz,"getClassLoader", "()Ljava/lang/ClassLoader;");
jobject tempClassLoader = env->CallObjectMethod(MainActivityClazz,getClassLoaderMethoid);
globalClassLoader=env->NewGlobalRef(tempClassLoader);
在jni的子线程中loadClass
//jclass MainActivityClasszz=FindClass("com/example/javaandso/MainActivity")
jclass LoadClassClazz=env->FindClass("java/lang/ClassLoader");
jmethodID LoadClassmMethoid=env->GetMethodID(LoadClassClazz,"loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
jclass MainActivityClasszz= static_cast<jclass>(env->CallObjectMethod(globalClassLoader,
LoadClassmMethoid,
env->NewStringUTF(
"com.example.javaandso.MainActivity")));
我们要得到的MainActivityClasszz其实在主线程已经获取过一次了,所以第三种方法其实有点麻烦,不如第二种使用全局引用
22、init与initarray
1、so在执行JNI_Onload之前,还会执行两个构造函数init、initarray
2、so加固、so中字符串加密等等,一般会把相关代码放到这里,所以JNI_Onload的时候一般so都是已经被解密了
3、init的使用
extern "C" void _init(){ //函数名必须为_init
LOGD("_init ");
}
4、initarray的使用
__attribute__ ((constructor)) void initArrayTest3(){
LOGD("initArrayTest3");
}
__attribute__ ((constructor)) void initArrayTest1(){
LOGD("initArrayTest1 ");
}
__attribute__ ((constructor)) void initArrayTest2(){
LOGD("initArrayTest2 ");
}
如果不加任何修饰,会安装我们代码写的顺序执行
constructor后面的值,较小的先执行,最好从100以后开始用
__attribute__ ((constructor(101))) void initArrayTest3(){
LOGD("initArrayTest3");
}
__attribute__ ((constructor(303))) void initArrayTest1(){
LOGD("initArrayTest1 ");
}
__attribute__ ((constructor(202))) void initArrayTest2(){
LOGD("initArrayTest2 ");
}
__attribute__ ((constructor(101))) void initArrayTest3(){
LOGD("initArrayTest3");
}
__attribute__ ((constructor)) void initArrayTest5(){
LOGD("initArrayTest5");
}
__attribute__ ((constructor(303))) void initArrayTest1(){
LOGD("initArrayTest1 ");
}
__attribute__ ((constructor)) void initArrayTest2(){
LOGD("initArrayTest2 ");
}
__attribute__ ((constructor(202))) void initArrayTest4(){
LOGD("initArrayTest4");
}
对于这种有值和没值都有的,先执行有值的顺序,然后再按没值的顺序
__attribute__ ((constructor, visibility("hidden"))) void initArrayTest6(){
LOGD("initArrayTest6");
}
反编译so时会抹去initArrayTest6符号
反编译so
_init在ida反编译后为.init_proc
通过段表,可以看到init_array段,跟过去看看
存放顺序已经排好序了,而且initArrayTest6符号已经没了,变成了sub_1F824
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 767778848@qq.com