JNI相关
静态注册
// java代码需要和C++代码相符通讯就需要通过JNI来进行注册
//java: public native String stringFromJNI(); //代表该函数的实现在so
//so: Java_com_first_firstndkdemo_MainActivity_stringFromJNI(...)
//静态注册时,so中的方法名编写规则:Java_+java中该函数所在的 包名_+类名_+方法名_
JNI_onLoad
//1.so中各种函数的执行时机:
// init、init_array、JNI_OnLoad
//2.JNI_Onload的定义:
JNIEXPORT jint JNI_OnLoad(JavaVM *vm,void *reserved){ //JavaVM是在jni.h中定义好的一个结构体
JNIEnv *env = nullptr;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK){ //GetEnv:获取jni env(得到jni的环境)
LOGD("GetEnv failed");
return -1;
}
return JNI_VERSION_1_6;
}
//3.注意事项
//一个so中可以不定义JNI_OnLoad
//一旦定义了JNI_OnLoad,在so首次被加载的时候就会被执行
//必须返回JNI版本 JNI_VERSION_1_6
Java VM
//在jni.h中用C++定义的一个结构体,里面常用的几个方法,GetEnv(在主线程中获取jni的环境等)。。。。
//JavaVM的获取方式:
//JNI_OnLoad的第一个参数
//JNI_OnUnLoad的第一个参数
//env->GetJavaVM
//对比各种方式获取的JavaVM指针是否一致
//作用:获取JNI_Env,然后实现so与Java进行通信
JNIEnv
//1.它本身是一个在jni.h中定义的一个很大的结构体
//2.通过它里面的函数让C++代码与Java代码进行交互
so函数注册
'''
一般不是静态注册的函数不会出现在导出表里面
1.jin函数的静态注册
必须遵循一定的命名规则,一般是Java_包名_类名_方法名
系统会通过dlopen加载对应的so,通过dlsym来获取指定的函数地址,然后调用静态注册jni函数,必然在导出表里
2.jni函数的动态注册
通过env->RegisterNatives注册函数,通常在JNI_OnLoad中注册
JNINativeMethod
函数签名
可以给同一个Java函数注册多个native函数,以最后一次为准
//翻译java层的代码,将这个函数作为注册的第三个函数
jstring encodeFromC(JNIEnv* env,jobject obj,jint a,jbyteArray b,jstring c){
return env->NewStringUTF("encodeFromC:Hello from C++");
//NewStringUTF:将C语言的char*转为jstring
}
//注册操作是在 JNI_OnLoad中操作的,所以在加载so的时候就会进行注册操作
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;
}
//=======================动态注册=============================
// 动态注册是在加载so的时候就注册的,静态注册是在调用他的时候在注册的
//首先去寻找类,因为是根据类来注册的(就是需要注册的函数所在的类)
jclass MainActivityClass = env->FindClass("com/first/firstndk/MainActivity");
//因为通常是注册多个函数,所以这个地方定义成一个数组
//JNINativeMethod的原型就是一个结构体
// typedef struct {
// const char* name;
// const char* signature;
// void* fnPtr;
// } JNINativeMethod;
JNINativeMethod methods[] = {
//public native String encode(int i,String str,byte[] byt);
//{"需要注册的函数名字","函数的签名(传入的参数和返回的类型)",(void *)encodeFromC}
{"stringFromJNI2", "(I[BLjava/lang/String;)Ljava/lang/String;",(void *)encodeFromC}
//(I[B)Ljava/lang/String;): I:int [B:字节数组,L;:对象,Ljava/lang/String;:代表java/lang/String对象
//后面的哪个 Ljava/lang/String; 代表返回值
//(void *)encodeFromC:就是将java成的那个函数翻译为C代码
};
//jint RegisterNatives(jclass clazz(类), const JNINativeMethod* methods(地址),jint nMethods(注册数量))
env->RegisterNatives(MainActivityClass,methods,sizeof(methods)/sizeof(JNINativeMethod));
JavaVM *vm2;
env->GetJavaVM(&vm2);
//因为在同一个线程中,所以这两个env打印出来的值肯定是一样的
LOGD("JNI_OnLoad JavaVM1: %p",vm);
LOGD("JNI_OnLoad JavaVM2: %p",vm2);
LOGD("JNI_OnLoad GetEnv: %p",env);
return JNI_VERSION_1_6;
}
//当函数为动态注册时,这是不会出现在导出表里面,因为我们在动态注册的时候是在JNI_OnLoad中注册的,这时候就在导出表中直接搜jni_onload,然后进入
//arm中:DCQ表示8个字节,就是当传入的参数是指针的时候用这个模式(字母d切换模式)
//可以给同一个java函数注册多个native函数,以最后一次为准
so中常见的Log输出
//原生
#include <android/log.h> //安卓中的log包
//这里的数字3 是代编选择什么样的形式输出(info,debugger,error.....),在源码里面这个第一个参数就是一个枚举类型,
//枚举中第一个参数是0,依次往后推,故这里的3就是选的第四个参数
//参数:sheation是标签名
//参数:"cstr: %s",hello.c_str()是要输出的内容
__android_log_print(3,"sheation","cstr: %s",hello.c_str());
//封装后
#define TAG "sheation"
#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__);
LOGD("cstr: %s",hello.c_str());
多个cpp编译成一个so
//1.步骤
// 编写多个cpp文件
//当创建一个cpp文件的时候,这时候想要在其他文件是使用他的话,需要在当前cpp文件中加上一个 extern 函数名();
// 修改CMakeLists.txt 文件(需要该两个)
add_library( # Sets the name of the library.
native-lib # 这个名字就是编译后的so文件的名字
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
file_1.cpp file_2.cpp ...)
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )
//如果要编译成多个so,那就把这个在复制一个 改一下so的名字 和下面的需要编译的cpp文件的名字
add_library( # Sets the name of the library.
native-lib_2 # 这个名字就是编译后的so文件的名字
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
file_3.cpp file_4.cpp ...)
target_link_libraries( # Specifies the target library.
native-lib_2
# Links the target library to the log library
# included in the NDK.
${log-lib} )
// Java静态代码块加载多个so
//如果需要使用该so就必须在java层加载该函数
static {
System.loadLibrary("native-lib");
System.loadLibrary("native-lib1");
}
so路径的动态获取
//由于32位和64位存放的路径是不一样的,为了更加通用,可以用代码动态获取so的路径
//当手机安装好app后,存放路径在data/app目录下的
public class Utils {
public String getPath(Context cxt){
// 通过上下文context获取包管理器
PackageManager pm = cxt.getPackageManager();
//通过包管理器得到已经安装的所有app
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.first.firstndk")){
return pi.applicationInfo.nativeLibraryDir;
}
}
return null;
}
}
so之间的相互调用
//1.使用dlopen,dlsym,dlclose获取函数地址,然后调用,需要导入dlfcn.h(安卓7.0,7.1用不了 dlopen)
jstring encodeFromC(JNIEnv* env,jobject obj,jint a,jbyteArray b,jstring c){
//将jstring转换为C中的char*
const char *soPath = env->GetStringUTFChars(c, nullptr);
//获取函数地址
void *soinfo = dlopen(soPath,RTLD_NOW); //RTLD_NOW:加载完后立马初始化
//定义一个函数指针
void (*def)() = nullptr;
//dlsym(soinfo,"_Z7fromSoBPc"):soinfo:so文件的指针,"_Z7fromSoBPc"对应的cpp函数名(这里的函数名是符号修饰过后的名字,也即是该函数在汇编里面的样子)
//返回该函数的地址,这里需要将其强转成函数指针后才能用
//函数指针定义:void (*test)(); void (*)() 代表函数指针的类型,test代表函数指针的变量的名字
def = reinterpret_cast<void (*)()>(dlsym(soinfo,"_Z4testv"));
//通过函数指针调用函数
def();
return env->NewStringUTF("encodeFromC:Hello from C++");
//NewStringUTF:将C语言的char*转为jstring
}
通过jni在so中创建Java对象
// 1.通过NewObject创建对象
jclass clazz = env->FindClass("com/sheation/ndk/NDKDemo"); //寻找类
jmethodID methodID = env->GetMethodID(clazz "<init>","()V"); //寻找方法
jobject ReflecDemoObj = env->NewObject(clazz,methodID);//实例化对象
LOGD("ReflectDemoObj %p",ReflectDemoObj);
//2.通过ALLocObject创建对象
jclass clazz = env-FindClass("com/sheation/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->CallNovirtualVoidMethod(ReflectDemoObj2,clazz,methodID2,jstr,100);//初始化对象
通过jni访问Java属性
//1.获取静态字段
//GetStaticFieldID这个方法是根据查找方法的类型而定的
jfieldID privateStaticStringFieldID = env->GetStaticFieldID(NDKDemoClazz,"privateStaticStringField","Ljava/lang/String;");
//获取属性结果,需要转换一下,因为GetStaticObjectField返回的jobject,而我们需要的是jstring
jstring jstr = static_cast<jstring>(env->GetStaticObjectField(NDKDemoClazz,
privateStaticStringFieldID));
//如果需要在当前文件中打印的话还需要将jstring转为cstring
const char* cstr = env->GetStringUTFChars(jstr, nullptr);
LOGD("cstr %s",cstr);
//获取对象字段
jfieldID privateStringFieldID = env->GetFieldID(NDKDemoClazz,"privateStringField","Ljava/lang/String;");
//这里传的是对象的名字,不是类
jstring jstr1 = static_cast<jstring>(env->GetObjectField(ndkobj,privateStringFieldID));
const char* cstr1 = env->GetStringUTFChars(jstr1, nullptr);
LOGD("cstr1 %s",cstr1);
env->ReleaseStringChars(jstr1, reinterpret_cast<const jchar *>(cstr1));//操作完释放一下
//3。设置值
//先获取fieldID
jfieldID privateStringFieldID1 = env->GetFieldID(NDKDemoClazz,"privateStringField","Ljava/lang/String;");
//然后设置值
env->SetObjectField(ndkobj,privateStringFieldID1,env->NewStringUTF("sheation"));
//接着获取值
jstring setJstr = static_cast<jstring>(env->GetObjectField(ndkobj, privateStringFieldID1));
//转换类型
const char* setCstr = env->GetStringUTFChars(setJstr, nullptr);
LOGD("setCstr %s",setCstr);
通过jni访问java数组
//访问数组
//获取数组字段ID
jfieldID byteArrayID = env->GetFieldID(NDKDemoClazz,"byteArray","[B");
//获取数组
jbyteArray byteArray = static_cast<jbyteArray>(env->GetObjectField(ndkobj, byteArrayID));
//获取数组长度
jsize _byteArrayLength = env->GetArrayLength(byteArray);
LOGD("byteArrayLength",_byteArrayLength);
//修改数组字段
jbyte newArray[_byteArrayLength];
for (int i = 0; i < _byteArrayLength; ++i) {
newArray[i] = i*100;
}
env->SetByteArrayRegion(byteArray, 0, _byteArrayLength,reinterpret_cast<const jbyte *>(&newArray));
//获取数组元素
char* cbyteArray = reinterpret_cast<char *>(env->GetByteArrayElements(byteArray, nullptr));
//打印出数组元素
for (int i = 0; i < _byteArrayLength; ++i) {
LOGD("byteArray[%d]=%d",i,cbyteArray[i]);
}
//释放数组
env->ReleaseByteArrayElements(byteArray, reinterpret_cast<jbyte *>(cbyteArray), 0);
通过jni访问java方法
//调用静态函数
//获取函数ID
jmethodID publicStaticFuncID = env->GetStaticMethodID(NDKDemoClazz,"publicStaticFunc","()V");
//调用函数
env->CallStaticVoidMethod(NDKDemoClazz,publicStaticFuncID);
//调用对象函数
jmethodID publicFuncID = env->GetMethodID(NDKDemoClazz,"publicFunc","()V");
env->CallVoidMethod(ndkobj,publicFuncID);