前言:前面一贴,我们大概了解了Android系统的架构,对于每个层次的东东也有了基本的了解,那么我们就可以正式动工,开始逐个突破,攻克每一个难点,你有没有很激动?OK,下面我们就拿JNI来练练手。
JNI:Java NativeInterface(Java本地调用),说的简单点,JNI就是一种技术,它是干什么的?主要有两点:
(1)Java程序中的函数可以调用Native语言写的函数,Native一般就是指C/C++编写的函数;
(2)Native程序中的函数可以调用Java层的函数。
看到这个,大家怎么想?其实,我们可以把JNI当作一个桥梁,连接着Java层与Native层。
虽然JNI是用Native编写的,但是我们依然把它归类于一个单独的层:JNI层。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1、JNI实例:MediaScanner(结合层次图进行分析)---多媒体系统的重要组成部分,功能是扫描媒体文件,得到诸如歌曲时长、歌曲作者等媒体信息,并将它们存入到媒体数据库中。
(1)Java世界对应的是MediaScanner,MediaScanner这个类有一些函数是需要Native层来实现的;
(2)JNI对应的是libmedia_jni.so,media_jni是JNI库的名称。其中_线前的media是native层库的名字,这里就是libmedia库。_线后的jni代表它是一个JNI库。JNI库的名称可以随便取,但是遵循以下命名规则:"lib模块名_jni.so";
(3)Native对应的是libmedia.so,这个库完成了实际的功能;
(4)Java层将通过JNI库的libmedia_jni.so与Native库的libmedia.so交互。
我们先来看看与JNI有关的代码:
1 public class MediaScanner 2 { 3 static{ 4 System.loadLibrary("media_jni"); //加载对应的JNI库 5 native_init(); //调用native函数 6 } 7 ...... 8 //非native函数 9 public void scanDirectories(String[] directories, String volumeName) 10 { 11 ...... 12 } 13 private static native final void native_init(); //申明一个native函数,native为Java关键字,表示它将由JNI层完成。 14 ...... 15 private native void processFile(String path, String mimeType, MediaScannerClient client); 16 ...... 17 }
以上的代码列出了两个比较重要的点:
(1)加载JNI库:要调用Native,就必须通过一个位于JNI层的动态库来实现,一般是在类的static语句中加载,方式是:System.loadLibrary,加载动态库的名字后,系统会自动根据不同的平台拓展成真实的动态库文件名,例如:在Linux中会拓展成libmedia_jni.so,而在windows平台上则会拓展成media_jni.dll。
(2)Java的native函数:我们发现在native_init和processFile函数前都有native关键字,它表示这两个函数将由JNI层来实现。
所以,你如果要想使用JNI,只要完成以上两项工作。不过JNI要完成任务,可是比较复杂的,下面我们会一起分析。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2、JNI层MediaScanner分析
由于时间的问题,部分JNI层代码用图片的形式表示。
此时,我相信你可能会产生疑惑:如何才能知道Java层的native_init函数对应的是JNI层的android_media_MediaScanner_native_init函数呢?
(1)注册JNI函数
大家知道,native_init函数位于android.media包中,它的全路径名称是:android.media.MediaScanner.native_init,而JNI层函数的名字是android_media_MediaScanner_native_init。
有没有发现问题?好了,我直接告诉你把:在Native语言中,符号 “.” 是有着特殊含义的,所以JNI层需要把Java函数名称(包括包名) 中的 “.” 换成 “ _ ”,也就是通过这种方式,native_init找到了自己JNI层的本家兄弟android_media_MediaScanner_native_init的。
其实上面,我们讨论的就是一个“注册”的问题。“注册”就是:将Java层的native函数和JNI层对应的实现函数关联起来。有了这种关联,调用Java层的native函数事,就能顺利转到JNI层对应的函数执行了。
“注册”有两种方式:(对于关联的具体不过代码不作分析,我们大概了解注册的原理即可,如果感兴趣的同学,可以给我回复,有必要的话,我会专门写一个帖子讲解一下)
· 静态注册:就是根据函数名来建立Java函数和JNI函数之间的关联关系(其实就是保存了JNI层函数的函数指针),而且它要求JNI层函数的名字必须遵循特定的格式。
· 动态注册:如果我们专门使用一个结构(JNINativeMethod)来保存这种关联关系,这样我们就可以直接让native函数知道JNI层对应函数的函数指针,这样的话,是不是提高了效率呢?答案是肯定的!
我们直接给出答案:当Java层通过System.loadLibrary加载完JNI动态库后,紧接着会查找该库中一个叫JNI_Onload的函数(此函数在android_media_MediaPlayer.cpp中)。如果有,就调用它。而动态注册的工作就是在这里完成的。
(2)数据类型转换问题
在Java中调用native函数,传递的参数是Java数据类型,那么这些参数传递到JNI层会变成什么?
我们知道,Java的数据类型分为两种:基本数据类型和引用数据类型。
对于这两个类型,JNI也是区分对待的,下面我们一起来看看:
· 基本数据类型的转换
需要注意一下字长的变化,比如jchar的字长为16位,占两个字节,这与普通的char占一个字节是不一样的。
· 引用数据类型的转换
(3)JNIEnv介绍
我们来分析以下JNI层函数的5个参数:后面三个参数,大家应该都比较清楚,它们对应Java中的native函数3个参数,那么另外两个参数是什么含义呢?
我们先来看一下jobject:代表Java层的MediaScanner对象,它表示在哪个MediaScanner对象上调用的processFile。如果Java层是static函数,那么这个参数将是jclass,表示是在调用哪个Java Class的静态函数。
下面,我们正式引出主角:JNIEnv。
JNIEnv:是一个与线程相关的代表JNI环境的结构体。
内部结构:
JNIEnv实际上就是提供了一些JNI系统函数,通过这些函数可以做到:
1、调用Java的函数;2、操作jobject对象等很多事情
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
在下一个帖子,我们将一起来看看如何使用JNIEnv的函数,并且会跟大家介绍一个关于JNIEnv的重要知识点。