NDK开发环境搭建_r8

本文主内容:

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,结果如下图:

NDK开发环境搭建_r8_第1张图片 

注意:1.samples下面包含几个实例开发演示项目,第一次接触NDK开发,建议先从示例开始。

      docs内是技术文档,英语能力强的可以研究研究。

2.ndk 文件夹存放的路径中的文件夹里面不要有空格,否则在配置环境变量中 出现问题。

二、安装Cygwin与使用NDK编译

由于NDK开发大都涉及到C/C++在GCC环境下编译、运行,所以在Windows环境下,需要用Cygwin模拟Linux编译环境。

下载:

Cygwin的下载地址:http://www.cygwin.com/

NDK开发环境搭建_r8_第2张图片

点击右上角的“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左右)。此界面其他设置都不用动。

NDK开发环境搭建_r8_第3张图片

第八步:等待下载完成。下载完成时间决定于你选择的安装包数量及网络连接速度,安装我安装的版本,约983M,下载完成后会自动安装到上文设置的安装目录,安装也要时间的,总时间较长,去吃个饭没啥问题。

提醒:第四步的备份建议,尽量去做。如果有备份,第二步中选择离线安装。

验证:

运行安装目录下的“Cygwin.bat”,第一次运行时,它会自动创建用户信息,用户信息存放在“.\Cygwin\home”中。

在运行“Cygwin.bat”打开的命令行窗口输入:“cygcheck -c cygwin”命令,会打印出当前Cygwin的版本和运行状态,如果status是ok的话,则cygwin运行正常。

分别输入:“make –v”和,“gcc –v”命令如果检测成功,会有make和gcc相关版本信息打印出来。

NDK开发环境搭建_r8_第4张图片

设置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开发环境搭建_r8_第5张图片

         将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开发环境搭建_r8_第6张图片 

使用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开发环境搭建_r8_第7张图片 

         第二步:编译。输入命令“$ndk/ndk-build”命令即可编译。ndk-build是调用ndk的编译程序。

关于下面的错误,我没遇到,但是前人有总结,记录如下:

错误:Android NDK: Host 'awk' tool is outdated。

解决方法:打开目录“D:\Java\android-ndk-r8\prebuilt\windows\bin\”,删除awk.exe(为保险起见请先备份)。

NDK开发环境搭建_r8_第8张图片 

         第三步:到”…/hello-jni/libs/armeabi“目录下看有没有生成的.so文件,如果有,你的ndk就运行正常啦!

NDK开发环境搭建_r8_第9张图片 

导入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的离线安装包。(推荐使用这个,保留离线包,复用)

NDK开发环境搭建_r8_第10张图片

离线安装:

         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插件安装成功了。

NDK开发环境搭建_r8_第11张图片 

四、安装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的路径。

NDK开发环境搭建_r8_第12张图片 

验证:

         右键之前建立的“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)”。进入“下一步”。

NDK开发环境搭建_r8_第13张图片 

         第二步:选中你刚才建的“HelloJni”工程,下面左边选“Makefile project”右边选“Cygwin GCC”。确定后提示的“透视图”不清楚是什么,点击“是”即可。

NDK开发环境搭建_r8_第14张图片 

         第三步:在“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”。

NDK开发环境搭建_r8_第15张图片

         C/C++ General ->  Paths and Symbols:在Includes下add新的GNU C依赖路径。此“HelloJni”工程需要“D:\Java\android-ndk-r8\platforms\android-8\arch-arm\usr\include”即可,以后根据不同项目选择不同的依赖库。 

NDK开发环境搭建_r8_第16张图片 

验证:

         将“/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-jniappshello-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文件:

NDKapps目录下面创建一个myjni文件夹,在myjni文件夹中创建一个Application.mk文件,文件的内容为:

# PanoramaGL library
# Version 0.1
# Copyright (c) 2010 Javier Baez
#
# 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.hcc_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整个文件夹copyandroid项目下:

运行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/<你的盘符>/ndk 目录> 例    如: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中的jarray类型。(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, "", "()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的基本数据类型是无符号的

 

 


你可能感兴趣的:(NDK开发环境搭建_r8)