深入理解Android-JNI

JNI概述

  • Java程序中的函数可以调用Native语言写的函数,Native一般指的是C/C++写的函数
  • Native层中的函数可以调用Java层的函数,也就是在C/C++程序中调用Java的函数
    要想做到以上两点,JNI(Java Native Interface)技术应运而生。
    深入分析后可以知道,虽然Java代码具有平台无关性,但运行在具体平台上的Java虚拟机并不能做到这一点,但是JNI技术的出现屏蔽了不同平台的差异,使得上层的Java具有了平台无关的特性。

MediaScanner的分析

android大量使用了JNI技术,书中分析了MediaScanner这个例子。

  1. Java世界对应的是MediaScanner类,这个类一些函数需要Native层来实现,比如对目录的扫描。
  2. Native层对应是libmedia.so,他完成了实际的功能。
  3. JNI层对应的是libmedia_jni.so,这里可以看出JNI层其实也是Native代码写的。其中下划线前的media是Native层库的名字,这里指的就死libmedia库,android对JNI库的名字有规范,一般是:
    lib模块名_jni.so
  4. Java层的MediaScanner将通过libmedia_jni.so来与Native层的libmedia.so交互。

Java层的MediaScanner

framewor/base/media/java/android/media/MediaScanner.java是MediaScanner在Java层源码的位置。

   public class MediaScanner
   {    
      static {
          /**加载对应的JNI库,media_jni是对应JNI库的名字,实际上加载动态库的时候他会拓展成libmedia_jni.so **/        
          System.loadLibrary("media_jni");        
          native_init();   //调用native_init()函数,他是一个native函数
       }
      ......
      //扫面目录的函数,里面用到了processDirectory这个native函数
      public void scanDirectories(String[] directories, String volumeName) {
      ......
      for (int i = 0; i < directories.length; i++) { 
            processDirectory(directories[i], mClient);
      }
      ......
      private native void processDirectory(String path, MediaScannerClient client);
      private static native final void native_init();
      ......

这里说明了两点内容,在需要native函数的java类中会包括JNI的加载和Native函数的声明,指示它由JNI层去完成。

  1. JNI加载流程
    Java层要调用Native函数,必须通过位于JNI层的动态库才能实现。动态库的必须在运行时加载,所以,我们通常的做法就是在类的static语句中加入System.loadLibrary方法完成对动态库的加载,系统会自动将参数转化成对应环境下真实动态库的名字。linux上是so文件,win下就是dll文件。
  2. Java的native函数
    加载完JNI库后,我们只需要在Java中声明由关键字native修饰的函数即可。是不是很简单,但是JNI层的MediaScanner就没那么轻松了。

JNI层的MediaScanner

JNI层的MediaScanner代码位于
frameworks/base/media/jni/android_media_MediaScanner.cpp
上面再Java层声明的native_init和processFile在这里都有具体的实现,那么问题就来了,Java层声明的函数如何才能对应到JNI层的。

  1. 注册JNI函数

native_init函数位于android.media这个包中,这样可以得到它的全路径名称android.media.MediaScanner.native_init,之后再将.全部换成_,native_init就找到了JNI层的实现。
上述就是JNI函数注册的问题

  • 静态注册
    流程如下:


注意:因为packagename是该类完整的包名,所以javah命令需要在包外面执行,否则会找不到该类
静态注册的弊端:1、需要编译所有声明了native函数的java类。2、javah生成的jni函数名会特别长。 3、初次调用native函数时要根据函数名来搜索对应JNI层的函数,并建立关联,这会影响效率。

  • 动态注册
    既然Java Native函数和JNI函数一一对应,能不能有这种结构可以保存这种关联关系呢,肯定是有的,在JNI技术中有一种JNINativeMethod的结构,它记录下了这些关联关系。其实Android大部分采用的是动态注册的方法
    MediaScanner的JNI层中的示例:
    static const JNINativeMethod gMethods[] = {
    {
    "processDirectory",
    "(Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
    (void )android_media_MediaScanner_processDirectory
    },
    {
    "processFile",
    "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
    (void )android_media_MediaScanner_processFile
    },
    .......
    {
    "native_init",
    "()V",
    (void )android_media_MediaScanner_native_init
    },
    ......
    };
    基本结构定义:
    typedef struct {
    //java类中native函数的名字,不用携带包名,例如“native_init”
    const char
    name;
    //java函数的签名信息,用字符串表示,是参数类型和返回值类型的组合
    const char
    signature;
    //JNI层对应函数的函数指针,它是void
    类型
    void* fnPtr;
    }JNINativeMethod;

    定义完毕之后,需要执行注册

     int register_android_media_MediaScanner(JNIEnv *env){
        return AndroidRuntime::registerNativeMethods(env,  
             kClassMediaScanner, gMethods, NELEM(gMethods));
    }
    

    我们发现AndroidRunTime类提供了注册函数,而它又调用了JNIHelp中的jniRegisterNativeMethods方法,最终会走到JNIEnv的RegisterNatives完成注册工作。

那到底是什么时候去执行这个?
在Java层执行System.loadLibrary加载JNI动态库后,紧接着会查找该库中一个JNI_OnLoad的函数,如果有的话,动态注册就在这里完成。然而在MediaScanner对应的android_media_MediaScanner.cpp中并没有发现这个函数。由于多媒体系统中很对地方用到JNI,所以register_android_media_MediaScanner这个注册方法被放在了android_media_MediaPlayer.cpp的JNI_OnLoad方法中,当然还有其它的多媒体相关的注册函数。

数据类型转换

主要解决的问题是:在Java中调用native函数传递的参数是Java数据类型,如何将他们转化成Native的数据类型。
Java分基本数据类型和引用数据类型,先看基本数据类型:

Java Native类型 符号属性 字长
boolean jboolean 无符号 8位
byte jbyte 无符号 8位
char jchar 无符号 16位
short jshort 有符号 16位
int jint 有符号 32位
long jlong 有符号 64位
float jfloat 有符号 32位
double jdouble 有符号 64位

这里要注意的是转换成Native类型后对应的数据类型的字长,char在Java中是占两个字节,jchar同样也是,要区别于普通char只占一个字节。

引用类型的转换

Java引用类型 Native类型 Java引用类型 Native类型
All object jobject char[] jcharArray
java.lang.Class实例 jclass short[] jshortArray
java.lang.String实例 jstring int[] jintArray
Object[] jobjectArray long[] jlongArray
boolean[] jbooleanArray flaot[] jflaotArray
byte[] jbyteArray double[] jdoubleArray
java.lang.Throwable实例 jthrowable

再拿MediaScanner举例,其中的processFile
android_media_MediaScanner_processFile( JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client){
Java层的函数中只传了三个参数,即path,mineType和client,这里它们的类型都转化成了native类型。前两个参数分别是JNIEnv和Java层的MediaScanner对象(jobject)

JNIEnv

JNIEnv其实是一个与线程相关的代表JNI环境的结构体。


JNIEnv提供了JNI相关的系统函数,这些函数可以做到:调用Java函数和操作jobject对象等。

  1. 通过JNIEnv操作jobject
    在JNI规则中,用jfiledID和jMethodID来表示java类的成员变量和成员函数。比如MyMediaScannerClient中。
    代码中将scanFile和handleStringTag函数的jmethodID保存为MyMediaScannerClient的成员变量,这是为了解决每次操作object都会去查询jmethodID或jfieldID而带来的运行效率降低。
    使用可以看virtual status_t scanFile中的mEnv->CallVoidMethod
    完成JNI层调用Java对象的函数。
  2. jstring
    JNIEnv提供有关jstring的函数
  3. JNI签名介绍
    Java函数支持函数重载,也就是说,可以定义同名但是不同参数的函数,但仅仅根据函数名是没法找到具体函数的。为了解决这个问题,JNI技术就将参数类型和返回值类型的组合作为一个函数的签名信息,有了这个签名和函数名,就能很顺利的找到Java中的函数。
  4. 垃圾回收
    JNI的三种类型引用技术:
    local reference:本地引用,一旦JNI函数返回时,jobject就可能会被回收
    Global reference:全局引用,不主动释放不会被回收
    Weak Global reference:特殊的全局引用,可能会被回收,每次使用时需要判断是否回收
  5. JNI中的异常处理



    JNIEnv提供了三个函数给予帮助
    ExceptionOccured函数,用来判断是否发生异常
    ExceptionClear函数,用来清理JNI层发生的异常。
    ThrownNew函数,用来Java层抛出异常。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容