本文主内容:
1、 Android NDK 安装
2、 安装Cygwin与使用NDK编译
3、 在Eclipse中集成C/C++开发环境CDT
4、 安装Sequoyah插件
5、 JNI编译环境配置
本文建立在已经完成Android开发环境搭建的基础上。其基础环境至少需要包含以下内容:
1、 JDK
2、 Eclipse
3、 Android SDK and ADT
可以参考我之前的“Android开发环境搭建”。
一、Android NDK 安装与配置
下载Android NDK。下载地址:http://developer.android.com/tools/sdk/ndk/index.html
下载后解压缩到你的工作目录,例如:D:\Java\android-ndk-r8,结果如下图:
注意:1.samples下面包含几个实例开发演示项目,第一次接触NDK开发,建议先从示例开始。
docs内是技术文档,英语能力强的可以研究研究。
2.ndk 文件夹存放的路径中的文件夹里面不要有空格,否则在配置环境变量中 出现问题。
二、安装Cygwin与使用NDK编译
由于NDK开发大都涉及到C/C++在GCC环境下编译、运行,所以在Windows环境下,需要用Cygwin模拟Linux编译环境。
下载:
Cygwin的下载地址:http://www.cygwin.com/
点击右上角的“setup.exe”即可下载。
安装:
第一步:运行setup.exe程序,直接点击Next进入下一步。
第二步:选择安装方式。第一次可以采用Direct Connection在线下载安装,如有现成的离线包,可以选择离线安装(Install from Local Directory)。
第三步:选择安装目录。比如D:\Java\Cygwin,注意此目录是指Cygwin最终的安装目录,不是下载文件暂存目录。
第四步:设置本地包暂存路径。暂存目录默认是放到setup.exe的同级目录下,建议放到指定的文件夹,如D:\Cygwin_install_file。安装完成后把这个文件夹打包备份,以后再配置时不用重新下载。
第五步:设置网络连接方式。这个目前河蟹没爬过来,选第一个即可。
第六步:选择下载站点地址。据说国内163站点的速度不错,我也是用的这个。
第七步:等待加载安装项载入,选择安装项。点击Devel-Default,使之变成Devel-Install,展开后可以看到其下的子项被选中了(网上多数教程都说选中某12个包,找起来太坑爹了,直接全下载了吧,全选多了150M左右)。此界面其他设置都不用动。
第八步:等待下载完成。下载完成时间决定于你选择的安装包数量及网络连接速度,安装我安装的版本,约983M,下载完成后会自动安装到上文设置的安装目录,安装也要时间的,总时间较长,去吃个饭没啥问题。
提醒:第四步的备份建议,尽量去做。如果有备份,第二步中选择离线安装。
验证:
运行安装目录下的“Cygwin.bat”,第一次运行时,它会自动创建用户信息,用户信息存放在“.\Cygwin\home”中。
在运行“Cygwin.bat”打开的命令行窗口输入:“cygcheck -c cygwin”命令,会打印出当前Cygwin的版本和运行状态,如果status是ok的话,则cygwin运行正常。
分别输入:“make –v”和,“gcc –v”命令如果检测成功,会有make和gcc相关版本信息打印出来。
设置NDK路径:(/cygdrive/E/develop_tools/android-ndk-r9d-windows-x86_64/android-ndk-r9d) 这种格式样式的哦。
在windows的系统环境变量中添加NDK的路径。使用“/cygdrive/d/Java/android-ndk-r8”这种Linux风格路径,如果使用Windows下的“D:\Java\android-ndk-r8”,Cygwin在编译时会发出警告。
将NDK环境变量值加入path中去:
.;%JAVA_HOME%\lib;%JAVA_HOME%\bin;%ANDROID_HOME%\tools;%ANDROID_HOME%\platform-tools;%NDK%;%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\;C:\Program Files\TortoiseSVN\bin;C:\Program Files (x86)\Rational\common
运行Cygwin命令行,可以直接使用此环境变量,当然也可以手动的cd到该目录:
使用NDK编译程序:
现在我们用安装好的NDK来编译一个NDK提供的sample程序hello-jni(我的目录位于:D:\Java\android-ndk-r8\samples\hello-jni)。
第一步:运行Cygwin,配置环境变量后可输入“cd $ndk/samples/hello-jni/”,未配置则输入命令“cd /cygdrive/d/java/android-ndk-r8/samples/hello-jni”,进入到“hello-jni”工程目录。
第二步:编译。输入命令“$ndk/ndk-build”命令即可编译。ndk-build是调用ndk的编译程序。
关于下面的错误,我没遇到,但是前人有总结,记录如下:
错误:Android NDK: Host 'awk' tool is outdated。
解决方法:打开目录“D:\Java\android-ndk-r8\prebuilt\windows\bin\”,删除awk.exe(为保险起见请先备份)。
第三步:到”…/hello-jni/libs/armeabi“目录下看有没有生成的.so文件,如果有,你的ndk就运行正常啦!
导入NDK的hello-jni示例到Eclipse中:
第一步:在Eclipse中新建一个Android工程HelloJni。在Create Android Project时勾选“Create project from existing source”,Location中填“D:\Java\android-ndk-r8\samples\hello-jni” (注意:在选择API level时需要选择1.5或更高的版本)。
第二步:直接以Android Aplication运行。这里要注意,你之前在使用NDK编译程序时要把这个hello-jni编译过并产生了.so文件,此处才能运行起来。
三、在Eclipse中集成C/C++开发环境CDT
CDT的安装可以使我们在一个工程中,同时开发基于C/C++的Native代码和基于Java语言的壳,之后的配置还可以使得一次编译两部分代码。
下载:
下载地址:http://www.eclipse.org/cdt/downloads.php
说明:
Eclipse C/C++ IDE Indigo SR2:是带CDT的Eclipse开发环境。
p2 software repository:在线安装的地址。(似乎被河蟹爬了)
cdt-master-8.0.2.zip:这个是CDT的离线安装包。(推荐使用这个,保留离线包,复用)
离线安装:
Eclipse -> Help -> Install New Software,点击add。Name:随意,建议使用好记的“CDT_版本”。Location:点击Archive,定位到下载的“cdt-master-8.0.2.zip”文件。
错误:
如果Location的下面出现“Duplicate location”错误,请到Window -> preferences -> Install/Update -> Avaliable Software Site中找到该条,remove之。
或者安装cdt路径:
在Eclipse菜单help里面,安装CDT,网址为http://download.eclipse.org/tools/cdt/releases/galileo
验证:
安装完成后,在Eclispe中新建一个项目,如果出现了C/C++项目,则表明CDT插件安装成功了。
四、安装Sequoyah插件
Sequoyah插件用于设置Android工程对Native开发的支持。
官方网址:http://www.eclipse.org/sequoyah/downloads/
在线安装:
官网提供了用于在线安装的Update Site地址以及安装包的下载地址。貌似安装包才1M多,在线安装也没被河蟹爬过,直接在线安装了。勾选全部列出的可安装项并完成安装。
Location:http://download.eclipse.org/sequoyah/updates/2.0/
注意:
在安装界面不要勾选“Group items by category”复选框,默认是勾选的,出现了列表为空(There are no categorized items)的情况。
配置:
安装完Sequoyah插件后,为Android配置NDK路径。
在“window –> preferences ->Android -> 本机开发”中添加NDK的路径。
验证:
右键之前建立的“HelloJni”项目,在“Android Tools”选项中包含“Add Native Support…”选项即成功。
五、JNI编译环境配置
仍旧以之前建立的“HelloJni”为例,到目前为止,如果我们修改“/HelloJni/jni/hello-jni.c”文件,动态链接库libhello-jni.so文件却不会被重新编译生成。这是因为我们没有给JNI项目添加它需要的编译配置和依赖库。现在我们来配置它。
第一步:转换工程。点击“文件 -> 新建 -> 其他”(快捷键:Ctrl+N)。选择“C/C++”下的“Convert to a C/C++ Project(Adds C/C++ Nature)”。进入“下一步”。
第二步:选中你刚才建的“HelloJni”工程,下面左边选“Makefile project”右边选“Cygwin GCC”。确定后提示的“透视图”不清楚是什么,点击“是”即可。
第三步:在“HelloJni”工程上右键,选择“属性”。配置“C/C++ Build”和“C/C++ General -> Paths and Symbols”。
C/C++ Build:点击“C/C++ Build”,在右边的“Builder Settings”中去掉默认勾选的“Use default build command”复选框。设置Build command为“bash D:\Java\android-ndk-r8\ndk-build”。
C/C++ General -> Paths and Symbols:在Includes下add新的GNU C依赖路径。此“HelloJni”工程需要“D:\Java\android-ndk-r8\platforms\android-8\arch-arm\usr\include”即可,以后根据不同项目选择不同的依赖库。
验证:
将“/HelloJni/jni/hello-jni.c”中的字符串“Hello from JNI !”如改为“Hello JNI from Baron!”,运行后在模拟器上输出的字符串改变即说明配置成功。
目标:
利用NDK生成SO库,使用SO库进行JNI调用,在Android sdcard创建文件并写入数据。
工具:
NDK1.5 R1, android SDK1.5 R1, SDCARD, Eclipse , ADT 0.9, Eclipse Galileo for C/C++, Cygwin 1.5。
工具比较多,我是在Windows XP进行操作的,如果在Ubuntu或者其他的Linux系统下进行操作直接进入步骤2即可。请看步骤:
1. 安装Cygwin
关于Cygwin的安装请自行百度或者google,因为网速原因,建议大家使用本地安装包进行下载:http://www.gougou.com/search?search=cygwin&id=2 下载一个38.2M 即可,我用的就是这个版本。
2. 安装NDK
进入正题安装NDK,首先打开cygwin,然后使用cd命令进入你的NDK根目录,(不会用linux命令??Google去),然后运行:build/host-setup.sh
如果出现以上的信息,说明安装正确,如果出现GCC为找到,请输入gcc命令查看,如果出现toolchain未找到的信息,请试着修改build/host-setup.sh文件第一行代码,将:#!/bin/sh 改成#!/bin/bash, 在Ubuntu上这个是需要修改的
安装成后,我们使用make命令试试编译自带的程序,输入make APP=hello-jni,hello-jni为apps下hello-jni文件夹的名称,这个命令会先找到apps\hello-jni下地Application.mk文件,然后找到source\samples\hello-jni这个目录,然后找到Android.mk这个文件中的配置信息进行编译。看图:
因为我的已经编译过了,使用使用 make APP=hello-jni -B进行重新编译
出现上面的信息,说明编译成功,so文件在apps/hello-jni/project/libs/armeabi文件夹下。
3. 书写java native接口
下面我们进入实战的阶段,写代码。
建立一个android1.5的项目,NDK不支持1.5以下的版本
JNI.java的代码:
JniTest.java的代码:
4. 生成JNI 头文件
下面生成JNI头文件啦,使用javah命令,对JNI..java这个文件生成,别搞错啦!
使用cmd进入android项目的bin目录中的classes下面(E:\workspace\jniDemo\bin\classes),然后执行 javah -classpath . -jni cc.androidos.jni.JNI这个命令。别忘记包名!(^_^)
生成了一个cc_androidos_jni_JNI.h
打开看看:
里面都是c语言的头文件声明
5. 书写C代码,写入数据
下面进行c语言代码的阶段,不会C的,可以去学习一下。
我这里使用eclipse for c/c++的进行开发,简单的代码大家可以使用记事本。
工程如下:
cc_androidos_jni_JNI.h内容不变
cc_androidos_jni_JNI.c的内容:
在这里的代码会在运行的时候向sdcard写入androidos.cc.txt文件。然后写入aaaa数据。
6. 编译SO文件
进行编译so文件:
在NDK的apps目录下面创建一个myjni文件夹,在myjni文件夹中创建一个Application.mk文件,文件的内容为:
# PanoramaGL library
# Version 0.1
# Copyright (c) 2010 Javier Baez <javbaezga@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#support armeabi armeabi-v7a x86 mips some products
APP_ABI := armeabi armeabi-v7a x86 mips
注意:support armeabi armeabi-v7a x86 mips some products 意思是支持 armeabi armeabi-v7a x86 mips 四个平台的芯片厂商,一般的我们只写第一个armeabi
在NDKsources目录下创建myjni文件夹,在myjni文件中创建一个Android.mk文件,然后将cc_androidos_jni_JNI.h和cc_androidos_jni_JNI.c文件copy进来:
Android.mk中的内容如下:
# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)
注意:LOCAL_MODULE := hello-jni 生成的so文件名称
LOCAL_SRC_FILES := hello-jni.c 要编译成so文件的c文件,一般这个文件就是JNI文件的支持文件.
在myjni文件夹里面包括了c文件和h文件,Android.mk和Application.mk文件,一般的将myjni文件夹放到android项目project下面,同 src,res文件夹同目录即可.
编译:在cygwin环境下,用命令进入androd project下面,执行 $NDK_HOME/ndk-build即可,生成的so文件就在libs下面存在了
7. 加入到Android项目中进行运行
将apps\myjni\libs整个文件夹copy到android项目下:
运行android项目使用adb shell进行查看:
进入sdcard查看数据。
注意:sdcard具有system的权限,如果你的目录是root权限的,那么是不能写成功的,除非是真机越权,模拟器是不成功的,并且在Eclipse logcat下会有DEBUG信息出现。
/linux下面配置
三:配置NDK环境变量
1、 首先找到cygwin的安装目录,找到一个home\<你的用户名>\.bash_profile文件,我的 是:E:\cygwin\home\Administrator\.bash_profile,(注意:我安装的时候我的home文件夹下面神马都没有,解决 的办法:首先打开环境变量,把里面的用户变量中的HOME变量删掉,在E:\cygwin\home文件夹下建立名为Administrator的文件夹(是用户名),然后把E:\cygwin\etc\skel\.bash_profile拷贝到该文件夹下)。
2、 打开bash_profile文件,添加NDK=/cygdrive/<你的盘符>/<androidndk 目录> 例 如:NDK=/cygdrive/e/android-ndk-r5
export NDK
NDK这个名字是随便取的,为了方面以后使用方便,选个简短的名字,然后保存
3、打开cygwin,输入cd $NDK,如果输出上面配置的/cygdrive/e/android-ndk-r5信息,则表明环境变量设置成功了。
四:用NDK来编译程序
1、 现在我们用安装好的NDK来编译一个简单的程序吧,我们选择ndk自带的例子hello-jni,我的位于E:\android-ndk-r5\samples\hello-jni(根据你具体的安装位置而定),
2、 运行cygwin,输入命令cd /cygdrive/e/android-ndk-r5/samples/hello-jni,进入到E:\android-ndk-r5\samples\hello-jni目录。
3、 输入$NDK/ndk-build,执行成功后,它会自动生成一个libs目录,把编译生成的.so文件放在里面。($NDK是调用我们之前配置好的环境变量,ndk-build是调用ndk的编译程序)
4、 此时去hello-jni的libs目录下看有没有生成的.so文件,如果有,你的ndk就运行正常啦!
//开发资料、、、、、、、、、、、、、、、
JNI层的代码其实比较简单,难点是要掌握c++和java数据类型的转换,明白java程序是运行在虚拟机中的,特别是函数并不是可以互相调用,jni中的内存概念并没有暴露给java虚拟机进程等。
一. java参数类型和jni本地参数类型对照
基本类型
Java 类型 jni本地类型 描述
boolean jboolean C/C++ unsigned 8 bits
byte jbyte C/C++ signed 8 bits
char jchar C/C++ unsigned 16 bits
short jshort C/C++ signed 16 bits
int jint C/C++ signed 32 bits
long jlong C/C++ signed 64 bits
float jfloat C/C++ 32位浮点型
double jdouble C/C++ 64位浮点型
void void N/A
表一
对象类型
Object jobject 任何Java对象,或者没有对应
java类型的对象
Class jclass class对象
String jstring 字符串对象
表二
数组类型
boolean[] jbooleanArray 布尔型数组 unsigned
byte[] jbyteArray 比特型数组 signed
char[] jcharArray 字符型数组 unsigned
short[] jshortArray 短整型数组 signed
int[] jintArray 整型数组 signed
long[] jlongArray 长整型数组 signed
float[] jfloatArray 浮点型数组
double[] jdoubleArray 双浮点型数组
Object[] jobjectArray 任何对象的数组
表三
JNI引用类型与Java的对应关系如下树层次图:
1. java中的返回值void和JNI中的void是完全对应的。
2. java中的基本数据类型(boolean ,byte , char ,short ,int,long,float,double八种)在JNI中对应的数据类型只要在前面加上j就对应了(jboolean ,jbyte , jchar ,jshort ,jint,jlong,jfloat,jdouble)。
JNI中还有个Java中没有的jsize,定义如下:
typedef jint jsize;
其实jsize整型是用来描述基本指标和大小。
3. java中的对象,包括类库中定义的类、接口以及自定义的类接口,都对应于JNI中的jobject。
4. java中基本数据类型的数组对应与JNI中的j<type>array类型。(type就是上面说的8种基本数据类型)
5. java中对象的数组对应于JNI中的jobjectArray类型。(在java中一切对象、接口以及数组都是对象)
http://blog.csdn.net/xyz_lmn/article/details/6956003
http://www.cnblogs.com/liangwind/archive/2009/08/18/1925515.html
Java基本类型的精度
java 的基本数据类型是不存在有符号和无符号这种概念的. JAVA中的基本数据类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或者操作系统的改变而改变。
简单类型 字节数 范围/精度
float 4 32位IEEE754单精度
double 8 64位IEEE754双精度
byte 1 -128到127
short 2 -32,768到32,767
int 4 -2,147,483,648到2,147,483,647
long 8 -9,223,372,036,854,775,808到9,223,372,036,854,775,807
char 2 整个Unicode字符集
boolean 1 True或者false
像byte 是范围是 -128到127, 你想要变为 0到255 怎么办, 跟 0XFF 做与运算 就可以了.
如 byte bb , 如果你想赋值它值 255, 那是不行的, 就算赋值了, bb 的值也是 255 对 256 求模后的值 -1
如果你只是想取他 0到255 的值, 还是很简单的,
bb & 0XFF , 如 bb = -1, 那 bb & 0XFF 结果为 255,
这个与运算后的结果会隐式转换为int 类型的, 因为 byte 放不下了.
与运算 还是很快的, 比加减法还快的.
http://www.stuhack.com/biancheng/java/35169.html
二.jni层使用java的基本类型数据
对于上面八种基本的数据类型boolean ,byte , char ,short ,int,long,float,double,jni层的c++代码可以用强制直接转换成对于长度的c/c++类型数据。
如:unsigned char tmp = (unsigned char) m_jboolean;
unsigned short tmp = (unsigned short)m_jchar;
或者同长度类型的数据就可以直接赋值的,int tmp = m_jint;
三.jni层对数组的使用
JNI通过JNIEnv提供的操作Java数组的功能。它提供了两个函数:一个是操作java的简单型数组的,另一个是操作对象类型数组的。
1. 操作java的简单型数组
因为速度的原因,简单类型的数组作为指向本地类型的指针暴露给本地代码。因此,它们能作为常规的数组存取。这个指针是指向实际的Java数组或者Java数组的拷贝的指针。另外,数组的布置保证匹配本地类型。
为了存取Java简单类型的数组,你就要要使用GetXXXArrayElements函数(见表三),XXX代表了数组的类型。这个函数把Java数组看成参数,返回一个指向对应的本地类型的数组的指针。
完整的函数族见下表:
函数 Java 数组类型 本地类型
GetBooleanArrayElements jbooleanArray jboolean
GetByteArrayElements jbyteArray jbyte
GetCharArrayElements jcharArray jchar
GetShortArrayElements jshortArray jshort
GetIntArrayElements jintArray jint
GetLongArrayElements jlongArray jlong
GetFloatArrayElements jfloatArray jfloat
GetDoubleArrayElements jdoubleArray jdouble
当你对数组的存取完成后,要确保调用相应的ReleaseXXXArrayElements函数,参数是对应Java数组和GetXXXArrayElements返回的指针。如果必要的话,这个释放函数会复制你做的任何变化(这样它们就反射到java数组),然后释放所有相 关的资源。
例如:
static jint com_ginwave_fs_com_HWRC_GetRecogRange(JNIEnv* env, jclass clazz, jintArray Handle)
{
unsigned long *pHandle = NULL;
int ret = 0;
jint *tmpHandle = env->GetIntArrayElements(Handle, 0);
pHandle = (unsigned long *)tmpHandle;
ret = (int)HWRC_GetRecogRange(pHandle);
env->ReleaseIntArrayElements(Handle, tmpHandle, 0);
return r
}
获取数组的长度:
jint theArrayLength = env->GetArrayLength(Frame);
创建一个新的函数数组簇如下:
NewBooleanArray
NewByteArray
NewCharArray
NewShortArray
NewIntArray
NewLongArray
NewFloatArray
NewDoubleArray
参数为数组长度,如:
jbyte *list;
jbyteArray byteArray = NULL;
byteArray = env->NewByteArray(len);
if (byteArray)
env->SetByteArrayRegion(byteArray, 0, len, list);
关于函数簇GetXXXArrayRegion和SetXXXArrayRegion,其中XXX为基本类型。
例如:
env->SetByteArrayRegion(buffer, destOffset, count, (const jbyte *)address + srcOffset);
Setxxx的方向是从JNI层往java层传递;
env->GetByteArrayRegion(buffer, srcOffset, count, (jbyte *)address + destOffset);
而Getxxx的方向则是数据从java层向jni层传递。
这里是获取简单型数组中的数据供jni或者下层使用,如果需要在jni层设置java
中对于的简单型数组的话,就需要使用到接下来讲到的对象类型的一些操作。
总结下,有以下几簇函数:
GetArrayLength
NewXXXArray
GetXXXArrayElements
ReleaseXXXArrayElements
GetXXXArrayRegion
SetXXXArrayRegion
对于数据,暂时遇到这些函数了。。。
2. 操作java对象类型数据
Java对象做为引用被传递到本地方法中,所有这些Java对象的引用都有一个共同的父类型jobject(相当于java中的Object类是所有类的父类一样)。
1). string对象
从java程序中传过去的String对象在本地方法中对应的是jstring类型,jstring类型和c中的char*不同,所以如果你直接当做char*使用的话,就会出错。因此在使用之前需要将jstring转换成为c/c++中的char*,这里使用JNIEnv的方法转换。
static jstring com_prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)
{
char buf[128];
const char *str = (*env)->GetStringUTFChars(env, prompt, 0);
printf("%s", str);
env->ReleaseStringUTFChars(prompt, str);
...
}
这里使用GetStringUTFChars方法将传进来的prompt(jstring类型)转换成为UTF-8的格式,就能够在本地方法中使用了。
注意:在使用完你所转换之后的对象之后,需要显示调用ReleaseStringUTFChars方法,让JVM释放转换成UTF-8的string的对象的空间,如果不显示的调用的话,JVM中会一直保存该对象,不会被垃圾回收器回收,因此就会导致内存溢出。
下面是访问String的一些方法:
GetStringUTFChars 将jstring转换成为UTF-8格式的char*
GetStringChars 将jstring转换成为Unicode格式的char*
ReleaseStringUTFChars 释放指向UTF-8格式的char*的指针
ReleaseStringChars 释放指向Unicode格式的char*的指针
NewStringUTF 创建一个UTF-8格式的String对象
NewString 创建一个Unicode格式的String对象
GetStringUTFLength 获取UTF-8格式的char*的长度
GetStringLength 获取Unicode格式的char*的长度
提供给两个jstring和char *互相转换的函数:
/* c/c++ string turn to java jstring */
static jstring strTojstring(JNIEnv* env, const unsigned char* pStr)
{
int strLen = strlen((const char*)pStr);
jclass jstrObj = env->FindClass("java/lang/String");
jmethodID methodId = env->GetMethodID(jstrObj, "", "([BLjava/lang/String;)V");
jbyteArray byteArray = env->NewByteArray(strLen);
jstring encode = env->NewStringUTF("utf-8");
env->SetByteArrayRegion(byteArray, 0, strLen, (jbyte*)pStr);
return (jstring)env->NewObject(jstrObj, methodId, byteArray, encode);
}
//check ok!
/* java jstring turn to c/c++ string */
static char* jstringTostr(JNIEnv* env, jstring jstr)
{
char* pStr = NULL;
jclass jstrObj = env->FindClass("java/lang/String");
jstring encode = env->NewStringUTF("utf-8");
jmethodID methodId = env->GetMethodID(jstrObj, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray byteArray = (jbyteArray)env->CallObjectMethod(jstr, methodId, encode);
jsize strLen = env->GetArrayLength(byteArray);
jbyte *jBuf = env->GetByteArrayElements(byteArray, JNI_FALSE);
if (jBuf > 0)
{
pStr = (char*)malloc(strLen + 1);
if (!pStr)
{
return NULL;
}
memcpy(pStr, jBuf, strLen);
pStr[strLen] = 0;
}
env->ReleaseByteArrayElements(byteArray, jBuf, 0);
return pStr;
}
// check ok!
2) 访问java对象
JNI提供的另外一个功能是在本地代码中使用Java对象。通过使用合适的JNI函数,你可以创建Java对象,get、set 静态(static)和 实例(instance)的域,调用静态(static)和实例(instance)函数。JNI通过ID识别域和方法,一个域或方法的ID是任何处理域和方法的函数的必须参数。
下表列出了用以得到静态(static)和实例(instance)的域与方法的JNI函数。每个函数接受(作为参数)域或方法的类,它们的名称,符号和它们对应返回的jfieldID或jmethodID。
函数 描述
GetFieldID 得到一个实例的域的ID
GetStaticFieldID 得到一个静态的域的ID
GetMethodID 得到一个实例的方法的ID
GetStaticMethodID 得到一个静态方法的ID
下面以一个例子来说明用法:上下层之间需要传递一个或者多个结构体值。
c/c++结构体定义:
typedef struct tagTHWFrame{
short left;
short top;
short width;
short height;
} THWFrame;
当然在java层也需要定义一个匹配的类出来:
public class THWFrame{
public short left;
public short top;
public short width;
public short height;
}
注意貌似这里只能定义成public的。
下面是jni层相关的代码,主要思想是对java对应类对象的属性域获得ID值后一个一个访问。
/* int HWRC_SetInputBox( unsigned long *pHandle, const THWFrame *pFrame ); */
static void ObjectTOTHWFrameStruct(JNIEnv* env, jobjectArray Frame, THWFrame *pFrame, int index)
{
jobject obj = env->GetObjectArrayElement(Frame, index);
jclass cls = env->GetObjectClass(obj);
jfieldID left = env->GetFieldID(cls, "left", "S");
pFrame[index].left = (short)env->GetShortField(obj, left);
jfieldID top = env->GetFieldID(cls, "top", "S");
pFrame[index].top = (short)env->GetShortField(obj, top);
jfieldID width = env->GetFieldID(cls, "width", "S");
pFrame[index].width = (short)env->GetShortField(obj, width);
jfieldID height = env->GetFieldID(cls, "height", "S");
pFrame[index].height = (short)env->GetShortField(obj, height);
}
static jint com_ginwave_fs_com_HWRC_SetInputBox(JNIEnv* env, jclass clazz,
jintArray Handle, jobjectArray Frame)
{
unsigned long *pHandle = NULL;
THWFrame *pFrame = NULL;
int frame_len = 0;
int ret = 0;
jint *tmpHandle = env->GetIntArrayElements(Handle, 0);
pHandle = (unsigned long *)tmpHandle;
jint theArrayLength = env->GetArrayLength(Frame);
frame_len = theArrayLength;
pFrame = (THWFrame *)malloc( sizeof(THWFrame) * frame_len );
for( int i = 0; i < frame_len; i++ ){
ObjectTOTHWFrameStruct(env, Frame, pFrame, i);
}
ret = HWRC_SetInputBox(pHandle, (const THWFrame *)pFrame);
env->ReleaseIntArrayElements(Handle, tmpHandle, 0);
free(pFrame);
frame_len = NULL;
return ret;
}
// {"HWRC_SetInputBox", "([I[Ljava/com/ginwave/fs/com/THWFrame)I" , (void *)com_ginwave_fs_com_HWRC_SetInputBox },
// check ok!
/* int HWRC_GetInputBox( unsigned long *pHandle, THWFrame *pFrame ); */
static void THWFrameStructTOObject(JNIEnv* env, jobjectArray Frame, THWFrame *pFrame, int index)
{
jobject obj = env->GetObjectArrayElement(Frame, index);
jclass cls = env->GetObjectClass(obj);
jfieldID left = env->GetFieldID(cls, "left", "S");
env->SetShortField(obj, left, (short)pFrame[index].left);
jfieldID top = env->GetFieldID(cls, "top", "S");
env->SetShortField(obj, top, (short)pFrame[index].top);
jfieldID width = env->GetFieldID(cls, "width", "S");
env->SetShortField(obj, width, (short)pFrame[index].width);
jfieldID height = env->GetFieldID(cls, "height", "S");
env->SetShortField(obj, height, (short)pFrame[index].height);
}
static jint com_ginwave_fs_com_HWRC_GetInputBox(JNIEnv* env, jclass clazz,
jintArray Handle, jobjectArray Frame)
{
unsigned long *pHandle = NULL;
THWFrame *pFrame = NULL;
int frame_len = 0;
int ret = 0;
jint *tmpHandle = env->GetIntArrayElements(Handle, 0);
pHandle = (unsigned long *)tmpHandle;
jint theArrayLength = env->GetArrayLength(Frame);
frame_len = theArrayLength;
pFrame = (THWFrame *)malloc( sizeof(THWFrame) * frame_len );
ret = HWRC_GetInputBox(pHandle, pFrame);
for( int i = 0; i < frame_len; i++ ){
THWFrameStructTOObject(env, Frame, pFrame, i);
}
env->ReleaseIntArrayElements(Handle, tmpHandle, 0);
free(pFrame);
frame_len = NULL;
return ret;
}
// {"HWRC_GetInputBox", "([I[Ljava/com/ginwave/fs/com/THWFrame)I" , (void *)com_ginwave_fs_com_HWRC_GetInputBox },
// check ok!
其中,比较难理解的应该是函数的签名了,下面是他们的一些规则:
这个数组的类型是JNINativeMethod,定义如下:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
第一个变量name是Java中函数的名字。
第二个变量signature,用字符串是描述了函数的参数和返回值
第三个变量fnPtr是函数指针,指向C函数。
其中比较难以理解的是第二个参数,例如
"()V"
"(II)V"
"(Ljava/lang/String;Ljava/lang/String;)V"
实际上这些字符是与函数的参数类型一一对应的。
"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();
"(II)V" 表示 void Func(int, int);
具体的每一个字符的对应关系如下
字符 Java类型 C类型
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
数组则以"["开始,用两个字符表示
[I jintArray int[]
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
[D jdoubleArray double[]
[J jlongArray long[]
[Z jbooleanArray boolean[]
objects对象 Lfully-qualified-class-name; L类名
Arrays数组 [array-type [数组类型
方法参数或者返回值为java中的对象时,必须以“L”加上其路径,不过此路径必须以“/”分开,自定义的对象也使用本规则,不在包中时直接“L”加上类名称。比如说 java.lang.String为“java/lang/String”,com.nedu.jni.helloword.Student为"com /nedu/jni/helloword/Student"
方法参数或者返回值为数组时类型前加上[,例如[I表示 int[],[[[D表示 double[][][],即几维数组就加几个[。
JNI函数中始终包含两个必要的参数:JNIEnv* env, jclass clazz
JNIEnv *――它是一个接口指针,用于定位函数表中的函数!
在JNI规范中一般称 为 “Interface Pointer”。看到这儿好像和过程调用很类似了!是的,JNI中的操作过程,就是面向过程的!后面的jobject是 一个指向该类的指针,类似与C语言中的this。这个第二个参数是变化的,当该方法为类的实例方法时该参数为jobject;当该方法为类方法 (即静态方法)时该参数为jclass,指向该类的class。
通过ndk编程来得到jni层头文件的时候,这第二个参数对于staic方法,生成出来的就是jclass,而对于非staic方法,生成出来的就是jobject。
从上图可知,jobject包含了其实概括了所有的java类型,也就是说,像上图中的非jobject类型的数据,在传递参数的时候都可以以jobject类型传递下去。比如说,如果要java中要传递一个二(多)维int数组下去,就可以包装成jobjectArray传下去,只不过对应的签名要弄成[[I了。
对于访问java对象的方法
在本地方法中调用Java对象的方法的步骤:
①.获取你需要访问的Java对象的类:
jclass cls = (*env)->GetObjectClass(env, obj); // FindClass(“android/util/log”)
使用GetObjectClass方法获取obj对应的jclass。 // 直接搜索类名,需要是static修饰的类。
②.获取MethodID:
jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "(I)V");
// GetStaticMethodID(…) , 获取静态方法的ID
使用GetMethdoID方法获取你要使用的方法的MethdoID。其参数的意义:
env-->JNIEnv
cls-->第一步获取的jclass
"callback"-->要调用的方法名
"(I)V"-->方法的Signature, 签名同前面的JNI规则。
③.调用方法:
(*env)->CallVoidMethod(env, obj, mid, depth);
// CallStaticIntMethod(….) , 调用静态方法
使用CallVoidMethod方法调用方法。参数的意义:
env-->JNIEnv
obj-->通过本地方法穿过来的jobject
mid-->要调用的MethodID(即第二步获得的MethodID)
depth-->方法需要的参数(对应方法的需求,添加相应的参数)
注:这里使用的是CallVoidMethod方法调用,因为没有返回值,如果有返回值的话使用对应的方法,在后面会提到。
CallVoidMethod CallStaticVoidMethod
CallIntMethod CallStaticVoidMethod
CallBooleanMethod CallStaticVoidMethod
CallByteMethod CallStaticVoidMethod
…
其实jni中还有很多很多的接口函数这里没有列举,可以直接参考源码:
$ find frameworks/base type d -name jni
./voip/jni
./rfid/jni
./freestylus/jni
./native/graphics/jni
./drm/jni
./tests/BrowserTestPlugin/jni
./services/jni
./packages/TtsService/jni
./media/jni
./media/libdrm/mobile1/include/jni
./media/libdrm/mobile1/src/jni
./graphics/jni
./core/jni
./opengl/tests/gl_jni/jni
./opengl/tests/gl2_jni/jni
./opengl/tests/gldual/jni
这么多jni目录都可以参考,其中主要是core/jni目录了。
四、关于异常
异常接口有:
jniThrowException(env, "java/lang/RuntimeException", "Method called after release()");
env->ThrowNew(env->FindClass("java/io/IOException"),"CWJLog Error, IOException");
doThrow(env, "java/lang/IllegalStateException", msg);
使用Throw,自己构造(没用过)
jclass clazz = env->FindClass("java/io/IOException");
jmethodID methodId = env->GetMethodID(clazz, "<init>", "()V");
jthrowable throwable = env->NewObject(clazz, methodId);
env->Throwthrowable);
参考网址:
http://blog.csdn.net/xyz_lmn/article/details/6959545
Android JNI入门第三篇——jni头文件分析
http://blog.csdn.net/xyz_lmn/article/details/6966259
Android JNI入门第四篇——Android.mk文件分析
http://blog.csdn.net/xyz_lmn/article/details/7017420
Android JNI开发提高篇
http://blog.csdn.net/xyz_lmn/article/details/6956003
Android JNI入门第二篇——Java参数类型与本地参数类型对照
http://wenku.baidu.com/view/e9e28ca1b0717fd5360cdc18.html
JNI入门
http://www.ibm.com/developerworks/cn/java/j-jni/
使用 Java Native Interface 的最佳实践
http://helloxuweifu.iteye.com/blog/1168647
http://blog.csdn.net/kangyaping/article/details/6584027
JNI函数调用大全
http://newfaction.net/2010/11/30/java-jni-getfieldid-and-getmethodid-and-parameter-description.html
java jni GetFieldID 和 GetMethodID 以及参数的说明
http://hi.baidu.com/spmno/blog/item/7d4d764ea78a6809b3de0588.html
jni中使用数组的几个方法
http://xxw8393.blog.163.com/blog/static/3725683420107109411366/
JNI 返回结构体参数
http://www.cnblogs.com/nicholas_f/archive/2010/11/30/1892124.html
JNI中java类型与C/C++类型对应关系
http://blog.csdn.net/sunny09290/article/details/6884994
JNI数据类型
http://www.cnblogs.com/liangwind/archive/2009/08/18/1925515.html
jni --c/c++ 数据类型、数组、对象
http://www.cnblogs.com/diyunpeng/archive/2009/09/24/1573296.html
Java有符号数与无符号数
http://www.stuhack.com/biancheng/java/35169.html
Java的基本数据类型是无符号的