本章篇幅较长,需要多天完成。下面部分是05/27/15完成的。
本章主要讨论JNI的其他特性。
8.1 JNI和线程
Java虚拟机支持在同一个地址空间中控制多个线程并发执行。多线程比单线程有多大的复杂度,比如多个线程可能会同时访问一个对象,需要进行多线程的同步与互斥。在继续下面的讨论之前,你必须要懂Java多线程编程,懂得同步与互斥。
安装的教程网上一大片,不再敖述。安装步骤就是下载安装JDK、下载和安装Android Studio和SDK、下载安装NDK。
打开Android Studio,在菜单栏File->Project Structure->SDK Location,填写正确的SDK安装目录和JDK的安装目录。如果已经填写好了,确认正确后保存即可。
打开SDK Manager,在菜单栏Tools->Android->SDK Manager,自动从Google的网站上搜寻SDK Platfrom相关的下载包(需要翻墙)。建议只下载最新版本的SDK Platform与4.2的SDK Platform相关文件,其他的Platform按需下载。
由于本章篇幅太长,需要花费几天的时间才能介绍完。下面部分是05/18/15完成的。
JNI使用不透明引用来使用对象、数组(jobject、jclass、jstring、jarray等)。不透明引用屏蔽了JNI内部数据结构的实现细节,让程序员无需了解Java虚拟机中JNI的实现细节。原生代码不能直接通过不透明引用访问引用指向的内容,必须通过JNI函数来访问。
在JNI中,有三种不同的不透明引用:
1.局部引用、全局引用和弱全局引用。
2.局部引用在原生代码离开其作用于后被自动释放(很像C/C++中的局部变量,离开其作用域后自动销毁),全局引用和弱全局引用必须由程序员手动显式释放(很像C中的malloc与free,C++中的new与delete)。
3.局部引用和全局引用在其有效期间,其指向的对象可以不被垃圾回收机制回收。而弱全局引用在其有效期间则允许垃圾回收机制回收其指向的对象。
4.并不是所有的引用都能在任何上下文中被使用。比如当创建一个局部引用的原生代码返回值后,该局部引用就不能继续被使用了,否则不合法。
本章对这三种引用着重介绍。
在开发NDK程序时,javah工具分析编译完成的使用了声明了原生代码的Java类文件(*.class),自动生成符合JNI规定的原生函数声明。如:
private native int func(int i);
经过javah命令分析后,自动生成的头文件包含其对应的声明为:
JNIEXPORT jint JNICALL Java_com_example_func_MainActivity_func(JNIEnv *, jobject, jint);
在Eclipse下,可以使用宏和系统环境变量等构件自动的工具,只需要点击这个自制的工具就能完成javah的命令。但是在Android Studio中,我目前还没有找到这种方法,只能打开Android Studio自带的Terminal,手动输入命令。假定我们创建的项目为Func, 在Android Studio的工作目录下,该项目的Java类的目录路径为Func/app/src/main/java/com/example/func/MainActivity.java,JNI代码的目录路径为Func/app/src/main/jni/func.cpp,Java类生成的类文件(*.class)路径为Func/app/build/intermediates/classes/debug/com/example/func/,Android SDK的支持jar包的路径在我的Mac上为/Users/my_name/Library/Android/sdk/platforms/android-21/android.jar,所以,进入Func/app/目录,执行javah命令:
javah -classpath ./build/intermediates/classes/debug/com/example/func:/Users/my_name/Library/Android/sdk/platforms/android-21/android.jar -jni -d ./src/main/jni -force com.example.func.MainActivity
命令中的参数都是上面假定的,根据个人的情况做相应的调整。其中
-classpath参数说明后面的字符串指定了本项目Java类文件(*.class)以及Java中使用到的如Activity等Android定义的组建的类文件的路径。
./build/intermediates/classes/debug/com/example/func:/Users/my_name/Library/Android/sdk/platforms/android-21/android.jar
第二章以一个非常简单的HelloWorld示例演示了使用JNI技术Java调用C,Java程序只是单纯的调用了C程序,C程序不接受任何参数(除了必须的JNIEnv和jobject外),也不提供返回值。但是,真实项目中很少有机会碰到这么简单的使用JNI的情况,大部分情况是C代码接受参数,也有返回值。这样就涉及到了两种语言之间不同类型的转换。如Java中int与C的int可能定义不同,String与char不同,数组也不同,因此,下面仍然结合示例程序,讲一下两种语言如何类型转换。在下面的讲解中,不会重复出现第二章中已经讲过的命令使用方法和配置环境变量等,如果需要请看第二章内容。本章只会贴出Java和C的源代码,并讲解其中的知识点,javac,javah,gcc等命令的用法不再出现。
本章使用到的函数可以在JNI函数列表页面详细了解函数原型、参数解释、返回值及注意事项等。本章只对函数的使用方法和重要的地方做介绍。
这个示例程序中,原生方法的原型为:String getLine(String prompt);。Java提供一个字符串,调用getLine函数,该函数打印这个字符串,并从终端获取用户输入,返回给Java程序。结合这个例子,可以讲解Java与C转换字符串类型和基本类型。
本文讲解一个简单的示例程序:在Java程序中调用原生方法,打印Hello World。让读者大概了解JNI编程的步骤。
对于本系列所有的示例程序,作者均在终端(Terminal)下编写、编译、调试和配置完成的。大家根据个人习惯进行编程。不过我还是说一下本人的编程环境:
1.Mac系统(之前用的Linux,不久前换了Mac,两个系统差不多),Windows系统没有实践过。
2.在执行命令之前,必须确定系统中已经安装了JDK最新版本,gcc和g++等工具。
3.编辑器是终端vim,大家根据习惯可以使用IDE或记事本等。
4.编译器在终端下用命令javac、javah、gcc/g++。
5.所有的配置(基本上是环境变量的设置)均在终端下完成,图形界面没有实践过。
下面提到的内容是用于达成共识:
1.执行命令时,我会这么写:$javac HelloWorld.java。其中’$’是终端命令提示符,不用用户输入,”javac HelloWorld.java”是需要用户输入的命令。