jni基础

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);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值