《深入理解Android》导读之JNI

        首先声明,以下内容,大部分摘抄自《深入理解Android》一书,只是结合自己的理解作了些重点的梳理。

        读这章之前,如果能看过JNI相关的书,那么看这章可能会有感觉一点。以前看JNI的文档,有点像谭浩强的C,比较注重实用性,而这本书,则比较主用原理性,有点像《c programme language》,看完之后会对JNI的总体印象和原理明白得透彻一点,而不是光是盲目的实用。

        这章主要讲了JNI技术中的几个重要方面,包括

        1. JIN函数的注册方法

        2.Java和JNI层数据类型的转换

        3.JNIEnv和jstring的实用方法,以及JNI中的类型签名

        4.垃圾回收在JNI层中的实用,以及异常处理方面的知识


概述

      首先,是以MediaScanner为例子的典型JNI层次图:

        

       


        JNI库的名字可以随便取,但是Android平台基本上都采用“lib模块名_jni.so”的命名方式。

        对于Java程序员来说,只要做两件事情:

       a. 加载对应的JNI库。

            在调用native函数前,任何时候,任何地方加载都可以。通行的做法是在类的static语句中加载,调用System.loadLibrary方法

       b.声明由关键字native修饰的函数



JNI函数的注册方法

        JNI函数的注册方法有两种,静态注册和动态注册。

    1.静态注册

        当Java层调用native_init函数是,它会从对应的JNI库中需找Java_android_media_MediaScanner_native_linit函数(注意,这里没有写错,是native_linit,因为Java层函数名中如果有一个“_”, javah转换出来的JNI后就变成了“_l”),如果没有,就会报错。如果找到,则会为这个native_init和Java_android_media_MediaScanner_native_linit建立一个关联关系(android.media是包名,MediaScanner是类名),其实就是保存JNI层函数的函数指针。以后再调用native_init函数时,直接使用这个函数指针就可以了,当然这项工作是有虚拟机完成的。
       因此,静态方法就是根据函数名来建立Java函数和JNI函数之间的关联关系的,这里要求JNI层函数的名字必须遵循特定的格式。

    2.动态注册

        系统提供结构体:

typedef struct{
       const char* name;  //Java中native函数的名字,不用携带包的路径,例如“native_init”
        const char* signature; //Java函数的签名信息,用字符串表示,是参数类型和返回类型的组合,用以应对Java里的函数重载
        void* fnPtr; //JNI层对应函数的函数指针,注意他是void*类型
} JNINativeMethod

      首先定义一个JNINativeMethod数据,里面填写好了Java函数名和对应的输入输出和对应JNI的函数名,然后调用动态注册函数register_android_media_MediaScanner,实质上是调用AnroidRuntime::registerNativeMethods,再实质上是调用jniRegisterNativeMethods函数,这个函数本质上只做了两件事情,如下:

jclass clazz = (*env)->FindClass(env,className); //className为对应的Java类名,在这里为“android/media/MediaScanner”
 (*env)->RegisterNatives(env, clazz, gMethods, numMethods); //最终调用JNIEnv的RegisterNatives函数,注册关联关系。


        那什么时候调用register_android_media_MediaScanner去动态注册JNI函数呢?当Java层通过System.loadLibrary加载完JNI动态库后,紧接着回查找该库中一个叫JNI_OnLoad的函数。如果有,就调用他,而动态注册的工作就是在这里完成的。所以,如果想使用动态注册方法,就必须实现JNI_OnLoad的函数,只有在这个函数中才有机会完成动态注册的工作。静态注册的方法则没有这个要求,但建议大家也实现这个JNI_OnLoad函数,因为有一些初始化工作是可以在这里做的。


Java和JNI层数据类型的转换

        数据类型的对应转换,就是一个表,不细说了,其中有一点,对象类型都用jobject表示,就好比是Native层的void *类型一样。我觉得这里讲得最有用的是Native层怎样调用Java的东西。
        首先是JNIEnv,先贴个图:

      
        JNIEnv实际上就是提供了一些JNI系统函数。通过这些函数可以调用Java的函数和操作jobject对象等很多事情。JNIEnv是线程相关的,不同的线程有不同的JNIEnv结构体,JavaVm是进程相关的,无论有多少个进程,一个线程只有一个JavaVM,它们的关系是:
        1. 调用JavaVM的AttachCurrentThread函数,就可以得到这个线程的JNIEnv结构体。这样就可以在后台线程中回调Java函数了。
        2.另外,在后台线程退出前,需要调用JavaVM的DetachCurrentThread函数来释放

        Native层调用Java层函数,只要通过env->GetFieldID, env->GetMethodID得到成员变量或者函数的ID,通过env->Call*Method, 其中*为method的返回类型,来调用Java函数,通过Get/Set*Field,其中*为成员变量类型,来获得或者设置值。


垃圾回收

       在Java里,对已个引用类型执行赋值操作,它的引用计数就会增加,垃圾回收机制会保证那些没有被引用的对象才会被清理。但如果在JNI层使用下面的语句,是不会增加引用计数的:
save_thiz = thiz
       
    因此,如果thiz引用计数为0,会被回收。为解决这一问题,JNI技术一共提供了三种类型的引用:
     1. Local Reference:本地引用。一旦JNI层函数返回,这些jobject就可能被垃圾回收
     2. Global Reference:  全局引用。如果不住动释放,它就永远不会被垃圾回收。
     3. Weak Global Reference: 弱全局引用,在运行时有可能会被回收,在使用之前,需要调用JNIEnv的IsSameObject判断它是否被回收了


       所以,每当JNI层想要保存Java层中的某个对象时,就可以使用Global Reference, 使用完之后记住释放它。
      另外,虽然Local Reference在引用的函数退出之后会自动被回收,但是在不再需要用到时候,还是建议显式调用DeleteLocalRef来主动释放,因为没有即使回收Local Reference或许是进程占用内存过多的一个原因。


看完这一章,有以下不明白的地方:

1. 应该找时间看一下RegisterNatives这个函数到底做了些什么。

2.如果用静态注册的方法,一个JNI函数只能被注册到一个JAVA函数里,因为包名是严格遵守的。那么如果用动态注册的方法,能不能使一个JNI函数,同时被注册到两个不同包或者不同类的JAVA函数里呢?有空要试试。

3.关于JNIEnv和JavaVM的区别和相关的进程线程的东西,书上没有具体的例子,我也不大能搞明白

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值