JNI : Java Native Interface
1. 解决性能问题
Java具有平台无关性,这使人们在开发企业级应用的时候总是把它作为主要候选方案之一,但是性能方面的因素又大大削弱了它的竞争力。为此,提高Java的性能就显得十分重要。Sun公司及Java的支持者们为提高Java的运行速度已经做出了许多努力,其中大多数集中在程序设计的方法和模式选择方面。由于算法和设计模式的优化是通用的,对Java有效的优化算法和设计模式,对其他编译语言也基本同样适用,因此不能从根本上改变Java程序与编译型语言在执行效率方面的差异。由此,于是人们开始引入JIT(Just In Time,及时编译)的概念。它的基本原理是:首先通过Java编译器把Java源代码编译成平台无关的二进制字节码。然后在Java程序真正执行之前,系统通过JIT编译器把Java的字节码编译为本地化机器码。最后,系统执行本地化机器码,节省了对字节码进行解释的时间。这样做的优点是大大提高了Java程序的性能,缩短了加载程序的时间;同时,由于编译的结果并不在程序运行间保存,因此也节约了存储空间。缺点是由于JIT编译器对所有的代码都想优化,因此同样也占用了很多时间。
动态优化技术是提高Java性能的另一个尝试。该技术试图通过把Java源程序直接编译成机器码,以充分利用Java动态编译和静态编译技术来提高Java的性能。该方法把输入的Java源码或字节码转换为经过高度优化的可执行代码和动态库 (Windows中的. dll文件或Unix中的. so文件)。该技术能大大提高程序的性能,但却破坏了Java的可移植性。
JNI(Java Native Interface, Java本地化方法)技术由此闪亮登场。因为采用JNI技术只是针对一些严重影响Java性能的代码段,该部分可能只占源程序的极少部分,所以几乎可以不考虑该部分代码在主流平台之间移植的工作量。同时,也不必过分担心类型匹配问题,我们完全可以控制代码不出现这种错误。此外,也不必担心安全控制问题,因为Java安全模型已扩展为允许非系统类加载和调用本地方法。根据Java规范,从JDK 1. 2开始,FindClass将设法找到与当前的本地方法关联的类加载器。如果平台相关代码属于一个系统类,则无需涉及任何类加载器; 否则,将调用适当的类加载器来加载和链接已命名的类。换句话说,如果在Java程序中直接调用C/C++语言产生的机器码,该部分代码的安全性就由Java虚拟机控制。
2. 解决本机平台接口调用问题
JAVA以其跨平台的特性深受人们喜爱,而又正由于它的跨平台的目的,使得它和本地机器的各种内部联系变得很少,约束了它的功能。解决JAVA对本地操作的一种方法就是JNI。JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法。
3. 嵌入式开发应用
“一次编程,到处使用”的Java软件概念原本就是针对网上嵌入式小设备提出的,几经周折,目前SUN公司已推出了J2ME(Java 2 P1atform Micro Edition)针对信息家电的Java版本,其技术日趋成熟,开始投入使用。SUN公司Java虚拟机(JVM)技术的有序开放,使得Java软件真正实现跨平台运行,即Java应用小程序能够在带有JVM的任何硬软件系统上执行。加上Java语言本身所具有的安全性、可靠性和可移植性等特点,对实现瘦身上网的信息家电等网络设备十分有利,同时对嵌入式设备特别是上网设备软件编程技术产生了很大的影响。也正是由于JNI解决了本机平台接口调用问题,于是JNI在嵌入式开发领域也是如火如荼。
MyNativeInterface.java
layout : jni_test.xml
用javah命令生成head文件 :
用法:javah [选项] <类>
其中 [选项] 包括:
-help 输出此帮助消息并退出
-classpath <路径> 用于装入类的路径
-bootclasspath <路径> 用于装入引导类的路径
-d <目录> 输出目录
-o <文件> 输出文件(只能使用 -d 或 -o 中的一个)
-jni 生成 JNI样式的头文件(默认)
-version 输出版本信息
-verbose 启用详细输出
-force 始终写入输出文件
使用全限定名称指定 <类>(例
如,java.lang.Object)。
(误)javah -classpath bin -d jni com.demos.jni
// 错误:无法访问 com.demos.jni
// 未找到 com.demos.jni 的类文件
// javadoc: 错误 - 找不到类 com.demos.jni。
// Error: 未在命令行中指定任何类。请尝试使用 -help。
(正)javah -classpath bin -d jni com.demos.jni.MyNativeInterfaceViewDemo
此时在Android Project 根目录下生成jni文件夹其中包含文件com_demos_jni_MyNativeInterface.h
com_demos_jni_MyNativeInterface.h: 内容为:
之后实现C/C++代码(存放位置:/jni/com_demos_jni_MyNativeInterface.cpp):
如:
Android Project 工程根目录JNIDemo/Android.mk
JNIDemo/jni/Android.mk内容
之后将JNIDemo工程放到AndroidSource/package/app/JNIDemo
运行shell : . build/envsetup.sh
编译JNIDemo应用: mmm package/app/JNIDemo
如编译成功会有如下提示:
target Package: JNIDemo (out/target/product/generic/obj/APPS/ViewDemo_intermediates/package.apk)
Warning: AndroidManifest.xml already defines versionCode (in http://schemas.android.com/apk/res/android)
Warning: AndroidManifest.xml already defines versionName (in http://schemas.android.com/apk/res/android)
'out/target/common/obj/APPS/ViewDemo_intermediates/classes.dex' as 'classes.dex'...
Install: out/target/product/generic/system/app/JNIDemo.apk
Copy: out/target/product/generic/system/etc/apns-conf.xml
make: 放弃循环依赖 .so <- .so 。
make: 放弃循环依赖 .so <- out/target/product/generic/symbols/system/lib/libJNITest 。
make: 放弃循环依赖 .so <- .so 。
target Non-prelinked: libJNITest (out/target/product/generic/symbols/system/lib/libJNITest)
Install: out/target/product/generic/system/lib/libJNITest.so
之后将out/target/product/generic/system/lib/libJNITest.so push 到你的Android手机或者emulator 的system/lib/下(adb push out/target/product/generic/system/lib/libJNITest.so system/lib)
运行JNIDemo.apk已经加载System.loadLibrary("JNITest");
并获得数据。
其它:
对程序的一点解释:
1) 前文不是说过,加了static和不加只是一个参数的区别吗。就是jclass的不同,不加static这里就是jobject。也就是JNIEXPORT void JNICALL Java_MyNative_HelloWord(JNIEnv *env, jobject obj)。
2) 这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。而jstring是以JNI为中介使JAVA的String类型与本地的string沟通的一种类型,我们可以视而不见,就当做String使用(具体对应见表一)。函数的名称是JAVA_再加上java程序的package路径再加函数名组成的(参见有包的情况)。参数中,我们也只需要关心在JAVA程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。
3) NewStringUTF()是JNI函数,从一个包含UTF格式编码字符的char类型数组中创建一个新的jstring对象。
4) 以上程序片断jstr=env->NewStringUTF(str);是C++中的写法,不必使用env指针。因为JNIEnv函数的C++版本包含有直接插入成员函数,他们负责查找函数指针。而对于C的写法,应改为:jstr=(*env)->NewStringUTF(env,str);因为所有JNI函数的调用都使用env指针,它是任意一个本地方法的第一个参数。env指针是指向一个函数指针表的指针。因此在每个JNI函数访问前加前缀(*env)->,以确保间接引用函数指针。
在C和Java编程语言之间传送值时,需要理解这些值类型在这两种语言间的对应关系。这些都在头文件jni.h中,用typedef语句声明了这些类在目标平台上的代价类。头文件也定义了常量如:JNI_FALSE=0 和JNI_TRUE=1;表一说明了Java类型和C类型之间的对应关系。
表一 Java类型和C类型
见附件
以上
Consuela / 20110111 14:42
参考文献:
1. 解决性能问题
Java具有平台无关性,这使人们在开发企业级应用的时候总是把它作为主要候选方案之一,但是性能方面的因素又大大削弱了它的竞争力。为此,提高Java的性能就显得十分重要。Sun公司及Java的支持者们为提高Java的运行速度已经做出了许多努力,其中大多数集中在程序设计的方法和模式选择方面。由于算法和设计模式的优化是通用的,对Java有效的优化算法和设计模式,对其他编译语言也基本同样适用,因此不能从根本上改变Java程序与编译型语言在执行效率方面的差异。由此,于是人们开始引入JIT(Just In Time,及时编译)的概念。它的基本原理是:首先通过Java编译器把Java源代码编译成平台无关的二进制字节码。然后在Java程序真正执行之前,系统通过JIT编译器把Java的字节码编译为本地化机器码。最后,系统执行本地化机器码,节省了对字节码进行解释的时间。这样做的优点是大大提高了Java程序的性能,缩短了加载程序的时间;同时,由于编译的结果并不在程序运行间保存,因此也节约了存储空间。缺点是由于JIT编译器对所有的代码都想优化,因此同样也占用了很多时间。
动态优化技术是提高Java性能的另一个尝试。该技术试图通过把Java源程序直接编译成机器码,以充分利用Java动态编译和静态编译技术来提高Java的性能。该方法把输入的Java源码或字节码转换为经过高度优化的可执行代码和动态库 (Windows中的. dll文件或Unix中的. so文件)。该技术能大大提高程序的性能,但却破坏了Java的可移植性。
JNI(Java Native Interface, Java本地化方法)技术由此闪亮登场。因为采用JNI技术只是针对一些严重影响Java性能的代码段,该部分可能只占源程序的极少部分,所以几乎可以不考虑该部分代码在主流平台之间移植的工作量。同时,也不必过分担心类型匹配问题,我们完全可以控制代码不出现这种错误。此外,也不必担心安全控制问题,因为Java安全模型已扩展为允许非系统类加载和调用本地方法。根据Java规范,从JDK 1. 2开始,FindClass将设法找到与当前的本地方法关联的类加载器。如果平台相关代码属于一个系统类,则无需涉及任何类加载器; 否则,将调用适当的类加载器来加载和链接已命名的类。换句话说,如果在Java程序中直接调用C/C++语言产生的机器码,该部分代码的安全性就由Java虚拟机控制。
2. 解决本机平台接口调用问题
JAVA以其跨平台的特性深受人们喜爱,而又正由于它的跨平台的目的,使得它和本地机器的各种内部联系变得很少,约束了它的功能。解决JAVA对本地操作的一种方法就是JNI。JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法。
3. 嵌入式开发应用
“一次编程,到处使用”的Java软件概念原本就是针对网上嵌入式小设备提出的,几经周折,目前SUN公司已推出了J2ME(Java 2 P1atform Micro Edition)针对信息家电的Java版本,其技术日趋成熟,开始投入使用。SUN公司Java虚拟机(JVM)技术的有序开放,使得Java软件真正实现跨平台运行,即Java应用小程序能够在带有JVM的任何硬软件系统上执行。加上Java语言本身所具有的安全性、可靠性和可移植性等特点,对实现瘦身上网的信息家电等网络设备十分有利,同时对嵌入式设备特别是上网设备软件编程技术产生了很大的影响。也正是由于JNI解决了本机平台接口调用问题,于是JNI在嵌入式开发领域也是如火如荼。
package com.demos.jni;
import com.demos.views.R;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class JniDemo extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.jni_test);
System.loadLibrary("JNITest");
TextView JNITest = (TextView) findViewById(R.id.JNITest);
String data = MyNativeInterface.loadData();
JNITest.setText(data);
}
}
MyNativeInterface.java
package com.demos.jni;
public class MyNativeInterface {
public static native String loadData();
}
layout : jni_test.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:id="@+id/JNITest" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:text="@string/jni_test" />
</LinearLayout>
用javah命令生成head文件 :
用法:javah [选项] <类>
其中 [选项] 包括:
-help 输出此帮助消息并退出
-classpath <路径> 用于装入类的路径
-bootclasspath <路径> 用于装入引导类的路径
-d <目录> 输出目录
-o <文件> 输出文件(只能使用 -d 或 -o 中的一个)
-jni 生成 JNI样式的头文件(默认)
-version 输出版本信息
-verbose 启用详细输出
-force 始终写入输出文件
使用全限定名称指定 <类>(例
如,java.lang.Object)。
(误)javah -classpath bin -d jni com.demos.jni
// 错误:无法访问 com.demos.jni
// 未找到 com.demos.jni 的类文件
// javadoc: 错误 - 找不到类 com.demos.jni。
// Error: 未在命令行中指定任何类。请尝试使用 -help。
(正)javah -classpath bin -d jni com.demos.jni.MyNativeInterfaceViewDemo
此时在Android Project 根目录下生成jni文件夹其中包含文件com_demos_jni_MyNativeInterface.h
com_demos_jni_MyNativeInterface.h: 内容为:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_demos_jni_MyNativeInterface */
#ifndef _Included_com_demos_jni_MyNativeInterface
#define _Included_com_demos_jni_MyNativeInterface
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_demos_jni_MyNativeInterface
* Method: loadData
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_demos_jni_MyNativeInterface_loadData
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
之后实现C/C++代码(存放位置:/jni/com_demos_jni_MyNativeInterface.cpp):
如:
#include "com_demos_jni_MyNativeInterface.h"
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
JNIEXPORT jstring JNICALL Java_com_demos_jni_MyNativeInterface_loadData
(JNIEnv * env, jobject obj)
{
char * str = "hello JNI!";
printf("\n c-string: %s", str);
jstring jstr;
//char str="Hello,word!\n";
jstr=env->NewStringUTF("Android JNI Demo!");
return jstr;
}
Android Project 工程根目录JNIDemo/Android.mk
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := user eng
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := JNIDemo
include $(BUILD_MULTI_PREBUILT)
include $(BUILD_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
JNIDemo/jni/Android.mk内容
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
com_demos_jni_MyNativeInterface.cpp
LOCAL_C_INCLUDES := \
$(JNI_H_INCLUDE)
LOCAL_SHARED_LIBRARIES := libutils
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE := libJNITest
include $(BUILD_SHARED_LIBRARY)
之后将JNIDemo工程放到AndroidSource/package/app/JNIDemo
运行shell : . build/envsetup.sh
编译JNIDemo应用: mmm package/app/JNIDemo
如编译成功会有如下提示:
target Package: JNIDemo (out/target/product/generic/obj/APPS/ViewDemo_intermediates/package.apk)
Warning: AndroidManifest.xml already defines versionCode (in http://schemas.android.com/apk/res/android)
Warning: AndroidManifest.xml already defines versionName (in http://schemas.android.com/apk/res/android)
'out/target/common/obj/APPS/ViewDemo_intermediates/classes.dex' as 'classes.dex'...
Install: out/target/product/generic/system/app/JNIDemo.apk
Copy: out/target/product/generic/system/etc/apns-conf.xml
make: 放弃循环依赖 .so <- .so 。
make: 放弃循环依赖 .so <- out/target/product/generic/symbols/system/lib/libJNITest 。
make: 放弃循环依赖 .so <- .so 。
target Non-prelinked: libJNITest (out/target/product/generic/symbols/system/lib/libJNITest)
Install: out/target/product/generic/system/lib/libJNITest.so
之后将out/target/product/generic/system/lib/libJNITest.so push 到你的Android手机或者emulator 的system/lib/下(adb push out/target/product/generic/system/lib/libJNITest.so system/lib)
运行JNIDemo.apk已经加载System.loadLibrary("JNITest");
并获得数据。
其它:
对程序的一点解释:
1) 前文不是说过,加了static和不加只是一个参数的区别吗。就是jclass的不同,不加static这里就是jobject。也就是JNIEXPORT void JNICALL Java_MyNative_HelloWord(JNIEnv *env, jobject obj)。
2) 这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。而jstring是以JNI为中介使JAVA的String类型与本地的string沟通的一种类型,我们可以视而不见,就当做String使用(具体对应见表一)。函数的名称是JAVA_再加上java程序的package路径再加函数名组成的(参见有包的情况)。参数中,我们也只需要关心在JAVA程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。
3) NewStringUTF()是JNI函数,从一个包含UTF格式编码字符的char类型数组中创建一个新的jstring对象。
4) 以上程序片断jstr=env->NewStringUTF(str);是C++中的写法,不必使用env指针。因为JNIEnv函数的C++版本包含有直接插入成员函数,他们负责查找函数指针。而对于C的写法,应改为:jstr=(*env)->NewStringUTF(env,str);因为所有JNI函数的调用都使用env指针,它是任意一个本地方法的第一个参数。env指针是指向一个函数指针表的指针。因此在每个JNI函数访问前加前缀(*env)->,以确保间接引用函数指针。
在C和Java编程语言之间传送值时,需要理解这些值类型在这两种语言间的对应关系。这些都在头文件jni.h中,用typedef语句声明了这些类在目标平台上的代价类。头文件也定义了常量如:JNI_FALSE=0 和JNI_TRUE=1;表一说明了Java类型和C类型之间的对应关系。
表一 Java类型和C类型
见附件
以上
Consuela / 20110111 14:42
参考文献: