Android JNI开发入门
来源:互联网 发布:java string转数组 编辑:程序博客网 时间:2024/05/16 05:08
1 前言
JNI作为JAVA与C/C++库的一种交互方式,在Android系统中被广泛使用,本文将与您一道学习Android中JNI的使用。
因为作者是JNI的初学者,JNI编程经验有限,如有遗漏或有误的地方,请大家帮忙指出,感谢!
2 ANDROID JNI简介
JNI是Java NativeInterface的缩写,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。
Android系统底层都是C/C++实现的,上层提供的API都是Java的,但是Android系统不允许一个纯粹使用C/C++的应用程序出现,它要求必须是通过Java代码嵌入Native C/C++——即通过JNI的方式来使用本地(Native)代码。因此JNI对Android底层开发人员非常重要。比如:AndroidAPI多媒体接口MediaPlayer类,其实底层通过JNI调用libmedia库。
当然,JNI也有弊端。Java层使用JNI与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少保证本地代码能工作在任何Java虚拟机环境下。
3 ANDROID JNI编写
Android JNI的编写主要需完成两个部分:Java编写的Android应用程序;C/C++编写的本地lib库。下面我们以经典的helloworld应用为例,学习AndroidJNI。
3.1 编译环境
Java层的应用程序使用JDK编译即可,我们使用Eclipse进行开发,请自行安装配置JDK,ADT。
Native C/C++代码是和硬件相关的,必须要用交叉编译器,编译成特定硬件可执行代码。它的编译环境有两种: Android NDK编译;Android源码编译环境。有关NDK编译环境,下面会有单独一章进行介绍,本章我们使用Android源码编译环境编译Native代码。
3.2 Java编写的Android应用程序
我们首先用Java编写HelloWorld应用程序(APK),这个代码很简单。代码如下:
packagecom.jnitest;
importandroid.app.Activity;
importandroid.os.Bundle;
public class HelloWorld extends Activity {
private static finalString nativeLibName ="helloworld";
static {
System.loadLibrary(nativeLibName);
}
private native String printJNI();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fullscreen);
System.out.println("Callback: "+printJNI());
}
}
这个HelloWorld Activity非常简单,只是调用JNI接口printJNI()返回一些信息,再由Java层输出。我们需要关注一下的是printJNI()的声明,有一个native的关键字,说明他是一个用native代码实现的函数,需要用JNI调用Native代码。
另外注意static代码段,这段代码意思是当类HelloWorld第一次被加载的时候,加载libhelloworld.so(请注意这里写的是库名称,在Linux中共享库名为xxx共享库,文件存在形式为libxxx.so。所以loadLibrary的参数不是libhelloworld.so,而是helloworld。如果写错误了将会加载库失败,将会收到异常)。
编写完成后,生成APK文件。
3.3 C语言实现helloworld 库
接下来我们需要来完成Native代码部分了,这里需要强调一下,Android JNI实现中为C/C++提供了两套不同的API,调用的时候需要注意,否则非常有可能你会收到一些libc库的崩溃信息。
3.3.1 C/C++ Native函数头文件获取
Native层的函数原型与Java层声明的形式不太一样,我们一般使用javah工具来生成标准的Native函数头文件。
打开终端进入HelloWorld工程的bin\classes目录,运行以下命令:
javah -jni com.jnitest.HelloWorld
您将在classes目录下得到一个com_jnitest_HelloWorld.h 文件,这里包含有printJNI接口的C/C++声明。这个声明肯定正确,如果你把printJNI接口声明写错了,HelloWorld将找不到printJNI接口,然后产生崩溃。
注: javah是一个逆向工具,所以请确保Build了最新的Java工程。
3.3.2 C/C++ Native函数源文件编写
接下来,我们创建com_jnitest_HelloWorld.c文件,以实现com_jnitest_HelloWorld.h 头文件声明的函数,C文件内容如下:
#include <jni.h>
#define LOG_TAG "HelloWorld"
#include "com_jnitest_HelloWorld.h"
/* Native interface, it will be call in java code */
JNIEXPORT jstring JNICALLJava_com_jnitest_HelloWorld_printJNI(JNIEnv *env, jobject obj)
{
return (*env)->NewStringUTF(env,"C lib JNI: Hello World!");
}
/* This function will be call when the library first be load.
* You can do some initin the libray. return which version jni it support.
*/
jint JNI_OnLoad(JavaVM* vm, void*reserved)
{
void *venv;
if ((*vm)->GetEnv(vm, (void**)&venv,JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
return JNI_VERSION_1_4;
}
请注意Java_com_jnitest_HelloWorld_printJNI函数的名字,这是个符合JNI规范的名字。Java虚拟机在com.jnitest.HelloWorld类调用printJNI接口的时候,会自动找到这个C实现的Native函数调用。
你可能注意到这个名字非常的长,作为一个函数名它非常有可能不是一个好的选择。JNI API允许你提供一个函数映射表,注册给Jave虚拟机,这样Java虚拟机就可以用函数映射表来调用相应的函数,就可以不必通过函数名来查找需要调用的函数了。这样你的函数名也可以随便定义了(可以定义最能表现函数功能的函数名),这个将会在helloworld共享库的C++实现中演示。但是Android系统中,还是推荐用JNI标准的函数名定义的。
JNI_OnLoad函数是JNI规范定义的,当共享库第一次被加载的时候会被回调,这个函数里面可以进行一些初始化工作,比如注册函数映射表,缓存一些变量等,最后返回当前环境所支持的JNI环境。本例只是简单的返回当前JNI环境。此处,JNI_OnLoad函数其实可以不实现。
3.3.3 编写Android.mk文件
接下来编写Android.mk文件,创建之,并输入:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=com_jnitest_HelloWorld.c
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
LOCAL_MODULE:= libhelloworld
LOCAL_SHARED_LIBRARIES:= libutils
LOCAL_PRELINK_MODULE := false
LOCAL_LDLIBS += -lm -llog
LOCAL_CFLAGS +=-Wimplicit-function-declaration
include $(BUILD_SHARED_LIBRARY)
这里面有几个标签需要说明:
1) LOCAL_C_INCLUDES说明包含的头文件,这里需要包含JNI头文件。
2) LOCAL_MODULE当前模块的名称
3) LOCAL_SHARED_LIBRARIES当前模块需要依赖的共享库,因为在hellowold中我们调用Android打印系统输出到logger,所以我们必须要依赖libutils库。
4) LOCAL_PRELINK_MODULE指明该模块是否被启动就加载。我们的helloworld库不需要prelink,所以置为false。
3.4 lib库的编译与部署
到此为止,我们完成了Native部分的编码工作。将C源文件与Android.mk文件置于同一文件夹下。建议在本地工程目录中,添加jni文件夹,将Native代码全部放于jni文件夹中(强烈建议您这样做)。
Android源码编译环境配置完成后,使用终端进入jni文件夹,输入:mm,进行编译,编译完成后将得到libhelloworld.so库(可能在您的Android源码的/out/……/system/lib文件夹下)。
libhelloworld.so库的部署方式有两种:
1) 置于目标Android平台的/system/lib/下;
2) 置于APK工程的libs/armeabi/下(若工程下没有这个路径要自行创建)。
使用第一种方式,可能会提示写保护,通过”adb mount”的方式,获取写权限。这种方式,Native库可以被其它APK使用。
使用第二种方式,ADT插件自动编译输出的.apk文件中已经包括.so文件了。这种方式下,只有此APK才能使用Native库。
部署完lib库后,安装运行我们已写好的Java APK, APK运行时,System.loadLibrary()就会加载libhelloworld.so库。
4 NDK编译环境
前文提到过,除了Android源码编译环境外,还可以使用NDK编译Native代码。
Android NDK :全称是NativeDeveloper Kit,是用于编译本地JNI源码的工具,为开发人员将本地方法整合到Android应用中提供了方便。事实上NDK和完整源码编译环境一样,都使用Android的编译系统——即通过Android.mk文件控制编译。NDK可以运行在Linux、Mac、Window(+cygwin)三个平台上。
4.1 下载NDK
网址:http://developer.android.com/sdk/ndk/index.html
目前最新版本是r10c,选择自己的编译平台版本下载,解压。
例如,我的编译平台是linux_64 下载完成后,使用如下命令解压:
$: chmod a+x android-ndk-r10c-linux-x86_64.bin
$: ./ android-ndk-r10c-linux-x86_64.bin
4.2 配置环境变量
从r7版本开始,Linux下直接解压就可以使用,在windows下cygwin也已经集成在NDK里面了,解压完配置下环境变量就可以使用了。
在~/.bash_profile(Linux下配置环境变量的文件)文件末尾加上:
exportNDK_HOME=/home/xxx /android-ndk-r10c/
exportPATH=$NDK_HOME:$PATH
*注意:第一行的路径参数是ndk解压后的根路径,请结合自己的实际路径,进行调整。
到这里 NDK 的环境就已经配置好了,在任意目录下都可使用 NDK 提供的工具, NDK 提供的主要的工具是 ndk-build,在任意目录下执行:ndk-build
执行结果:
Android NDK: Could not findapplication project directory !
Android NDK: Please definethe NDK_PROJECT_PATH variable to point to it.
/home/dh.yuan/softWare/android-ndk-r10c/build/core/build-local.mk:148:*** Android NDK: Aborting . Stop.
说明配置成功。
4.3 编译源码
如果您按照3.4节的建议将Native源码与mk文件放入了Native工程根目录的jni文件夹下,那么,通过终端进入Native工程根目录,执行 :
$ ndk-build
即可,NDK会自动将生成的目标库,放在工程根目录的\libs\armeabi\ 中。
而如果您的源码没有使用这种部署方式,则执行ndk-build后,可能会报以下错误:
Android NDK: Could not find application projectdirectory !
Android NDK: Please define the NDK_PROJECT_PATHvariable to point to it.
5 C++ 实现HELLOWORLD 库
前文已经提到,Android系统的Java虚拟机为C和C++实现两套不同的API,所以我们调用的时候需要注意这一点。由于Google并没有提供JNI的文档,我们调用的时候可以参考Android的jni.h文件,里面有C和C++的JNI函数原型。
在本例中Android应用程序不需要有任何变化,我们需要重新用C++实现HelloWorld共享库。创建com_jnitest_HelloWorld.cpp文件,并在文件中输入如下内容:
#include <jni.h>
#include "com_jnitest_HelloWorld.h"
#define NULL (0x00)
/*
* Class: com_jnitest_HelloWorld
* Method: printJNI
* Signature:()Ljava/lang/String;
*/
//JNIEXPORT jstring JNICALLJava_com_jnitest_HelloWorld_printJNI(JNIEnv *env, jobject obj)
JNIEXPORT jstring JNICALL HelloWorld_print(JNIEnv *env, jobjectobj)
{
return env->NewStringUTF("C++lib: Hello World!");
}
static const char *classPathName = "com/jnitest/HelloWorld";
staticJNINativeMethod methods[] = {
{"printJNI", "()Ljava/lang/String;", (void*)HelloWorld_print},
};
/*
* Register severalnative methods for one class.
*/
static int registerNativeMethods(JNIEnv* env, constchar* className,
JNINativeMethod*gMethods, int numMethods)
{
jclass clazz;
clazz =env->FindClass(className);
if (clazz == NULL) {
returnJNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0){
return JNI_FALSE;
}
return JNI_TRUE;
}
/*
* Register nativemethods for all classes we know about.
*
* returns JNI_TRUE onsuccess.
*/
static int registerNatives(JNIEnv* env)
{
if (!registerNativeMethods(env, classPathName,
methods,sizeof(methods) / sizeof(methods[0]))) {
return JNI_FALSE;
}
return JNI_TRUE;
}
typedef union {
JNIEnv* env;
void* venv;
} UnionJNIEnvToVoid;
/* This function will be call when the library first beloaded */
jint JNI_OnLoad(JavaVM* vm, void*reserved)
{
UnionJNIEnvToVoiduenv;
JNIEnv* env = NULL;
if (vm->GetEnv((void**)&uenv.venv, JNI_VERSION_1_4) !=JNI_OK) {
return -1;
}
env = uenv.env;
if (registerNatives(env) != JNI_TRUE) {
return -1;
}
return JNI_VERSION_1_4;
}
本例与上文3.3 C语言实现helloworld库对比有如下几点不同需要注意:
1、C和C++实现共享库调用不同JNIAPI。前面已经提到Android系统JNI为C和C++提供了两套不同的API。请仔细对比NewStringUTF,GetEnv函数,就会发现JNI API不同。
2、C++版的helloworld共享库提供了函数映射表。JNIAPI为了避免丑陋的函数名,提供了方法向Java虚拟机注册函数映射表。这样当Java调用Native接口的时候,Java虚拟机就可以不用根据函数名来决定调用哪个函数了,直接通过查询表格就可以找到需要调用的函数了。
3、我们注意到RegisterNatives第一个参数(C语言接口中是第二个参数)为调用该函数的Java类。这也和标准JNI函数名包含类名(包名和类名)的作用一样——声明那个Java类可以调用这个方法。
4、函数映射表的定义非常的怪异。关于JNINativeMethod数据与JNI数据类型,将在下一章介绍。
通过对比您可能也发现了,同样功能的库,C++的实现要比C加入更多的代码,并且C++中函数映射表的使用不是可选的,是必须的。原因在于,C编译器 与C++编译器 使用的函数名修饰规则不同。JNI可以通过标准函数名能找到C实现的共享库中的函数,但是用C++实现的却不行。所以C++实现的函数必须使用函数映射表进行注册,否则,即使编译通过了,在运行时也会出现以下错误:
Native method not found
下面是一个C++源码的Android.mk文件:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=com_jnitest_HelloWorld.cpp
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
LOCAL_MODULE:= libhelloworld
LOCAL_SHARED_LIBRARIES:= libutils
LOCAL_PRELINK_MODULE := false
LOCAL_LDLIBS += -lm -llog
LOCAL_CFLAGS +=-Wimplicit-function-declaration
include $(BUILD_SHARED_LIBRARY)
和前文3.3 C语言实现helloworld库的Android.mk文件相比只修改了一下源文件,没有什么可说。编译生成libhelloworld.so文件,运行APK,你将会得到和前文相同的结果。
6 JNINATIVEMETHOD数据结构
前面,我们在Native代码中看到不少jclass,jobject, jint一类,看似像Java的数据类型,实际又不是,以及函数映射表中用到的一个奇怪的JNINativeMethod结构,本章,我们就来探究一下这些数据结构/类型。
6.1 JNI 的数据类型
在C/C++里,编译器会很据所处的平台来为一些基本的数据类型分配长度,因此也就造成了平台不一致性,而这个问题在Java中则不存在,因为有JVM的缘故,所以Java中的基本数据类型在所有平台下得到的都是相同的长度,比如int的宽度永远都是32位。基于这方面的原因,Java和C/C++的基本数据类型就需要实现一些映射,保持一致性。基本映射表如下:
Java类型
本地类型
描述
boolean
jboolean
C/C++8位整型
byte
jbyte
C/C++带符号的8位整型
char
jchar
C/C++无符号的16位整型
short
jshort
C/C++带符号的16位整型
int
jint
C/C++带符号的32位整型
long
jlong
C/C++带符号的64位整型
float
jfloat
C/C++32位浮点型
double
jdouble
C/C++64位浮点型
Object
jobject
任何Java对象,或者没有对应java类型的对象
Class
jclass
Class对象
String
jstring
字符串对象
Object[]
jobjectArray
任何对象的数组
boolean[]
jbooleanArray
布尔型数组
byte[]
jbyteArray
比特型数组
char[]
jcharArray
字符型数组
short[]
jshortArray
短整型数组
int[]
jintArray
整型数组
long[]
jlongArray
长整型数组
float[]
jfloatArray
浮点型数组
double[]
jdoubleArray
双浮点型数组
6.2 JNINATIVEMETHOD数据结构
Andoird 中使用了一种不同于传统Java JNI的方式来定义其Native的函数。其中很重要的区别是Andorid使用了一种Java 和 C/C++ 函数的映射表数组,并在其中描述了函数的参数和返回值。这个数组的类型是JNINativeMethod,定义如下:
typedefstruct {
const char* name;
const char* signature;
void* fnPtr;
}JNINativeMethod;
Ø 第一个变量name是Java中声明的函数名字。
Ø 第二个变量signature,用字符串是描述了函数的参数和返回值。
Ø 第三个变量fnPtr是函数指针,指向C/C++函数。
其中比较难以理解的是第二个参数,例如
"()V"
"(II)V"
"(Ljava/lang/String;Ljava/lang/String;)V"
实际上这些字符是与函数的参数、返回值类型一一对应的:
"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示voidFunc();
"(II)V" 表示 voidFunc(int, int);
具体的每一个字符的对应关系如下
字符
本地类型
Java类型
V
void
void
Z
jboolean
boolean
I
jint
int
J
jlong
long
D
jdouble
double
F
jfloat
float
B
jbyte
byte
C
jchar
char
S
jshort
short
数组则以"["开始,用两个字符表示:
字符
本地类型
Java类型
[I
jintArray
int[]
[F
jfloatArray
float[]
[B
jbyteArray
byte[]
[C
jcharArray
char[]
[S
jshortArray
short[]
[D
jdoubleArray
double[]
[J
jlongArray
long[]
[Z
jbooleanArray
boolean[]
上面的都是基本类型。如果Java函数的参数是class,则以"L"开头,以";"结尾,中间用"/" 隔开的包及类名。而其对应的C/C++函数名的参数则为jobject。一个例外是String类,其对应的类为jstring:
字符
本地类型
Java类型
Ljava/lang/String;
jstring
String
Ljava/net/Socket;
jobject
Socket
如果JAVA函数位于一个嵌入类,则用$作为类名间的分隔符。
例如 "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z"
7 ANDROID JNI 编程进阶及参考
本章对JNI的Native回调与对象引用,作一些初步介绍,并提供部分参考文档,权当作抛砖引玉。
7.1 JNI Native 回调 JAVA层
JNI回调是指在C/C++代码中调用Java方法,作为Native层向Java层传递消息的一种方式。
我们以一个Native层调用Java层的方法,并传入String参数的例子,来说明回调的流程。
Java层,代码如下:
packagecom.jnitest;
importandroid.app.Activity;
importandroid.os.Bundle;
public class HelloWorld extends Activity {
private static finalString nativeLibName ="helloworld";
static {
System.loadLibrary(nativeLibName);
}
private native String printJNI();
private native voidnativeCallBack();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fullscreen);
nativeCallBack();
System.out.println("Javaget Native: " +printJNI());
}
public void javaCallBack(String text){
System.out.println("Nativecall back : " + text);
}
}
上面的代码中,与以前相比,声明了一个nativeCallBack()本地方法,提供了javaCallBack()方法供Native层的nativeCallBack()回调。通过对参数的打印,验证回调是否成功。
因为有新的本地方法,故头文件要重做(过程不再赘述),以下是用C++实现的Native源码:
#include <jni.h>
#include "com_jnitest_HelloWorld.h"
#define NULL (0x00)
/*
* Class: com_jnitest_HelloWorld
* Method: printJNI
* Signature:()Ljava/lang/String;
*/
//JNIEXPORT jstring JNICALLJava_com_jnitest_HelloWorld_printJNI(JNIEnv *env, jobject obj)
JNIEXPORT jstring JNICALL HelloWorld_print(JNIEnv *env, jobjectobj)
{
return env->NewStringUTF("C++lib: Hello World!");
}
/*
* Class: com_jnitest_HelloWorld
* Method: nativeCallBack
* Signature: ()V
*/
JNIEXPORT void JNICALL nativeCallBack
(JNIEnv *env, jobjectobj){
jclass cls =env->GetObjectClass(obj);
jmethodID callback =env->GetMethodID(cls,"javaCallBack","(Ljava/lang/String;)V");
jstring info =env->NewStringUTF("C++native call back.");
env->CallVoidMethod(obj,callback,info);
}
static const char *classPathName = "com/jnitest/HelloWorld";
staticJNINativeMethod methods[] = {
{"printJNI", "()Ljava/lang/String;", (void*)HelloWorld_print },
{"nativeCallBack", "()V", (void*)nativeCallBack},
};
/*
* Register severalnative methods for one class.
*/
static int registerNativeMethods(JNIEnv* env, constchar* className,
JNINativeMethod*gMethods, int numMethods)
{
jclass clazz;
clazz =env->FindClass(className);
if (clazz == NULL) {
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0){
return JNI_FALSE;
}
return JNI_TRUE;
}
/*
* Register nativemethods for all classes we know about.
*
* returns JNI_TRUE onsuccess.
*/
static int registerNatives(JNIEnv* env)
{
if (!registerNativeMethods(env, classPathName,
methods,sizeof(methods) / sizeof(methods[0]))) {
return JNI_FALSE;
}
return JNI_TRUE;
}
typedef union {
JNIEnv* env;
void* venv;
} UnionJNIEnvToVoid;
/* This function will be call when the library first beloaded */
jint JNI_OnLoad(JavaVM* vm, void*reserved)
{
UnionJNIEnvToVoiduenv;
JNIEnv* env = NULL;
if (vm->GetEnv((void**)&uenv.venv, JNI_VERSION_1_4) !=JNI_OK) {
return -1;
}
env = uenv.env;
if (registerNatives(env) != JNI_TRUE) {
return -1;
}
return JNI_VERSION_1_4;
}
以上代码,我们只需关注nativeCallBack函数:
JNIEXPORT void JNICALLnativeCallBack (JNIEnv *env, jobject obj){
// 获得类的引用
jclass cls =env->GetObjectClass(obj);
// 获得JAVA层的javaCallBack方法ID
jmethodIDcallback = env->GetMethodID(cls,"javaCallBack","(Ljava/lang/String;)V");
// New 一个String,携带需要传递给Java层的信息
jstring info = env->NewStringUTF("C++ native call back.");
// 调用 Java层的方法。最后第三个参数,其它是一个可变参数列表,输入的是被调用的Java方法的参数列表。
env->CallVoidMethod(obj,callback,info);
}
nativeCallBack()函数的注册,只需在JNINativeMethod结构体中添加:
{"nativeCallBack","()V", (void*)nativeCallBack },
至于为什么是这种形式,请参考 6.2 JNINATIVEMETHOD数据结构。
Android.mk文件无需更改。
以上便是一个简单的Native回调Java方式。
7.2 对象引用
JNI规范中关于引用部分是最难理解的,本节就简单介绍一下对象引用。
在JNI编程中,Native代码不能对Java虚拟机中对象的内存分布有任何假设。因为Java虚拟机可以根据自己的策略自己定义对象的内存布局。这就要求JNI规范有如下要求:
1) 如果要在Native代码中生成Java对象,则必须调用Java虚拟机的JNI接口来生成。
2) 对Java对象的操作也需要调用Java虚拟机的JNI接口来进行。
3) 在Native代码中,通过引用来访问Java对象。对象引用有Java虚拟机的JNI接口返回,比如:NewStringUTF函数,用utf8的字符串创建一个Java字符串对象,在Native代码中以jstring类型的引用来访问它(可参考上一节nativeCallBack函数中的”jstring info”)。
7.2.1 JNI引用的分类
JNI规范中定义了三种引用——局部引用(Local reference),全局引用(Global reference),弱全局引用(Weak global reference)。
这三种引用的生存期是不同的,全局引用和弱全局引用的生存期为创建之后,到程序员显式的释放他们。局部引用会被Java虚拟机在当前上下文(可以理解成Java程序调用Native代码的过程)结束之后自动释放。
全局引用和局部引用将会迫使Java虚拟机不会垃圾回收其指向的对象。而弱全局引用指向的对象可以被Java虚拟机垃圾回收。
每种引用都有自己的用途,比如当Native函数返回的时候,需要返回局部引用(和C语言的局部变量要区分开)。全局引用和弱全局引用在多线程之间共享其指向的对象等。
7.2.2 局部引用
当创建局部变量之后,将迫使Java虚拟机不对其指向的对象进行垃圾回收,直到Native代码显式调用了DeleteLocalRef删除局部引用。Native代码调用DeleteLocalRef显式删除局部引用之后,Java虚拟机就可以对局部引用指向的对象垃圾回收了。当Native代码创建了局部引用,但未显式调用DeleteLocalRef删除局部引用,并返回Java虚拟机的话,那么由虚拟机来决定什么时候删除该局部引用,然后对其指向的对象垃圾回收。程序员不能对Java虚拟机删除局部引用的时机进行假设。
比如,上一节C++代码中的nativeCallBack()函数中的”jstring info”变量就是一个局部变量,你不能认为nativeCallBack()函数调用完成后,“info”就马上被释放了。故,可以在函数的最后加入下面的代码显式删除它:
env->DeleteLocalRef(info);
局部引用仅仅对于java虚拟机当前调用上下文有效,不能够在多次调用上下文中共享局部引用。这句话也可以这样理解:局部引用只对当前线程有效,多个线程之间不能共享局部引用。局部引用不能用C语言的静态变量或者全局变量来保存,否则第二次调用的时候,将会产生崩溃。
所以,在Native方法中使用一个静态变量来保存一个局部引用,以便在随后的调用中使用该局部引用,这种方式是行不通的。
7.2.3 全局引用
在所有引用中,全局引用是最好理解的一个了。为什么呢?主要和C语言的全局变量非常相近。
上文提到局部引用大部分是通过JNI API返回而创建的,而全局引用必须要在Native代码中显式的调用JNI API NewGlobalRef来创建,创建之后将一直有效,直到显式的调用DeleteGlobalRef来删除这个全局引用。
全局引用和局部引用一样,可以防止其指向的对象被Java虚拟机垃圾回收。与局部引用只在当前线程有效不同的是全局引用可以在多线程之间共享(如果是多线程编程需要注意同步问题)。
7.2.4 弱全局引用
弱全局引用是非常让人迷惑的一个,主要是它的用法太怪异了。
弱全局引用和全局引用一样,需要显式的创建和销毁。创建调用NewWeakGlobalRef,销毁调用DeleteWeakGlobalRef。
与全局引用和局部引用能够阻止Java虚拟机垃圾回收其指向的对象不同,弱全局引用指向的对象随时都可以被Java虚拟机垃圾回收,所以使用弱全局变量的时候,要时刻记着:它所指向的对象可能已经被垃圾回收了。JNI API提供了引用比较函数IsSameObject,用弱全局引用和NULL进行比较,如果返回JNI_TRUE,则说明弱全局引用指向的对象已经被释放。需要重新初始化弱全局引用。
7.3 参考
以上只是对Android JNI的一个简单介绍,如果我们想深入学习JNI,首先需要先熟悉标准的JNI。推荐大家学习《Java Native Interface:Programmer’s Guide and Specification》,权威的标准JNI学习文档。
Google没有为Android JNI编程提供文档,但是网上有一篇非常好的文档——《JNI Examples forAndroid》。这篇文章中举了一个例子包含了Android JNI开发的方方面面,想深入学习Android JNI开发的朋友请仔细研读。
8 附录
本章我将我在Android JNI学习中使用到的代码列出来,以方便您快速学习。
8.1 HelloWorld.java
声明了两个本地方法:一个(printJNI)用来调用本地函数,另一个(nativeCallBack)用来回调Java层。使用两个Button触发这两个调用。
------------------------------------------------------------------以下源码------------------------------------------------------
package com.jnitest;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class HelloWorld extends Activity {
ButtonmNativeCallBtn = null;
ButtonmNativeCallBackBtn = null;
TextViewmTextView = null;
privatestatic final String nativeLibName = "helloworld";
static {
System.loadLibrary(nativeLibName);
}
privatenative String printJNI();
privatenative void nativeCallBack();
@Override
protectedvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fullscreen);
mNativeCallBtn = (Button)findViewById(R.id.native_call_button);
mNativeCallBackBtn=(Button) findViewById(R.id.native_callBack_button);
mTextView = (TextView)findViewById(R.id.Content_Text);
initView();
}
publicvoid javaCallBack(String text){
if(mTextView!= null){
System.out.println("javaCallBack:"+text);
mTextView.setText(text);
}
}
privatevoid initView(){
if(mNativeCallBtn!= null){
mNativeCallBtn.setOnClickListener(newOnClickListener() {
@Override
publicvoid onClick(View view) {
if(mTextView!= null){
System.out.println("Callback: "+printJNI());
mTextView.setText(printJNI());
}
}
});
}
if(mNativeCallBackBtn!= null){
mNativeCallBackBtn.setOnClickListener(newOnClickListener() {
@Override
publicvoid onClick(View view) {
if(mTextView!= null){
nativeCallBack();
}
}
});
}
}
}
8.2 activity_fullscreen.xml
Application布局文件,包括两个Button用于触发调用,一个TextView用于显示调用信息。
------------------------------------------------------------------以下源码------------------------------------------------------
<FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0099cc"
tools:context=".HelloWorld" >
<LinearLayout
android:id="@+id/fullscreen_content_controls1"
style="?buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@color/white"
android:orientation="vertical"
tools:ignore="UselessParent" >
<LinearLayout
android:id="@+id/fullscreen_content_controls2"
style="?buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@color/black_overlay"
android:orientation="horizontal"
tools:ignore="UselessParent" >
<Button
android:id="@+id/native_call_button"
style="?buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/clib_button" />
<Button
android:id="@+id/native_callBack_button"
style="?buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/cpluslib_button"/>
</LinearLayout>
<TextView
android:id="@+id/Content_Text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:gravity="center"
android:textIsSelectable="false"
android:textColor="@color/black_overlay"
android:ems="24" >
</TextView>
</LinearLayout>
</FrameLayout>
8.3 Android.mk
本地C/C++编译规则文件。此文件编译的是C++源文件,如需编译C源文件,更改LOCAL_SRC_FILES标签即可。
------------------------------------------------------------------以下源码------------------------------------------------------
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=com_jnitest_HelloWorld.cpp
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
LOCAL_MODULE:= libhelloworld
LOCAL_SHARED_LIBRARIES:= libutils
LOCAL_PRELINK_MODULE := false
LOCAL_LDLIBS += -lm -llog
LOCAL_CFLAGS +=-Wimplicit-function-declaration
include $(BUILD_SHARED_LIBRARY)
8.4 com_jnitest_HelloWorld.cpp
此本地代码实现了两个本地方法,并使用了函数映射表(C++必须)。
------------------------------------------------------------------以下源码------------------------------------------------------
#include <jni.h>
#define NULL (0x00)
#define LOG_TAG "HelloWorld"
#include"com_jnitest_HelloWorld.h"
#include <android/log.h>
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG_TAG,__VA_ARGS__)
/*
*Class: com_jnitest_HelloWorld
*Method: printJNI
*Signature: ()Ljava/lang/String;
*/
//JNIEXPORT jstring JNICALLJava_com_jnitest_HelloWorld_printJNI(JNIEnv *env, jobject obj)
JNIEXPORT jstring JNICALLHelloWorld_print(JNIEnv *env, jobject obj)
{
LOGI("Hello World From libhelloworld.so!");
return env->NewStringUTF("C++ lib: Hello World!");
}
/*
*Class: com_jnitest_HelloWorld
*Method: nativeCallBack
*Signature: ()V
*/
JNIEXPORT void JNICALL nativeCallBack
(JNIEnv *env, jobject obj){
LOGI("Native call back!");
jclass cls = env->GetObjectClass(obj);
jmethodID callback =env->GetMethodID(cls,"javaCallBack","(Ljava/lang/String;)V");
jstringinfo = env->NewStringUTF("C++ native call back.");
env->CallVoidMethod(obj,callback,info);
env->DeleteLocalRef(info);
}
static const char *classPathName ="com/jnitest/HelloWorld";
static JNINativeMethod methods[] = {
{"printJNI", "()Ljava/lang/String;",(void*)HelloWorld_print },
{"nativeCallBack", "()V", (void*)nativeCallBack },
};
/*
*Register several native methods for one class.
*/
static int registerNativeMethods(JNIEnv*env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
LOGE("registerNativeMethods: '%s' numMethods=[%d]",className,numMethods);
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
LOGE("Native registration unable to find class '%s'",className);
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
LOGE("RegisterNatives failed for '%s'", className);
return JNI_FALSE;
}
return JNI_TRUE;
}
/*
*Register native methods for all classes we know about.
*
*returns JNI_TRUE on success.
*/
static int registerNatives(JNIEnv* env)
{
if(!registerNativeMethods(env, classPathName,
methods, sizeof(methods) /sizeof(methods[0]))) {
return JNI_FALSE;
}
return JNI_TRUE;
}
typedef union {
JNIEnv* env;
void* venv;
} UnionJNIEnvToVoid;
/* This function will be call when thelibrary first be loaded */
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
UnionJNIEnvToVoid uenv;
JNIEnv* env = NULL;
LOGI("JNI_OnLoad!");
if (vm->GetEnv((void**)&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
LOGE("ERROR: GetEnv failed");
return -1;
}
env = uenv.env;
if (registerNatives(env) != JNI_TRUE) {
LOGE("ERROR: registerNatives failed");
return -1;
}
return JNI_VERSION_1_4;
}
8.5 com_jnitest_HelloWorld.c
此代码就是上一节C++代码的C语言版。
------------------------------------------------------------------以下源码------------------------------------------------------
#include <jni.h>
#define LOG_TAG "HelloWorld"
#define NULL (0x00)
#include"com_jnitest_HelloWorld.h"
#include <android/log.h>
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG_TAG,__VA_ARGS__)
/* Native interface, it will be call injava code */
//JNIEXPORT jstring JNICALLJava_com_jnitest_HelloWorld_printJNI(JNIEnv *env, jobject obj)
JNIEXPORT jstring JNICALLHelloWorld_printJNI(JNIEnv *env, jobject obj)
{
LOGI("Hello World From libhelloworld.so!");
return (*env)->NewStringUTF(env, "C lib JNI: HelloWorld!");
}
static const char *classPathName ="com/jnitest/HelloWorld";
static JNINativeMethod methods[] = {
{"printJNI", "()Ljava/lang/String;",(void*)HelloWorld_printJNI},
};
/*
*Register several native methods for one class.
*/
static int registerNativeMethods(JNIEnv*env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = (*env)->FindClass(env, className);
if (clazz == NULL)
return JNI_FALSE;
if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0)
{
LOGE("registernativers error");
return JNI_FALSE;
}
return JNI_TRUE;
}
/*
*Register native methods for all classes we know about.
*
*returns JNI_TRUE on success.
*/
static int registerNatives(JNIEnv* env)
{
if(!registerNativeMethods(env, classPathName, methods, sizeof(methods) /sizeof(methods[0]))) {
return JNI_FALSE;
}
return JNI_TRUE;
}
/* This function will be call when thelibrary first be load.
*You can do some init in the libray. return which version jni it support.
*/
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
LOGI("JNI_OnLoad!");
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("ERROR: GetEnv failed");
return -1;
}
if (registerNatives(env) != JNI_TRUE) {
LOGE("ERROR:registerNatives failed");
return -1;
}
return JNI_VERSION_1_4;
}
- Android JNI开发入门
- Android JNI开发入门
- Android JNI开发入门
- Android JNI开发入门
- Android JNI开发入门
- Android JNI开发入门
- Android JNI开发入门
- Android JNI 开发入门
- android jni 开发入门
- Android JNI开发入门
- Android JNI开发入门篇
- Android JNI开发入门篇
- Android JNI开发入门篇
- Android JNI开发入门之一
- Android JNI开发入门之一
- Android JNI开发入门之一
- Android JNI开发入门篇
- Android JNI开发入门之一
- 【重要】融资融券
- java HashMap遍历的三种方式以及效率对比
- werer
- 查询oracle数据库表空间的大小,已使用空间,剩余空间
- Python统计列表中元素出现的次数
- Android JNI开发入门
- hdu1015
- Leetcode NO.166 Fraction to Recurring Decimal
- 《APUE》第三章笔记(2)
- 苹果Xcode帮助文档阅读指南
- Oracle数据库表空间占满的解决方法 ,ORA-01691
- openwrt uhttpd进程
- 使用 cloc 统计代码行数
- 2014年年终总结