JNI基本使用

JNI数据类型
基本数据

Java基本数据类型域JNI数据类型的映射关系:

Java类型->JNI类型->C类型

JNI的基本数据类型(左边是Java,右边是JNI):

boolean		jboolean
byte		jbyte
char		jchar
short		jshort
int			jint
long		jlong
float		jfloat
double		jdouble
void		void
引用类型(对象)
String		jstring
Object		jobject

数组,基本数据类型的数组
byte[]		jByteArray
对象数组
object[](String[])	jobjectArray
Native函数参数说明

每个natvie函数,都至少有两个参数(JNIEnv*,jclass或者jobject)

  1. 当native方法为静态方法时:
    jclass代表native方法所属类的class对象。
  2. 当native方法为非静态方法时:
    jobject代码natvie方法所属的对象。
关于属性与方法的签名

属性的签名
属性的签名其实就是属性的类型的简称,对应关系如下:

数据类型签名
booleanZ
byteB
charC
shortS
intI
longL
floatF
doubleD
voidV
objectL开头,然后以/分隔包的完整类型,后面再加;如果说String签名就是Ljava/lang/String;
Array以 [ 开头在加上数组元素类型的签名;比如 int[ ] 签名就是 [ I ,在比如int[ ][ ]的签名就是[ [ I ,object数组前面就是 [Ljava/lang/Object;

类的签名格式是: L完整包名;

获取指定类的所有属性、方法的签名:

javap -s -p 完整类名

我们通过cd命令:

F:\Android\NDKLearn\app\build\intermediates\classes\debug>javap -s -p com.example.asus1.ndk
learn.MainActivity
Compiled from "MainActivity.java"
public class com.example.asus1.ndklearn.MainActivity extends android.support.v7.app.AppComp
atActivity {
  public java.lang.String str;
    descriptor: Ljava/lang/String;
  public com.example.asus1.ndklearn.MainActivity();
    descriptor: ()V

  protected void onCreate(android.os.Bundle);
    descriptor: (Landroid/os/Bundle;)V

  public native java.lang.String stringFromJNI();
    descriptor: ()Ljava/lang/String;

  public native void accessFiled();
    descriptor: ()V

  static {};
    descriptor: ()V
}

其中descriptor就是我们性需要的签名,注意签名中末尾的分号不能省略。

方法前面的规律就是,括号不可以省略:

(参数类型签名)返回值类型签名
C/C++访问Java的属性、方法

有以下几种情况:

  1. 访问Java类的非静态属性。
  2. 访问Java类的静态属性。
  3. 访问Java类的非静态方法
  4. 访问Java类的静态方法
  5. 间接方法Java类的父类方法
  6. 访问Java类的构造方法。

访问Java的非静态属性
C代码:

extern "C"
JNIEXPORT void JNICALL
Java_com_example_asus1_ndklearn_MainActivity_accessFiled(JNIEnv *env, jobject instance) {

    // TODO
    //通过对象拿到Class
    jclass  clz = env->GetObjectClass(instance);
    //拿到对应属性的ID
    jfieldID fid = env->GetFieldID(clz,"str","Ljava/lang/String;");
    //通过属性ID拿到属性的值
    jstring  jstr = (jstring)(env->GetObjectField(instance,fid));

    //通过Java字符串拿到c字符串,第二个参数是一个出参,用来告诉我们GetStringUTFChars内部是否复制了一份字符串
    //如果没有复制,那么出参为isCopy,这时候就不能修改字符串的值了,因为Java中常量池中的字符串是不允许修改的(但是jstr可以指向另外一个字符串)
    const char * cstr = env->GetStringUTFChars(jstr,NULL);
    //在c层修改这个属性的值
    char  res[20] = "I LOVE YOU,";
    strcat(res,cstr);

    //重新生成Jasva的字符串,并且设置给对应的属性
    jstring  jstr_new = env->NewStringUTF(res);
    env->SetObjectField(instance,fid,jstr_new);

    //最后释放资源,通知垃圾回收器来回收
    env->ReleaseStringUTFChars(jstr,cstr);


}

在Java中,直接调用:

TextView tv = (TextView) findViewById(R.id.sample_text);
accessFiled();
tv.setText(str);

访问Java的静态属性

extern "C"
JNIEXPORT void JNICALL
Java_com_example_asus1_ndklearn_MainActivity_accessStaticFiled(JNIEnv *env, jobject instance) {

    // TODO
    jclass clz = env->GetObjectClass(instance);
    jfieldID  fid = env->GetStaticFieldID(clz,"NUM","I");
    jint  jInt = env->GetStaticIntField(clz,fid);
    jInt++;
    env->SetStaticIntField(clz,fid,jInt);

}

访问Java的非静态方法

extern "C"
JNIEXPORT void JNICALL
Java_com_example_asus1_ndklearn_MainActivity_accessMethd(JNIEnv *env, jobject instance) {

    // TODO
    jclass  clz = env->GetObjectClass(instance);
    jmethodID mid = env->GetMethodID(clz,"genRandomInt","(I)I");

    //调用该方法,最后一个是可变参数,就是调用该方法所传入的参数
    jint  jInt = env->CallIntMethod(instance,mid,100);
    //printf("output from C: %d",jInt);
   LOGD("output from C : %d",jInt);

}

访问Java的静态方法

extern "C"
JNIEXPORT void JNICALL
Java_com_example_asus1_ndklearn_MainActivity_accessStaticMathod(JNIEnv *env, jobject instance) {

    // TODO
    jclass  clz = env->GetObjectClass(instance);

    jmethodID mid = env->GetStaticMethodID(clz,"getUUID","()Ljava/lang/String;");

    //调用java的静态方法,拿到返回值
    jstring  jstr = (jstring)(env->CallStaticObjectMethod(clz,mid,NULL));

    //把拿到的Java字符串转换为C的字符串
    const  char * cstr = env->GetStringUTFChars(jstr,NULL);

    //后续操作,产生以UUID为文件名的文件
    char fileName[100];
    sprintf(fileName,"G:\\%s.txt",cstr);
    FILE * f = fopen(fileName,"w");
    fputs(cstr,f);
    fclose(f);

    LOGD("output from C : File had saved", jstr);
}

间接访问Java类的父类方法

父类:

public class Human {

    protected void speek(){
        System.out.println("Human Speek");
    }
}

子类:

public class Man extends Human {

    @Override
    protected void speek() {
        //super.speek();
        System.out.println("Man Speek");
    }
}

在MainActivity中

Human man = new Man();
    public native void accessNonVirtualMathod();

如果是直接使用main.speek()的话,访问的是子类Man的方法
但是通过底层C的方式可以间接访问到父类Human的方法,跳过子类的实现,甚至我们可以直接哪个父类(如果父类有多个的话),这是Java做不到的。

C代码:

extern "C"
JNIEXPORT void JNICALL
Java_com_example_asus1_ndklearn_MainActivity_accessNonVirtualMathod(JNIEnv *env, jobject instance) {

    // 先拿到属性man
    jclass  clz = env->GetObjectClass(instance);
    jfieldID  fid = env->GetFieldID(clz,"man","Lcom/example/asus1/ndklearn/Human;");
    jobject  man = env->GetObjectField(instance,fid);

    //拿到父类的类,以及speek的方法的id
    jclass  clz_human = env->FindClass("com/example/asus1/ndklearn/Human");
    jmethodID  mid = env->GetMethodID(clz_human,"speek","()V");

    //调用自己的speek实现
    env->CallVoidMethod(man,mid);

    //调用父类的speek实现
    env->CallNonvirtualVoidMethod(man,clz_human,mid);

}
  1. 当有这个类的对象的时候,使用env->GetObjectClass(instance)相当于Java中的test.getClass()
  2. 当没有这个类的对象的时候,env->FindClass()相当于Java中的Class.forName()

这里直接使用CallVoidMethod,虽然传进去的是父类的Method ID,但是访问的是子类的实现。

最后,通过CallNonVirtualVoidMethod,访问不覆盖的父类方法(C++使用virtual关键字来覆盖父类的实现)。

访问Java类的构造方法

Java代码:

 //调用Date的构造函数
    public native long accessConstructor();

C代码:

extern "C"
JNIEXPORT jlong JNICALL
Java_com_example_asus1_ndklearn_MainActivity_accessConstructor(JNIEnv *env, jobject instance) {

    jclass  clz_date = env->FindClass("java/util/Date");
    //构造方法的函数名格式是:<init>
    //不能写类名,因为构造方法函数名都一样区分不了,只能通过参数列表<签名>区分
    jmethodID  mid_Date = env->GetMethodID(clz_date,"<init>","()V");

    //调用构造函数
    jobject  date = env->NewObject(clz_date,mid_Date);

    //注意签名
    jmethodID  mid_getTime = env->GetMethodID(clz_date,"getTime","()J");

    //调用getTime方法
    jlong  jtime = env->CallLongMethod(date,mid_getTime);

    return jtime;

}
总结

属性,方法的访问和使用和Java的反射API类似。

数组的处理(主要是同步问题)

Java方法中,通过调用accessField,利用C修改静态属性

//数组处理
    public native void sortArray(int array[]);

C代码:

int compare(const void * a, const void * b)
{
    return (*(int *)a)-(*(int *)b);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_asus1_ndklearn_MainActivity_sortArray(JNIEnv *env, jobject instance,
                                                       jintArray array_) {
    //创建Java数组
    //env->NewIntArray(len);
	
	//通过Jva数组,拿到C的数组指针
    jint * c_arr = env->GetIntArrayElements(array_,NULL);
    jsize len = env->GetArrayLength(array_);

    //排序
    qsort(c_arr,len, sizeof(jint),compare);

    //操作完后需要同步C的数组到Java数组中
    env->ReleaseIntArrayElements(array_,c_arr,0);
}

注意

  1. 通过GetIntArrayElements拿到C类型的数组的指针,然后才能进行C数组的处理
  2. C拿到Java的数组进行操作或者修改,需要调用ReleaseIntArrayElements进行更新,这时候Java的数组也会同步更新过了

这个方法的最后一个参数是模式:
3. 0:Java数组进行更新,并且释放C/C++数组
4. JNI_ABORT:Java数值不进行更新,但是释放C/C++数组
5. JNI_COMMIT:Java数组进行更新,不释放C/C++数组(函数执行完毕后,还是会被释放)。

JNI引用

JNI引用概念:引用变量
引用类型:局部引用和全局引用(全局引用里面包含全局弱引用)。
作用:在JNI中告知虚拟机何时回收一个JNI变量。

局部引用

局部引用,通过DeleteLocalRef手动释放对象。

典型使用场景:

  1. 访问一个很大的Java对象,使用完之后,还要进行负责的耗时操作
  2. 创建了大量的局部引用,占用了太多的内存,而且这些局部引用跟后面的操作没有关联性。

例子:

extern "C"
JNIEXPORT void JNICALL
Java_com_example_asus1_ndklearn_MainActivity_localRef(JNIEnv *env, jobject instance) {

    int i = 0;
    for(;i<10;i++)
    {
        jclass  clz_date = env->FindClass("java/util/Date");
        jmethodID  mid = env->GetMethodID(clz_date,"<init>","()V");
        jobject  jobject_date = env->NewObject(clz_date,mid);
        
        //此处省略一万行代码
        
        
        //不再使用jobject对象
        //通知垃圾回收器回收这些对象,确保内存充足
        env->DeleteLocalRef(jobject_date);
        
    }

}
全局引用

主要作用:共享(可以跨多个线程),手动控制内存使用

//全局引用的字符串对象
jstring global_str;

//创建全局引用
extern "C"
JNIEXPORT void JNICALL
Java_com_example_asus1_ndklearn_MainActivity_createGlobalRef(JNIEnv *env, jobject instance) {

    global_str = env->NewStringUTF("I LOVE YOU");
    //通过NewGlobalRef创建全局引用
    env->NewGlobalRef(global_str);
    

}

//获取全局引用
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_asus1_ndklearn_MainActivity_getGlobalRef(JNIEnv *env, jobject instance) {

    // TODO
    return global_str;
}


//删除全局引用
extern "C"
JNIEXPORT void JNICALL
Java_com_example_asus1_ndklearn_MainActivity_deleteGlobalRef(JNIEnv *env, jobject instance) {

    // 通过DelteGlobalRef删除全局变量
    env->DeleteGlobalRef(global_str);

}
弱全局引用

使用方法与全局引用类似:

  1. 通过NewWeakGlobalRef创建全局引用。
  2. 通过DeleteWeakGlobalRef删除全局引用。

与全局引用不一样的是,弱引用:
3. 节省内存,在内存不足时可以释放所引用的对象。
4. 可以引用一个不常用的对象,如果为NULL的时候(被回收了),可以手动再临时创建一个。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值