android应用开发专题2 jni与ndk开发

Download Report

Transcript android应用开发专题2 jni与ndk开发

ANDROID应用开发专题2
JNI与NDK开发
刘健培 北京邮电大学
[email protected]
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/jn
iTOC.html
什么是NDK
http://developer.android.com/tools/sdk/ndk/index.html
 Native Development Kit, Google为Android进行本地开发而提供的一个本地开发工具

 本地工具集合
 将C/C++编译成本地库的工具,提供相应mk文件隔离CPU、平台、ABI等差异,只需修改.mk文
件创建.so
 把本地库嵌入到APK中的工具
SDK
NDK
 相对稳定、功能有限的本地API头文件与二进制库文件
编译插入
 文档、示例程序与教程
打包发布
Apk
 本地API接口标准
 libc (C library) headers
本地库
应用主模块
 libm (math library) headers
dex(Java)
so(C/C++)
 JNI interface headers
JNI
 libz (Zlib compression) headers
 liblog (Android logging) header
资源文件
配置文件
 A Minimal set of headers for C++ support
arsc …
XML
 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的一个工具
什么是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类名称>
 javah className -> className.h
 生成的接口函数原型:Java_类名_本地方法名
 JNIEXPORT jint JNICALL Java_class_method(JNIEnv *env, jobject obj);
 编写C/C++代码,实现JNI的Native函数
 JNIEXPORT jint JNICALL Java_class_method(JNIEnv *env, jobject obj){…}
 生成C/C++共享库
 运行Java程序,通过JNI调用Native函数
Java- C/C++函数映射方法
 2种方式
 静态命名匹配
 动态指针加载
Java- C/C++函数映射方法
 静态命名匹配
 假设在com.android.xxx package中有个文件class.java,内有函数
method,那么执行javah -jni om.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++函数映射方法
 动态指针加载
 静态命名匹配的方式是通过在加载运行库时,查找匹配的
函数名称(字符串比对)来实现的,效率较低,且无法在运
行时更改函数实现,不够灵活。
 动态指针加载方式是通过在加载运行库时,直接提供一个
映射表将JNI Native函数与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, jobject obj, jint depth)
{
jclass cls = (*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 方法 */
jclass cls = 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(int prio, const char *tag, const char *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\androidxx\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)更多功能
或访问第三方库的游戏。
谢 谢!