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)更多功能
或访问第三方库的游戏。
谢 谢!