300 likes | 705 Views
ANDROID 应用开发专题 2 JNI 与 NDK 开发. 刘健培 北京邮电大学 ljp020993@gmail.com 2013.05. 本次内容. Android 应用程序专题 之 NDK NDK 概念 NDK 开发 流程 JNI 原理 NDK 应用 示例. 参考资料. http://developer.android.com/tools/sdk/ndk/index.html http://groups.google.com/group/android-ndk NDK 安装目录 Docs/ 下的文档 Samples/ 下的示例 代码 JNI 接口规范
E N D
ANDROID应用开发专题2 JNI与NDK开发 刘健培 北京邮电大学 ljp020993@gmail.com 2013.05
本次内容 • Android应用程序专题之NDK • NDK概念 • NDK开发流程 • JNI原理 • NDK应用示例
参考资料 • http://developer.android.com/tools/sdk/ndk/index.html • http://groups.google.com/group/android-ndk • NDK安装目录 • Docs/下的文档 • Samples/下的示例代码 • JNI接口规范 • http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/jniTOC.html
什么是NDK • http://developer.android.com/tools/sdk/ndk/index.html • Native Development Kit, Google为Android进行本地开发而提供的一个本地开发工具 • 本地工具集合 • 将C/C++编译成本地库的工具,提供相应mk文件隔离CPU、平台、ABI等差异,只需修改.mk文件创建.so • 把本地库嵌入到APK中的工具 • 相对稳定、功能有限的本地API头文件与二进制库文件 • 文档、示例程序与教程 • 本地API接口标准 • libc (C library) headers • libm (math library) headers • JNI interface headers • libz (Zlib compression) headers • liblog (Android logging) header • A Minimal set of headers for C++ support • OpenSL ES native audio libraries • Android native application APIS • OpenGL ES 1.1 and OpenGL ES 2.0 (3D graphics libraries,NDK r3) headers • libjnigraphics (Pixel buffer access) header (for Android 2.2 and above, ,NDK r4). • 完整API列表见NDK下载包的docs/ STABLE-APIS.html • 如使用NDK不支持的系统库/函数,Android平台升级时,所引用个的库/函数可能被删除、更新 • 定位:配合、补充SDK的一个工具 SDK NDK 编译插入 打包发布 Apk 应用主模块dex(Java) 本地库 so(C/C++) JNI 资源文件 arsc … 配置文件XML
什么是NDK • NDK 发布之前,Android不支持进行C/C++开发? • Android SDK(Java-Dalvik-jni-C/C++) • 如何编译so? • 如何将so打包进apk? • Android源代码开发 • 有了NDK,我们可以使用纯C开发Android 应用? • it is not a good way! • 缺少框架支持:事件处理、生命周期维护、UI等 • Native Activity
何时使用NDK • 优势 • 代码重用 • 利用现有C/C++代码,如开源项目、游戏代码 • 提高性能 • 执行计算密集的操作,如复杂的数学、物理、图形、音视频计算 • 硬件控制 • 硬件控制代码通常使用C/C++编写,再借助JNI与Java联系起来 • 应用安全 • Java编译后的dex文件容易遭到逆向破解,so保密性更好些 • 代价 • 增加代码编写、构建、调试的复杂性,开发难度大 • 缺乏Android应用框架的支持,需同时使用Java与C/C++ • 兼容性较Java差 • NDK生成的原生库只能运行于Android1.5及以上的设备上
NDK应用开发流程 • 安装NDK开发环境 • 程序开发 • 使用JNI进行接口映射( Java <- -> C/C++) • 使用NativeActivity构建纯粹本地应用(C/C++) • 移植现有C/C++代码 • 测试调试 • 打包发布
搭建Android NDK开发环境 • 系统与软件需求 • Android SDK • 需要完整安装Android SDK(NDK没有用于生成apk包的工具) • Android 1.5 SDK及以上版本 • 支持的操作系统 • Windows XP (32-bit) or Vista (32- or 64-bit) • Mac OS X 10.4.8 or later (x86 only) • Linux (32 or 64-bit; Ubuntu 8.04, or other Linux distributions using GLibc 2.7 or later) • 需要的开发工具 • GNU Make 3.81或以上版本(NDK r8e已自带) • 最新版awk (GNU Awk或Nawk) (NDK r8e已自带) • Cygwin 1.7 或更高版本(仅限于Windows)(http://www.cygwin.com/) • Windows安装流程 • 可以直接下载Android NDK解压缩。 • http://developer.android.com/tools/sdk/ndk/index.html) • 为便于在命令行下编译本地代码,也可先安装cygwin(http://cygwin.com/setup.exe), 再下载Android NDK,然后将NDK的根目录添加到windows的Path环境变量中。 • 示例 • 安装Cygwin • 安装Android NDK • 整合Eclipse与NDK
第三方NDK开发工具 • vs-android • http://code.google.com/p/vs-android/ • VisualGDB • http://visualgdb.com/?features=android
NDK组成结构 Docs 文档 Samples 示例代码 你的源文件 Platform NDK头文件+库 Sources 第三方源代码,如STL Prebuilt Wak、make等工具 Toolchains 交叉编译工具 ndk-build 主编译程序 Build 编译脚本 Tests NDK测试 So本地库 Java文件 资源文件 配置文件 SDK工具 APK文件 ndk-stack 打印调用栈 ndk-gdb gdb调试
NDK帮助文档 • 几个重要文档 • documentation.html – NDK的概述
NDK示例代码 • bitmap-plasma - 如何在NDK中使用bitmap的例子,早期的NDK版本不能直接使用bitmap,后来的版本中增加了对bitmap的支持 • hello-gl2 - 在NDK中如何使用OpenGLES的运用 • hello-jni- 最基本的NDK使用方式,通过NDK获取字符串然后在Android应用中显示出来 • hello-neon - 在NDK中有关neon的优化 • module-exports - 多个库的调用方式。foo被编译为静态库,bar被编译为动态库并调用了库foo,zoo被编译为动态库并调用了库bar。 • native-activity - 完全用NDK实现整个Android程序 • native-audio - 在NDK中有关音频的操作 • native-media - 在NDK中对视频的操作 • native-plasma - 完全用NDK实现整个Android程序并且提供了涉及plasma的优化 • san-angeles - 移植到Android平台的OpenGL ES的例子 • test-libstdc++ - 对C++的支持,但并非支持C++的全部特征 • two-libs- 两个库的使用,first为静态库,second为动态库,并且second库调用first库
第一个NDK程序(示例) • 建立包含本地代码的应用程序的工程目录 • 使用NDK编译该工程,生成本地库so • 使用SDK建立工程,生成应用程序APK
Android.mk #一个Android.mk 文件从定义LOCAL_PATH 变量开始, 'my-dir'宏的功能由编译器提供,被用来返回当前目录的地址 LOCAL_PATH := $(call my-dir) #CLEAR_VARS 这个变量指明了一个GNU makefile文件,这个功能会清理掉除LOCAL_PATH之外所有的LOCAL_XXX,这句是必须的,保证变量的局部性 include$(CLEAR_VARS) #LOCAL_MODULE 变量必须被定义,用来区分Android.mk中的每一个模块。文件名必须是唯一的,不能有空格。最后会生成是'libhello-jni.so' LOCAL_MODULE := hello-jni #LOCAL_SRC_FILES 变量必须包含一个C 和C++源文件的列表,这些会被编译并聚合到一个模块中。头文件和被包含的文件并不需要列出,因为编译系统会自动计算相关的属性 LOCAL_SRC_FILES := hello-jni.c #BUILD_SHARED_LIBRARY收集从'include $(CLEAR_VARS)'开始的LOCAL_XXX 变量,并且决定哪些要被编译进行动态库。BUILD_STATIC_LIBRARY用于生成动态库 include$(BUILD_SHARED_LIBRARY)
跨语言互操作问题 • 2个问题 • 语法层次如何相互理解 • API、静态、形式 • 语义层次如何相互理解 • ABI、动态、内容 • 2种类型 • 使用第三方统一标准 • Socket、管道、RPC等 • Web Service等 • COM、COBRA等分布式组件模型 • .NET的CLS通用语言规范 • 其中一门语言建立标准 • 编译型 -> 编译型 • 编译型 -> 解释型 • 解释型 -> 解释型 • 解释型 -> 编译型
JNI • JNI是本地编程接口(Java Native Interface) 。它使得在 Java 虚拟机 (VM) 内部运行的 Java 代码能够与用其它编程语言(如 C、C++ 和汇编语言)编写的应用程序和库进行互操作。 • 可以使用JNI从Java的程序中调用Native代码。 • 可以从Native程序中调用Java代码。 • 在Java代码和Native代码中需要按照固定的格式告诉JNI如何调用对方。 • 在Android Framework中,JNI是联系上层Java与底层C/C++的桥梁。 • Android中编写JNI的2种方式 • NDK • Android源代码
Java -> C/C++ • 六步: • 编写Java代码,在Java类中声明Native方法 • native void funcName() • static {System.loadLibrary(“libName”)} • 编译Java代码,调用Native方法 • javac fileName.java • 生成C语言头文件,用javah生成包含JNI Native函数原型的头文件 • Javah -jni <包含以native声明方法的Java类名称> • javahclassName-> className.h • 生成的接口函数原型:Java_类名_本地方法名 • JNIEXPORT jint JNICALL Java_class_method(JNIEnv *env, jobjectobj); • 编写C/C++代码,实现JNI的Native函数 • JNIEXPORT jint JNICALL Java_class_method(JNIEnv *env, jobjectobj){…} • 生成C/C++共享库 • 运行Java程序,通过JNI调用Native函数
Java- C/C++函数映射方法 • 2种方式 • 静态命名匹配 • 动态指针加载
Java- C/C++函数映射方法 • 静态命名匹配 • 假设在com.android.xxx package中有个文件class.java,内有函数method,那么执行javah -jniom.android.xxx.test,会生成头文件com_android_xxx_class.h,内有函数java_com_android_xxx_class_method。这个名字就包括了该函数所对应Java版本所在的包、文件以及名称。 • 具体调用过程: • 程序运行时,调用System.loadLibrary()方法,将Native方法具体实现的C/C++运行库加载到内存中。 • Java虚拟机检索加载进来的库函数符号,在其中查找与Java Native方法拥有相同签名的JNI Native函数符号,若找到一致的,则将本地方法映射到具体的JNI Native函数。
Java- C/C++函数映射方法 • 动态指针加载 • 静态命名匹配的方式是通过在加载运行库时,查找匹配的函数名称(字符串比对)来实现的,效率较低,且无法在运行时更改函数实现,不够灵活。 • 动态指针加载方式是通过在加载运行库时,直接提供一个映射表将JNINative函数与Java Native方法链接在一起实现的,效率更高,且可以在运行时更改映射表内容,从而达到弹性更换Native函数实现的目的。 • 具体实现过程: • 在Java代码中,调用System.loadLibrary()方法时,Java虚拟机会检索共享库的函数符号,检查JNI_OnLoad这个默认函数是否实现,是则调用JNI_OnLoad函数,否则继续选择函数命名匹配的方式。 • 所以程序员可以在JNI_OnLoad函数中调用RegisterNatives()将映射表注册到Java虚拟机。 • 示例
C/C++ -> Java • 2种方式 • JNI Native函数调用Java端的代码 • 使用JNI函数(JNI规范标准接口) • C程序中直接嵌入Java虚拟机 • 使用Invocation API
JNI函数 • Native代码通过调用 JNI 函数来访问 Java 虚拟机功能 • JNI 函数可通过接口指针(JNIEnv*)来获得。接口指针是指针的指针,它指向一个指针数组,而指针数组中的每个元素又指向一个接口函数。每个接口函数都处在数组的某个预定偏移量中。
JNI函数 • 常用操作 • 创建Java对象 • 访问类静态成员域 • 调用类的静态方法 • 访问Java对象的成员变量 • 访问Java对象的方法 JNIEXPORT void JNICALL Java_Callbacks_nativeMethod(JNIEnv*env,jobjectobj,jint depth) { jclasscls=(*env)->GetObjectClass(env,obj); jmethodID mid =(*env)->GetMethodID(env,cls,"callback","(I)V"); if(mid !=0) (*env)->CallVoidMethod(env,obj, mid, depth); }
Invocation API • JNI提供了一套Invocation API,它允许Native代码在自身内存区域内嵌入Java虚拟机,装载Java类,调用指定的方法。 • 厂商可以交付支持 Java 的应用程序,而不必链接 Java 虚拟机源代码。 • C/C++要调用JAVA 程序,必须先加载JAVA 虚拟机,由JAVA 虚拟机解释执行class文件。为了初始化JAVA 虚拟机,JNI 提供了一系列的接口函数,通过这些函数方便地加载虚拟机到内存中。 • Invocation API 允许本地应用程序用 JNI 接口指针来访问虚拟机特性。
#include <jni.h> /* 其中定义了所有的事项 */ ... JavaVM*jvm;/* 表示 Java 虚拟机*/ JNIEnv*env;/* 指向本地方法接口的指针 */ JDK1_1InitArgs vm_args;/* JDK 1.1 虚拟机初始化参数 */ vm_args.version=0x00010001;/* 1.1.2 中新增的:虚拟机版本 */ /* 获得缺省的初始化参数并且设置类路径 */ JNI_GetDefaultJavaVMInitArgs(&vm_args); vm_args.classpath=...; /* 加载并初始化 Java 虚拟机,返回 env中的JNI 接口指针 */ JNI_CreateJavaVM(&jvm,&env,&vm_args); /* 用 JNI 调用 Main.test方法 */ jclasscls=env->FindClass("Main"); jmethodID mid =env->GetStaticMethodID(cls,"test","(I)V"); env->CallStaticVoidMethod(cls, mid,100); /* 结束。*/ jvm->DestroyJavaVM();
NDK应用示例 • 建立工程 • 编写代码 • 编译程序 • 运行程序 • 调试程序
调试 • Eclipse+CDT+gdb • Cygwin+ant+adb • __android_log_print #include <android/log.h> int __android_log_print(intprio,constchar*tag,constchar*fmt,...) LOCAL_LDLIBS :=-llog
Native Activity • Native Activity是Android 2.3中正式发布的功能。 • 可以在没有Java代码的情况下,完全由本地代码生成应用,需要进行显示和事件处理方面的工作。 • Android.app包中的NativeActivity类方便开发者实现一个使用纯粹本地代码所实现的activity。但本地代码并不需要继承这个类,只需在AndroidManifest.xml中引用它,并调用NDK的API即可。 • 编写Native Activity需要的API头文件在<ndk目录>\platforms\android-xx\arch-arm\usr\include\android下 • <ndk目录>\sources\android\native_app_glue\是一个编写Native Activity的辅助库,帮助显示和事件处理,使用该库的Native Activity需要使用android_main()函数作为入口。 • android的ndk在<ndk目录>\samples\目录下有2个Native Activity的例子: • native-activity:在本地构建的应用,使用OpenGLES构建截面 • native-plasma:在本地构建的应用,使用本地访问位图构建界面
NDK vs. RenderScript • Android平台为应用程序在传统的Android应用边界外面运行提供了两种方法:NDK、RenderScript。 • Renderscript(RS)是Android3.0引入的一组提供高性能的3D图形渲染和密集型计算的API(C99标准)。 • 编程语言和可移植性 • NDK可以利用现有的C/C++库。 • RenderScript使用C99语法,最终编译成原生代码。RenderScript无法从其他C应用程序移植过来,不过它在Android设备上比NDK更为常见(Google TV) 。 • 编译和调试 • 用NDK编写的代码必须事先针对每一个目标原生平台来编译。采用NDK的应用程序可以使用gdb进行行级调试。 • RenderScript在开发机器上进行第一遍编译,然后在目标设备上进行最后一遍编译,因而带来了更高效的原生二进制代码。RenderScript应用程序在运行时无法调试。 • 性能 • NDK和RenderScript都未能在性能方面提供完美方案。两者都增加了项目的复杂性,降低了可移植性,提高了测试需求,加大了调试难度,还给项目增加了维护负担。 • 如果纯粹是用于计算,RenderScript的设置和配置很容易,最终的运行速度实际上可能胜过使用NDK的类似实现方法,需要编写的代码比较少。 • NDK比较适合高性能OpenGL应用程序或需要访问图形软件开发工具包(SDK)更多功能或访问第三方库的游戏。