Android NDK使用方法.doc_第1页
Android NDK使用方法.doc_第2页
Android NDK使用方法.doc_第3页
Android NDK使用方法.doc_第4页
Android NDK使用方法.doc_第5页
已阅读5页,还剩8页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Android NDK使用方法目前Android NDK只能编译出动态库.so文件,并不是能生成.apk文件,所以要调用底层的NDK程序 必须分两个部分单独编写。 本文档将按照开发人员编码顺序介绍如何使用NDK。本文使用的NDK版本为1.6r1版本, 因为不想配置交叉编译环境 直接使用了linux版本的NDK在linux机器上编译。1. 首先让NDK环境正确配置完成 然后才可以添加自己的代码编译。下载Android 1.6r1 NDK、解压到某一目录 并将环境变量ANDROID_NDK_ROOT 设为你解压的NDK所在路径 比如我现在的NDK放在/home/once下名字为android-ndk 那么就需要用export ANDROID_NDK_ROOT=/home/once/android-ndk这条命令来设置环境变量.运行./build/host-setup.sh更新系统工具链依赖 这点没什么好讲的 如果第一步配置对了 那么这一步NDK会根据你的配置生成对应的工具链. 建立你自己的工作目录在apps目录下 比如我的工程会放在/home/once/android-ndk/apps/下面 然后拷贝一个例子工程里的Application.mk到你的目录 并修改里面的参数为你的工程对应的值 APP_PROJECT_PATH := $(call my-dir)/projectAPP_MODULES := test decrypt /主要是这里要改成你工程的依赖关系 我这里有2个互相依赖的模块 就是一个库依赖于另一个库 我这里的是decrypt依赖test库 一般2个库的情形比较常见 就是我们的ndk接口依赖一个标准c语言被交叉编译出来的库 为了保持工具链一致 当然最好的方法就是把你自己的c库源码放进来编译新建一个文件夹名为project 这个文件夹将用来存放你的jni工程 以及java工程 Java工程用eclipse辅助完成即可 这里我们只讲JNI工程。我们创立一个jni文件夹在project下 然后同样从例子工程的jni文件夹下面拷贝一个Android.mk文件过来 将里面的参数改对 如下LOCAL_MODULE := test /这个是你的库的名字LOCAL_SRC_FILES := test.c test.h /这个是你用来生成库的源码文件之所以会生成动态库是因为下面这句:include $(BUILD_SHARED_LIBRARY)如果你的工程还需要依赖其他的模块就需要指定如下参数:LOCAL_SHARED_LIBRARIES := Libtest 这个libtest其实就是我们上面的module名字 所以上面的Application.mk得写好依赖关系 否则这个库编译的时候会找不到test库还有就是如果你想在你的NDK的c代码中打印android log那么你得再依赖一个系统库:LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib llog以上几个步骤全部做完了就可以进入第5点 编译一下你的ndk库看看有没错误的了.向上层目录 进入到ndk的根目录,运行make APP=hello-jni(你的项目名称 V1打印详细 B强制重编) 比如我这里的工程为email 那么如果我要编译我的工程 我就用这条命令 make APP=email 如果没有错误 那么基本上说明你的c代码初步编译无误。6.用eclipse创建一个Android Project,这里不多做论述。完成这个工程之后 将整个工程复制到$ANDROID_NDK_ROOT /apps/你的工程目录/project/下, 然后进入ndk的根目录运行命令编译整个工程, 这个时候ndk会帮你把jni下面的代码编译成库放到你的java工程下面对应的目录中去。这时候用eclipse导入这个android工程, 你会发现你的android工程下的libs下面有了你需要平台命名的文件夹里面包含你需要的库。这时候可以直接用eclipse编译并生成apk文件 安装到机器或者模拟器调试无误整个工程就初步完成了。以上讲的都是简单的工程构建步骤 在写NDK的时候还是有很多东西要注意的。NDK相比较JNI方式确实简化了很多步骤 但是简化的步骤NDK帮你自动生成就需要你严格按照规则来做。比如你的NDK里面的函数名 你必需严格的按照Java_com。 这样的名字命名 com以及后面就是你的调用ndk的java类文件名最后一个短横线之后的为你的native函数名。如果这里写的不正确 或者参数不正确整个库的引导就会报异常。TextView tv = new TextView(this);tv.setText( stringFromJNI() ); /如果调用失败会抛出java.lang.UnsatisfiedLinkError异常 setContentView(tv); /注意下面的native关键字 publicnativeString stringFromJNI(); publicnativeString unimplementedStringFromJNI(); static System.loadLibrary(hello-jni); /导入hello-jni库 因为这里会出很多的异常 所以如果你不希望调用ndk的时候抛出异常影响java层用户体验 你可以将native函数再封装一层 然后捕获异常 例如:try result = nativeDecrypt(crypedFileName, decryptedFileName, passwd); catch (final UnsatisfiedLinkError ue) Constant.LOG(decrypt UnsatisfiedLinkError); catch (final Exception e) Constant.LOG(decrypt Exception);return result;导入库的地方也可以封装:static try System.load(/data/data/com.app.email /lib/libtest.so);System.load(/data/data/com.app.emai /lib/libdecrypt.so); catch (SecurityException se) Constant.LOG(System.load SecurityException); catch (Exception e) Constant.LOG(System.load Exception);关于ndk如何编写 可以参考Sun的Java本地接口 (JNI) 规范下面贴出几种常用的编写方式 如下:示例 1 - 传递参数在第一个示例中,我们将三个常用参数类型传递给本地函数:String、int和boolean。本例说明在本地 C 代码中如何引用这些参数。public class MyNative public void showParms( String s, int i, boolean b ) showParms0( s, i , b ); private native void showParms0( String s, int i, boolean b ); static System.loadLibrary( MyNative ); 请注意,本地方法被声明为专用的,并创建了一个包装方法用于公用目的。这进一步将本地方法同代码的其余部分隔离开来,从而允许针对所需的平台对它进行优化。static子句加载包含本地方法实现的 DLL。下一步是生成 C 代码来实现 showParms0 方法。此方法的 C 函数原型是通过对 .class 文件使用 javah 实用程序来创建的,而 .class 文件是通过编译 MyNative.java 文件生成的。这个实用程序可在 JDK 中找到。下面是 javah 的用法:javac MyNative.java(将 .java 编译为 .class) javah -jni MyNative(生成 .h 文件)这将生成一个 MyNative.h 文件,其中包含一个本地方法原型,如下所示:/* * Class: MyNative * Method: showParms0 * Signature: (Ljava/lang/String;IZ)V */JNIEXPORT void JNICALL Java_MyNative_showParms0 (JNIEnv *, jobject, jstring, jint, jboolean);第一个参数是调用 JNI 方法时使用的 JNI Environment 指针。第二个参数是指向在此 Java 代码中实例化的 Java 对象 MyNative 的一个句柄。其他参数是方法本身的参数。请注意,MyNative.h 包括头文件 jni.h。jni.h 包含 JNI API 和变量类型(包括jobject、jstring、jint、jboolean,等等)的原型和其他声明。本地方法是在文件 MyNative.c 中用 C 语言实现的:#include #include MyNative.hJNIEXPORT void JNICALL Java_MyNative_showParms0 (JNIEnv *env, jobject obj, jstring s, jint i, jboolean b) const char* szStr = (*env)-GetStringUTFChars( env, s, 0 ); printf( String = %sn, szStr ); printf( int = %dn, i ); printf( boolean = %sn, (b=JNI_TRUE ? true : false) ); (*env)-ReleaseStringUTFChars( env, s, szStr );JNI API,GetStringUTFChars,用来根据 Java 字符串或 jstring 参数创建 C 字符串。这是必需的,因为在本地代码中不能直接读取 Java 字符串,而必须将其转换为 C 字符串或 Unicode。有关转换 Java 字符串的详细信息,请参阅标题为 NLS Strings and JNI 的一篇论文。但是,jboolean 和 jint 值可以直接使用。MyNative.dll 是通过编译 C 源文件创建的。下面的编译语句使用 Microsoft Visual C+ 编译器:cl -Ic:jdk1.1.6include -Ic:jdk1.1.6includewin32 -LD MyNative.c -FeMyNative.dll其中 c:jdk1.1.6 是 JDK 的安装路径。MyNative.dll 已创建好,现在就可将其用于 MyNative 类了。可以这样测试这个本地方法:在 MyNative 类中创建一个 main 方法来调用 showParms 方法,如下所示:public static void main( String args ) MyNative obj = new MyNative(); obj.showParms( Hello, 23, true ); obj.showParms( World, 34, false ); 当运行这个 Java 应用程序时,请确保 MyNative.dll 位于 Windows 的 PATH 环境变量所指定的路径中或当前目录下。当执行此 Java 程序时,如果未找到这个 DLL,您可能会看到以下的消息:java MyNative Cant find class MyNative这是因为 static 子句无法加载这个 DLL,所以在初始化 MyNative 类时引发异常。Java 解释器处理这个异常,并报告一个一般错误,指出找不到这个类。如果用 -verbose 命令行选项运行解释器,您将看到它因找不到这个 DLL 而加载 UnsatisfiedLinkError 异常。如果此 Java 程序完成运行,就会输出以下内容:java MyNative String = Hello int = 23 boolean = true String = World int = 34boolean = false示例 2 - 返回一个值本例将说明如何在本地方法中实现返回代码。将这个方法添加到 MyNative 类中,这个类现在变为以下形式:public class MyNative public void showParms( String s, int i, boolean b ) showParms0( s, i , b ); public int hypotenuse( int a, int b ) return hyptenuse0( a, b ); private native void showParms0( String s, int i, boolean b ); private native int hypotenuse0( int a, int b ); static System.loadLibrary( MyNative ); /* 测试本地方法 */ public static void main( String args ) MyNative obj = new MyNative(); System.out.println( obj.hypotenuse(3,4) ); System.out.println( obj.hypotenuse(9,12) ); 公用的 hypotenuse 方法调用本地方法 hypotenuse0 来根据传递的参数计算值,并将结果作为一个整数返回。这个新本地方法的原型是使用 javah 生成的。请注意,每次运行这个实用程序时,它将自动覆盖当前目录中的 MyNative.h。按以下方式执行 javah:javah -jni MyNative生成的 MyNative.h 现在包含 hypotenuse0 原型,如下所示:/* * Class: MyNative * Method: hypotenuse0 * Signature: (II)I */JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0 (JNIEnv *, jobject, jint, jint);该方法是在 MyNative.c 源文件中实现的,如下所示:#include #include #include MyNative.hJNIEXPORT void JNICALL Java_MyNative_showParms0 (JNIEnv *env, jobject obj, jstring s, jint i, jboolean b) const char* szStr = (*env)-GetStringUTFChars( env, s, 0 ); printf( String = %sn, szStr ); printf( int = %dn, i ); printf( boolean = %sn, (b=JNI_TRUE ? true : false) ); (*env)-ReleaseStringUTFChars( env, s, szStr );JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0 (JNIEnv *env, jobject obj, jint a, jint b) int rtn = (int)sqrt( (double)( (a*a) + (b*b) ) ); return (jint)rtn;再次请注意,jint 和 int 值是可互换的。使用相同的编译语句重新编译这个 DLL:cl -Ic:jdk1.1.6include -Ic:jdk1.1.6includewin32 -LD MyNative.c -FeMyNative.dll现在执行 java MyNative 将输出 5 和 15 作为斜边的值。示例 3 - 静态方法您可能在上面的示例中已经注意到,实例化的 MyNative 对象是没必要的。实用方法通常不需要实际的对象,通常都将它们创建为静态方法。本例说明如何用一个静态方法实现上面的示例。更改 MyNative.java 中的方法签名,以使它们成为静态方法:public static int hypotenuse( int a, int b ) return hypotenuse0(a,b); . private static native int hypotenuse0( int a, int b );现在运行 javah 为hypotenuse0创建一个新原型,生成的原型如下所示:/* * Class: MyNative * Method: hypotenuse0 * Signature: (II)I */JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0 (JNIEnv *, jclass, jint, jint);C 源代码中的方法签名变了,但代码还保持原样:JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0 (JNIEnv *env, jclass cls, jint a, jint b) int rtn = (int)sqrt( (double)( (a*a) + (b*b) ) ); return (jint)rtn;本质上,jobject 参数已变为 jclass 参数。此参数是指向 MyNative.class 的一个句柄。main 方法可更改为以下形式:public static void main( String args ) System.out.println( MyNative.hypotenuse( 3, 4 ) ); System.out.println( MyNative.hypotenuse( 9, 12 ) ); 因为方法是静态的,所以调用它不需要实例化 MyNative 对象。本文后面的示例将使用静态方法。示例 4 - 传递数组本例说明如何传递数组型参数。本例使用一个基本类型,boolean,并将更改数组元素。下一个示例将访问 String(非基本类型)数组。将下面的方法添加到 MyNative.java 源代码中:public static void setArray( boolean ba ) for( int i=0; i ba.length; i+ ) bai = true; setArray0( ba ); . private static native void setArray0( boolean ba );在本例中,布尔型数组被初始化为 true,本地方法将把特定的元素设置为 false。同时,在 Java 源代码中,我们可以更改 main 以使其包含测试代码:boolean ba = new boolean5; MyNative.setArray( ba ); for( int i=0; i GetBooleanArrayElements( env, ba, 0 ); jsize len = (*env)-GetArrayLength(env, ba); int i=0; / 更改偶数数组元素 for( i=0; i ReleaseBooleanArrayElements( env, ba, pba, 0 );指向布尔型数组的指针可以使用 GetBooleanArrayElements 获得。数组大小可以用 GetArrayLength 方法获得。使用 ReleaseBooleanArrayElements 方法释放数组。现在就可以读取和修改数组元素的值了。jsize 声明等价于 jint(要查看它的定义,请参阅 JDK 的 include 目录下的 jni.h 头文件)。示例 5 - 传递 Java String 数组本例将通过最常用的非基本类型,Java String,说明如何访问非基本对象的数组。字符串数组被传递给本地方法,而本地方法只是将它们显示到控制台上。MyNative 类定义中添加了以下几个方法:public static void showStrings( String sa ) showStrings0( sa ); private static void showStrings0( String sa );并在main方法中添加了两行进行测试:String sa = new String Hello, world!, JNI, is, fun. ; MyNative.showStrings( sa );本地方法分别访问每个元素,其实现如下所示。JNIEXPORT void JNICALL Java_MyNative_showStrings0 (JNIEnv *env, jclass cls, jobjectArray sa) int len = (*env)-GetArrayLength( env, sa ); int i=0; for( i=0; i GetObjectArrayElement(env, sa, i); jstring str = (jstring)obj; const char* szStr = (*env)-GetStringUTFChars( env, str, 0 ); printf( %s , szStr ); (*env)-ReleaseStringUTFChars( env, str, szStr ); printf( n );数组元素可以通过 GetObjectArrayElement 访问。在本例中,我们知道返回值是 jstring 类型,所以可以安全地将它从 jobject 类型转换为 jstring 类型。字符串是通过前面讨论过的方法打印的。有关在 Windows 中处理 Java 字符串的信息,请参阅标题为 NLS Strings and JNI 的一篇论文。示例 6 - 返回 Java String 数组最后一个示例说明如何在本地代码中创建一个字符串数组并将它返回给 Java 调用者。MyNative.java 中添加了以下几个方法:pub

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论