JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。在安卓中,主要做到以下两点:
Java程序中函数可以调用Native语言写的函数,Native一般指C/C++编写的函数
Native函数也可以反过来调用Java层函数
Java平台中,为什么需要创建一个与Native相关的JNI技术呢?是不是破坏了Java的平台无关特性,其实主要考虑到如下方面:
本文从源码中的MediaScanner中来初步窥视JNI用法
上图可以看到Java层 、JNI层、Native层之间的架构,JNI是中间层【这里区分了libmedia_jni.so、libmedia.so:平常开发直接用一个.so 文件。这里区分为了说明JNI层和Native层】
源码位置:android\frameworks\base\media\java\android\media\MediaScanner.java
public class MediaScanner
{
//1、静态代码块
static {
System.loadLibrary("media_jni"); //导入库
native_init(); //初始化操作,是一个native方法
}
//2、普通方法【非native方法】
@Override
public void scanFile(String path, long lastModified, long fileSize,
boolean isDirectory, boolean noMedia) {
// This is the callback funtion from native codes.
// Log.v(TAG, "scanFile: "+path);
doScanFile(path, null, lastModified, fileSize, isDirectory, false, noMedia);
}
//3、native 方法【它的具体实现实在JNI层完成的】
private native void processDirectory(String path, MediaScannerClient client);
private native void processFile(String path, String mimeType, MediaScannerClient client);
public native void setLocale(String locale);
public native byte[] extractAlbumArt(FileDescriptor fd);
private static native final void native_init();
private native final void native_setup();
private native final void native_finalize();
}
上面可以看到三点:1)加载JNI库 2)初始化Native层 3)声明native方法,具体实现再JNI层实现,应用层只需要声明native方法,程序中直接使用
加载jni库其实很简单,如上面MediaScanner源码中,静态代码块中直接调用System.loadLibrary方法就可以了。其参数就是动态库的名字,即media_jni.系统会根据不同的平台拓展成动态库文件,Linux系统会拓展成libmedia_jni.so,而在Windows平台会拓展成media_jni.dll
通过MediaScanner.java 的native函数刨根问底,追一下native_init() 和 processFile(String path, String mimeType, MediaScannerClient client)
源码位置:android\frameworks\base\media\jni\android_media_MediaScanner.cpp
// This function gets a field ID, which in turn causes class initialization.
// It is called from a static block in MediaScanner, which won't run until the
// first time an instance of this class is used.
static void android_media_MediaScanner_native_init(JNIEnv *env)
{
jclass clazz = env->FindClass(kClassMediaScanner);
fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
if (fields.context == NULL) {
return;
}
}
//native 方法processFile的实现
static void android_media_MediaScanner_processFile(
JNIEnv *env, jobject thiz, jstring path,
jstring mimeType, jobject client)
{
......
MediaScanner *mp = getNativeScanner_l(env, thiz);
const char *pathStr = env->GetStringUTFChars(path, NULL);
if (pathStr == NULL) { // Out of memory
return;
}
const char *mimeTypeStr =
(mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL);
......
MyMediaScannerClient myClient(env, client);
MediaScanResult result = mp->processFile(pathStr, mimeTypeStr, myClient);
env->ReleaseStringUTFChars(path, pathStr);
if (mimeType) {
env->ReleaseStringUTFChars(mimeType, mimeTypeStr);
}
}
有兴趣好好看一下源码,肯定会有很多疑问,比如:JNIEnv 、FindClass、jclass、ReleaseStringUTFChars 等,都是什么,都在执行什么任务。
不要急,且看如下内容!
所谓JNI函数注册就是将JNI层的native层的native函数和JNI层对于的实现函数关联起来,有了这种关联,在调用Java层测native函数时候,就能够顺利到JNI层对应的函数执行了。JNI函数注册有两种,我们主要分析第二种。
静态注册
静态注册就是直接在Java文件里写个native方法 然后再c/c++文件中实现这个方法就行了!流程如下:
1)编写 java 代码;
2)利用 javah 指令生成对应的 .h 文件;
3)对 .h 中的声明进行实现;
比如如下实现:
JNIEXPORT jstring JNICALL
Java_com_example_efan_jni_1learn2_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
那么有哪些弊端呢:
1)编写不方便,JNI 方法名字必须遵循规则且名字很长;
2)编写过程步骤多,不方便;
3)程序运行效率低,因为初次调用native函数时需要根据根据函数名在JNI层中搜索对应的本地函数,然后建立对应关系,这个过程比较耗时
jstring stringFromJNI(JNIEnv *env, jobject thiz){
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
static const JNINativeMethod gMethods[] = {
{"stringFromJNI", "()Ljava/lang/String;", (jstring*)stringFromJNI}
};
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
__android_log_print(ANDROID_LOG_INFO, "native", "Jni_OnLoad");
JNIEnv* env = NULL;
if(vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) //从JavaVM获取JNIEnv,一般使用1.4的版本
return -1;
jclass clazz = env->FindClass("com/example/efan/jni_learn2/MainActivity");
if (!clazz){
__android_log_print(ANDROID_LOG_INFO, "native", "cannot get class: com/example/efan/jni_learn2/MainActivity");
return -1;
}
if(env->RegisterNatives(clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0])))
{
__android_log_print(ANDROID_LOG_INFO, "native", "register native method failed!\n");
return -1;
}
return JNI_VERSION_1_4;
}
如上是一个简易的动态注册流程。
Java native函数和JNI函数式一一对应的,其实在在动态注册中直接用一个数据结构来保存这种关联关系的,用一个JNINativeMethod 的结构。
typedef struct {
const char* name; //name是Java中函数的名字
const char* signature; //signature,用字符串是描述了函数的参数和返回值
void* fnPtr; //fnPtr是函数指针,指向C函数。
} JNINativeMethod;
咋们再分析下MediaScanner
源码位置:master\android\frameworks\base\media\jni\android_media_MediaScanner.cpp
static 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
},
{
"setLocale",
"(Ljava/lang/String;)V",
(void *)android_media_MediaScanner_setLocale
},
{
"extractAlbumArt",
"(Ljava/io/FileDescriptor;)[B",
(void *)android_media_MediaScanner_extractAlbumArt
},
{
"native_init",
"()V",
(void *)android_media_MediaScanner_native_init
},
{
"native_setup",
"()V",
(void *)android_media_MediaScanner_native_setup
},
{
"native_finalize",
"()V",
(void *)android_media_MediaScanner_native_finalize
},
};
其中:processDirectory 、 processFile 、 setLocale 、 extractAlbumArt 、 native_init 、native_setup 、 native_finalize 方法不就是Java中native的放方法名么。
AndroidRunTime 类提供了一个registerNativeMethods函数完成注册工作的,如下源码:
\master\android\frameworks\base\core\jni\AndroidRuntime.cpp
/*
* Register native methods using JNI.
*/
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
const char* className, const JNINativeMethod* gMethods, int numMethods)
{
return jniRegisterNativeMethods(env, className, gMethods, numMethods); //就是这个方法
}
其中的jniRegisterNativeMethods是Android平台中为了方便JNI使用而提供的一个帮助函数。【RndroidRunTime源码可以看一看,有很多看了就非常熟悉的内容】
\master\android\libnativehelper\JNIHelp.cpp
extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
JNIEnv* e = reinterpret_cast(env);
scoped_local_ref c(env, findClass(env, className));
---
//下面的RegisterNatives 才是重点方法:真正实现注册的地方
//调用JNIEnv 的RegiisterNatives 函数,注册关联关系
//e 是env指针 c.get()得到jclass gMethods是native的函数数组
if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
---
}
return 0;
}
上面就完成了动态注册的流程, 还有一个问题需要考虑:这些动态注册的函数,什么时候和什么地方调用呢?
当Java层通过System.loadLibrary加载JNI动态库后,紧接着会查找该库中的一个叫JNI_OnLoad的函数,如果有就调用它,而动态注册的工作就是在这里完成的。
所以,如果想使用动态注册的方法,必须实现JNI_OnLoad函数,只有在这个函数中才有机会去完成动态注册的工作,有一些初始化工作可以在这里做的。
对于libmediia_jni.so的JNI_OnLoad函数实在哪里实现?多媒体系统很多地方都用了JNI,所以源码里面放在了android_media_MediaPlayer.cpp中。
\master\android\frameworks\base\media\jni\android_media_MediaPlayer.cpp
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
//第一个参数类型是JavaVM,这可是虚拟机在JNI层的代表,每个进程中只有一个这样的JavaVM
JNIEnv* env = NULL;
jint result = -1;
assert(env != NULL);
//动态注册MediaScanner 的JNII函数
if (register_android_media_MediaScanner(env) < 0) {
ALOGE("ERROR: MediaScanner native registration failed\n");
goto bail;
}
/* success -- return valid version number */
result = JNI_VERSION_1_4;
bail:
return result;
}
当Java层通过调用System.loadLibrary加载完JNI动态库后,紧接着会查找该库中一个叫JNI_OnLoad的函数。如果有,就调用他,而动态注册的工作就是在这里完成的。
所以,如果想使用动态注册方法,必须实现JNI_OnLoad函数,只有这个函数才有机会完成动态注册的工作,静态注册则没有这个要求。
你会发现在JNI世界里,你是离不开JNIEnv,它就是一个与线程相关的代表JNI环境的结构体。
上图可以看到:JNIEnv提供了一些JNI系统函数,通过这些函数可以
前面提到过JNI_OnLoad函数,第一个参数是JavaVM,它是虚拟机在JNI层的代表。
JNI_OnLoad(JavaVM* vm, void* reserved)
不论检查中多少个线程,JavaVM独此一份,在任意地方都可以使用它。
JavaVM和JNIEnv关系:
总结:看看JNI底层实现,分析源码,初步了解JNI。具体实践还需要了解JNI层是怎么操作Java层函数和调用JNI层函数的。
参考文章