OPhone平台的JNI机制探索
JNI是Java Native Interface的缩写,中文可译为Java本地调用。Java Native Interface (JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互.
OPhone下的JNI使得Dalvik虚拟机内部运行的Java代码能够与其他语言编写的应用程序或者库(例如C++编译完成之后的 so文件)进行交互.虽然JNI并不是Android的技术发展方向,但是作为一种重要的技术手段可以解决Linux内核和Dalvik虚拟机之间的交互 问题.
JNI提供的交互通道是双向的,即Java代码可以调用C/C++库中的功能, 另外,C/C++库也可以调用到Dalvik虚拟机之上的Java库功能.下面就针对这两种应用方式做详细说明。
1:通过JNI实现Java调用C/C++库功能
在Android的代码中,这样的例子不胜枚举.比如Media中的视频音频播放控制,Web页面的解析渲染显示,Graphics中 Paint功能等等,这些Java类在C++部分都有负责实现功能的C++类库相对应.较之Java,C++有更强的平台操控性和执行效率,所以比如像 WebKit这样需要复杂运算和平台依赖的核心类库来说,大部分的代码都是由C++来实现的.Brower这种强烈依赖WebKit的应用程序在 OPhone下都是用Java来实现的,这样JNI就负责了Java层与webcore.so(WebKit编译之后的类库)之间的通信.
打开Eclipse,我们做一个HelloJNI的例子.
创建一个Android工程,命名为HelloJNI
编写Java端文件: HelloJNI.java
- public class HelloJNI extends Activity {
- static {
- String libPath="cpluslib.so" ;
- //向Dalvik加载指定相对路径的C++库。
- System.loadLibrary(libPath);
- }
- //需要JNI的函数必须声明为 public native static
- public native static int get();
- public native static void set( int i);
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super .onCreate(savedInstanceState);
- setContentView(R.layout.main);
- set(100 );
- Log.v("HelloJNI" , "value=" +get());
- }
- }
public class HelloJNI extends Activity {
static {
String libPath="cpluslib.so";
//向Dalvik加载指定相对路径的C++库。
System.loadLibrary(libPath);
}
//需要JNI的函数必须声明为 public native static
public native static int get();
public native static void set(int i);
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
set(100);
Log.v("HelloJNI","value="+get());
}
}
用javac HelloJNI.java编译它,会生成HelloJNI.class。
再用javah HelloJNI ,则会在当前目录下生成HelloJNI.h文件,这个文件需要被C/C++程序调用来生成所需的库文件。 如下(此文件不能被修改)
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include
- /* Header for class HelloJNI*/
- #ifndef _Included_HelloJNI
- #define _Included_HelloJNI
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: HelloJNI
- * Method: get
- * Signature: ()I
- */
- JNIEXPORT jint JNICALL Java_HelloJNI_get (JNIEnv *, jclass);
- /*
- * Class: HelloJNI
- * Method: set
- * Signature: (I)V
- */
- JNIEXPORT void JNICALL Java_HelloJNI_set (JNIEnv *, jclass, jint);
- #ifdef __cplusplus
- }
- #endif
- #endif
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class HelloJNI*/
#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloJNI
* Method: get
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_HelloJNI_get (JNIEnv *, jclass);
/*
* Class: HelloJNI
* Method: set
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_HelloJNI_set (JNIEnv *, jclass, jint);
#ifdef __cplusplus
}
#endif
#endif
打开Vim,编写C++端代码
在具体实现的时候,我们只关心两个函数原型
JNIEXPORT jint JNICALL Java_HelloJNI_get (JNIEnv *, jclass); 和
JNIEXPORT void JNICALL Java_HelloJNI_set (JNIEnv *, jclass, jint);
这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。函数的名称是JAVA_再加上java程序的package路径再加函数名组成的。参数中,我们也只需要关心在JAVA程序中存在的参数。
- #include "HelloJNI.h"
- int i = 0 ;
- JNIEXPORT jint JNICALL Java_HelloJNI_get (JNIEnv *, jclass)
- {
- return i;
- }
- JNIEXPORT void JNICALL Java_HelloJNI_set (JNIEnv *, jclass, jint j)
- {
- i = j;
- }
#include "HelloJNI.h"
int i = 0;
JNIEXPORT jint JNICALL Java_HelloJNI_get (JNIEnv *, jclass)
{
return i;
}
JNIEXPORT void JNICALL Java_HelloJNI_set (JNIEnv *, jclass, jint j)
{
i = j;
}
编译链接生成cpluslib.so,将HelloJIN.apk和cpluslib.so Push到手机的/System/app下,运行.就可以在Log中看到.
2:通过JNI C/C++调用Java
在OPhone的系统框架中,在Applications和Linux kernal中间有三部分. Application Framework, Libraries, Android Runtimes.而Application Frameworks提供了Java层的框架API和核心对象,如Activity,View,ContentProvider,Service等等.他 们都是基于Dalvik虚拟机的Java类库. 与之相对的,Libraries都是C++类库,他们为上面的App Frameworks提供了核心对象的基础API,而后者对Libraries进行了包装来提供给Apps进行调用. C++ Libraries与App Frameworks的交互基本上都是通过JNI来实现的.
举一个JNI C/C++调用Java的例子. WebKit加载网页的过程中, 需要调用到Java层的HttpClient做网页的下载,C/C++要调用OPhone中Java程序的功能,必须先加载Dalvik虚拟机,由 Dailvk虚拟机解释执行apk/odex/dex文件。为了初始化Dalvik虚拟机,JNI提供了一系列的接口函数,通过这些函数方便地加载虚拟机 到内存.
1.加载虚拟机:
下面是WebKit加载Dalvik虚拟机的代码.
- static jint KJS_GetCreatedJavaVMs(JavaVM** vmBuf, jsize bufLen, jsize* nVMs)
- {
- static void * javaVMFramework = 0 ;
- //通过Dlopen来动态加载Java虚拟机
- if (!javaVMFramework)
- javaVMFramework = dlopen("/System/Library/Frameworks/JavaVM.framework/JavaVM" , RTLD_LAZY);
- if (!javaVMFramework)
- return JNI_ERR;
- static jint(*functionPointer)(JavaVM**, jsize, jsize *) = 0 ;
- if (!functionPointer)
- //调用创建虚拟机的函数,从已经加载的动态连接库中.
- functionPointer = (jint(*)(JavaVM**, jsize, jsize *))dlsym(javaVMFramework, "JNI_GetCreatedJavaVMs" );
- if (!functionPointer)
- return JNI_ERR;
- return functionPointer(vmBuf, bufLen, nVMs);
- }
static jint KJS_GetCreatedJavaVMs(JavaVM** vmBuf, jsize bufLen, jsize* nVMs)
{
static void* javaVMFramework = 0;
//通过Dlopen来动态加载Java虚拟机
if (!javaVMFramework)
javaVMFramework = dlopen("/System/Library/Frameworks/JavaVM.framework/JavaVM", RTLD_LAZY);
if (!javaVMFramework)
return JNI_ERR;
static jint(*functionPointer)(JavaVM**, jsize, jsize *) = 0;
if (!functionPointer)
//调用创建虚拟机的函数,从已经加载的动态连接库中.
functionPointer = (jint(*)(JavaVM**, jsize, jsize *))dlsym(javaVMFramework, "JNI_GetCreatedJavaVMs");
if (!functionPointer)
return JNI_ERR;
return functionPointer(vmBuf, bufLen, nVMs);
}
2.获取指定对象的类定义:
已知类名的情况使用FindClass来获取,如获得Java中的HashMap
jclass mapClass = env->FindClass("java/util/HashMap");
通过对象直接得到类定义GetObjectClass
jCalss clazz=env->GetObjectClass(obj);
3.获取要调用的方法:
比如Java端有个函数 void setTitle(String title);
C++部分要通过JNI获得这个方法:
jMethodID mSetTitle=env->GetMethodID(clazz,"setTitle","Ljava/lang/String;)V");
第三个参数是这个方法的Signature.如果不知道,那么运行javap -s -p 类名 就可以得到了.
4.调用JAVA层的方法
如上面已经得到SetTitle的Method ID,只需要运行env->CallVoidMethod(env, clazz , mSetTitle).
另外还有CallObjectMethod()方法,用来调用有返回值的java层方法。如:
- jobject obj = env->CallObjectMethod(env,calzz, dialog, userGesture);
jobject obj = env->CallObjectMethod(env,calzz, dialog, userGesture);
更加详尽的JNI资料请参见JNI白皮书,另外在WebKit Android版本的WebCoreFrameBridge.cpp和WebCoreJni.cpp能找到更多相关的应用实例。
http://www.ophonesdn.com/article/show/116