NDK Android* 应用移植方法
概述
本指南用于帮助开发者将现有的基于 ARM* 的 NDK 应用移植到 x86。假设您已经拥有一个正常执行的应用,须要知道怎样可以高速让 x86 设备在 Android* Market 中找到您的应用,本文将可以为您提供一些入门信息。同一时候本指南还提供了一些技巧和指南,以帮助您解决在移植过程中可能会遇到的编译器问题。
内容
NDK 概述
原生开发套件 NDK 是一款强大的工具,将原生 x86 代码的强大功能和 Android* 应用的图形界面结合在一起。通过使用该工具,开发者将可以提升某些应用的性能优势。但同一时候开发者也须要十分慎重,由于在某些情况并不能实现预期的效果.
NDK 致力于支持开发者完毕下面工作:
- 编译原生 C/C++ 库(由包装程序 Java* 代码调用)。以备在 Android* 应用包中使用
- 又一次编译 ARM* 原生库,供在 x86 平台(英特尔® 凌动™ 微架构)上使用。并在必要时进行移植
对于以上提到的第二点。部分情况下可能仅仅须要简单地改动 Build Flag 并又一次编译就可以,但有时却并不这么简单。
比如。假设原生库涉及到 C 代码内的内嵌汇编,那么代码将无法通过简单的汇编来实如今两种不同的架构自如执行的目标,此时将须要进行又一次编写(很多其它信息请參见比較 ARM* 的 NEON* 与英特尔的 SSE 的部分)。
Java* 原生接口(JNI)将 Android* Java* 代码与由 NDK 预编译的原生代码结合在一起。
如欲了解有关该接口的很多其它信息,请訪问: http://java.sun.com/docs/books/jni/。
以上链接对 JNI 规范进行了广泛、深入的剖析。 若仅仅是希望了解其概况。Wiki 页面就可以满足您的要求(存在疑问时,请随时參考规范以检查其正确性)。Wiki 页面的网址例如以下: http://en.wikipedia.org/wiki/Java_Native_Interface。
JNI 的开销十分庞大。因此在理想情况下,开发者应在应用中尽可能降低对 JNI 的调用。
详细而言,在 Android* 应用中使用原生代码并不一定能提升性能。通常而言,当原生代码涉及到由 CPU 进行的运算时(如大量使用 SSE 指令),将能够实现一定的性能提升。
但在另外一些情况下,如现有的应用仅用于为用户提供复杂的 Web 界面。此时通过 JNI 使用原生代码可能会降低性能。
在何时该用和不该用 NDK 上。不存在成文的规则,以上几点仅仅是提供了一些须要注意的通用准则和事项。
开发者可通过下面网址获取 NDK 的最新版本号: http://developer.android.com/sdk/ndk/index.html。 在最新版本号 NDK r6b 中。NDK 可用于构建基于 ARM* 和基于 x86(英特尔® 凌动™ 微架构)的原生库。 这为开发者在一个应用包内进行原生代码移植提供了方便。
开发者将须要为项目创建一个 Android.mk 文件和一个 Application.mk 文件(可选)。当中。Application.mk 文件用于描写叙述您的应用须要哪些原生模块。
Android.mk 文件用于控制怎样和从哪里构建一个模块(静态/共享库)。下面是一个简单 Android.mk 文件的片段:
图 1:简单 Android.mk 文件的内容
构建系统时将前置一个库,并同一时候生成一个名称为 libtest.so 的库。
按预期。开发人员将在 LOCAL_SRC_FILES 中为项目源文件命名。 LOCAL_LDLIBS 和 LOCAL_CFLAGS 分别用于指定 Linking Flag(链接标记)和Compilation Flag(编译标记)。
下面命令行提供了一个有关怎样指定构建指向 x86 架构的演示样例:ndk-build APP_ABI=x86
调用原生库可採用下面两种方法: System.loadLibrary("relative_path_and_name") and System.load("full_path_to_lib_file")。 前者更经常使用,后者更稳定。 使用前者时,Android.mk 指定的库名称中的“lib”部分可丢弃。 调用示比例如以下:
图 2:调用原生代码演示样例
此外,对于原生代码,开发者须要确保原生代码的输入方法具有正确的 JNIEXPORT 方法签名。而不是典型的 C/C++ 标头。前面提及的 JNI 链接包括有很多其它相关信息。
开发者可通过两种方式载入原生库:1)在 Android* apk 包中提供该库并在执行时对其进行引用;2)在 Android* 文件系统上提供通往该库的绝对路径。
採用以上方式中的哪一种取决于开发者的偏好。
但不管採用哪种方式,均应进行对应的正确处理。
通过使用 adb logcat 命令。开发者可确保在执行时成功载入目标原生库。
下面提供了一个描写叙述原生库已载入的系统日志的演示样例。注意需提供通往原生库文件的完整路径。
图 2:调用原生代码演示样例
以上各部分提供了有关怎样使用 NDK 的入门知识。
如欲了解更加复杂的细节,请阅读 NDK 应用包中包括的相关文档。 这些文档提供有出色的教程和针对各种应用的源码演示样例。
移植概述
对于大多数应用而言。将现有的 NDK 应用移植到 x86 很easy。
除非原生代码使用 ARM* 特有的特性。否则移植应用仅仅需进行又一次编译、又一次打包和又一次公布操作就可以完毕。
下面内容向您介绍了将 NDK 应用移植到 x86 涉及到的步骤。
- 获取最新的 NDK 工具。
x86 支持最先在 android-ndk-r6 中提供,但当时仍存在一些问题,之后谷歌非常快进行了修复。确保您已经从 Android* NDK 站点 下载和安装了最新的(写入时,最新的 NDK 为 android-ndk-r6b)NDK。
- 假设您已有一个 Application.mk 文件,可编辑 APP_ABI 行增加 x86。演示样例:
APP_ABI := armeabi armeabi-v7a x86
假设您没有 Application.mk 文件,可将 x86 加入到命令行构件中。下面为构建一个 NDK 演示样例应用时的命令行和输出内容。
$ ndk-build APP_ABI="armeabi armeabi-v7a x86"
Install : test-libstl => libs/armeabi/test-libstl
Install : test-libstl => libs/armeabi-v7a/test-libstl
Install : test-libstl => libs/x86/test-libstl - 在前一步中,我们能够发现包括每种架构的二进制代码的目录在 Libs 目录下创建。下一步,我们将又一次打包 APK 以包括新库。因为 Libs 目录位于根项目目录之下,用于创建 APK 的构建工具已经知晓该目录中的二进制代码。利用 Eclipse。仅仅需简单地又一次构建项目 APK 就可以包括新的 x86 二进制代码。
使用命令行进行构建的操作与此同样。下面列出了又一次构建演示样例演示 hello-jni 时的演示样例输出内容:
$ android.bat update project --path C:/Tools/android-ndk-r6b/samples/hello-jni
Updated local.properties
Added file C:\Tools\android-ndk-r6b\samples\hello-jni\build.xml
Added file C:\Tools\android-ndk-r6b\samples\hello-jni\proguard.cfg
$ ant -f hello-jni/build.xml debug
Buildfile: C:\Tools\android-ndk-r6b\samples\hello-jni\build.xml
…
debug:
[echo] Running zip align on final apk...
[echo] Debug Package: android-ndk-r6b\samples\hello-jni\bin\HelloJni-debug.apk
BUILD SUCCESSFUL - 下一步为在英特尔架构设备或 x86 模拟器上进行执行和測试。
验证全部二进制代码均已正确打包的最后一步为使用 zip 存档工具打开 APK 并确保当中包括二进制代码。下面是存在 x86 二进制代码时 APK 结构外观的截屏。
移植技巧:从 ARM* 到 x86
将应用移植到 x86 应当非常easy,虽然非常多人可能都有这种想法,但在实际的代码中。仍须要慎重注意并解决英特尔® 凌动™ 和 ARM* 架构之间的差异。下面主题介绍了您在移植过程中可能遇到的问题以及怎样予以解决。
您的构建环境有可能直接使用了工具链,而不是 Android* 构建脚本。在 ARM* 中,所用的路径例如以下:
android-ndk\toolchains\arm-linux-androideabi-4.4.3
对于 x86。使用路径:
android-ndk\toolchains\x86-4.4.3
有关具体信息。请參阅位于 android-ndk/docs/STANDALONE-TOOLCHAIN.html 的 NDK 文档。
在 ARM* 和英特尔® 凌动™ 微架构之间移植 C/C++ 代码时可能出现内存对齐不匹配的情况。下面文章针对这一点提供了一个典型的演示样例: /en-us/blogs/2011/08/18/understanding-x86-vs-arm-memory-alignment-on-android。基本的一点是:开发人员应当在设计代码时,在必要的地方考虑对数据进行明白的强制对齐。否则,开发人员将无法保证数据在不同的平台可以得到正确的处理。
眼下,在构建 NDK 库时,能够使用三种支持的应用二进制接口(ABI):
- ‘armeabi’ – 默认选项,将创建以基于 ARM* v5TE 的设备为目标的库。具有这样的目标的浮点运算使用软件浮点运算。
使用此 ABI 创建的二进制代码将能够在全部 ARM* 设备上执行。
- ‘armeabi-v7a’ – 创建支持基于 ARM* v7 的设备的库,并将使用硬件 FPU 指令。
- ‘x86’ – 生成的二进制代码可支持包括基于硬件的浮点运算的 IA-32 指令集。
全部这些 ABI 选项均支持浮点运算。除非使用的是特定于 ARM* 的汇编指令,否则在将代码移植到 x86 时不会发生故障。其优势在于,假设碰巧您的应用仅针对“armeabi”进行编译,而如今须要支持 x86,则您在进行大多数浮点运算时均能感觉到性能提升。
将 ARM* NEON* 指令移植至英特尔® 凌动™ 的英特尔® SSE
虽然这篇短小的文章不可能包罗万象,可是下面提供的信息将可以让您大致了解在英特尔架构和 ARM* 中。SIMD 扩展的实施有何不同。借助此简单介绍,开发人员还将获得一些工具,以便于開始进行一些简单的编码练习。
NEON* 是一种 ARM* 技术,主要用于多媒体(智能手机和高清电视等)应用。ARM* 表示其基于 128 位 SIMD 引擎的技术 – ARM* Cortex*(一种串行扩展)—可提供比 ARM* v5 架构至少高 3 倍的性能,以及比 ARM* v6 至少高 2 倍的性能。如欲了解有关此技术的具体信息。以深入了解 NEON 及其他性能考虑,请訪问下面网址:http://www.arm.com/products/processors/technologies/neon.php
此处的关键理念为,各寄存器被“堆积”成一个矢量。当中每个寄存器均为一个元素,并与其他元素的数据类型相匹配。在此基础之上。运算在管道内运行。因而这一方法被称作 Packed SIMD。
SSE 指面向英特尔架构(IA)的SIMD 流指令扩展。眼下,英特尔® 凌动™ 最高支持 SSSE3(补充 SIMD 流指令扩展 3)。
凌动™ 暂不支持 SSE4.x。
后者也是一个 128 位引擎,用于打包浮点数据。这一运行模式開始于 MMX 技术。
SSx 是较新的技术,代替了 MMX。如欲了解具体信息,请參阅英特尔《IA-32 和 IA-64 软件开发者手冊》中的“第一卷: 基础架构”部分。网址为: http://www.intel.com/content/www/cn/zh/processors/architectures-software-developer-manuals.html。
眼下,SSE 概述部分在 5.5 节。
它提供 SSE、SSE2、SSE3 和 SSSE3 的操作码。注意。数据运算一般会涉及到处理基于精度的打包浮点数值;而且须要在 XMM 寄存器之间。或在这些寄存器与内存之间批量数据传输。XMM 寄存器主要用于代替 MMX 寄存器。
在推荐使用前述《英特尔架构软件开发者手冊》来了解全部单个 SSE(x) 助记符的同一时候,我们也鼓舞开发者通过下面链接了解各种 SSE 汇编级指令。网址为: http://neilkemp.us/src/sse_tutorial/sse_tutorial.html。
在该链接中。您可通过“文件夹”部分直接跳到代码演示样例或首先具体了解某些背景信息。相同,下面直接来自 ARM* 的手冊提供了一些信息和 NEON* 汇编小片段:/sites/default/files/m/b/4/c/DHT0002A_introducing_neon.pdf。
请參阅 ARM* 文档中的第 1.4 节。
下面是在一般层面上比較 NEON 和 SSE 汇编代码时的几个要点(注意:随着技术的发展,信息随时会过时。依据详细的 SIMD 技术和当下的应用编码问题。可能还存在其他差异):
字节存储次序。英特尔仅支持低位优先汇编,而 ARM* 则同一时候支持高位或低位优先顺序(ARM* 支持两种顺序)。在提供的代码演示样例中,同英特尔一样。ARM* 代码採用的也是低位优先顺序。
注意虽然如此,在 ARM 中可能会存在一些编译器影响*。比如,使用 GCC* 为 ARM* 进行编译时具有 -mlittle-endian 和 -mbig-endian 标记。有关具体信息请訪问:http://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html
Granularity. 在引用简单汇编代码演示样例的情况中(请再次注意这并不包括开发者可能发现的 NEON 和 SSE 之间的全部差异),将 SSE 的 ADDPS 指令与 NEON 的 VADD.ix(即:x = 8 或 16)进行比較。
请注意,后者在作为引用助记符的一部分而待处理的数据上有一定粒度减少。
在将 C/C++ 代码 NEON 代码移植到 SSE 时,可能会出现非常多 API 问题。
此处请注意本文的一个如果。即这里未使用内嵌汇编。而使用的是真正的 C/C++ 代码。
对于更高层级的编程。NEON 与 SSE 之间的差异涉及到大尺寸数据(128 位)的处理。
本文针对这样的移植练习提供了一个简短演示样例: http://stackoverflow.com/questions/7203231/neon-vs-intel-sse-equivalence-of-certain-operations
结论
我们希望这一指南可以为您提供一些有用信息,帮助您成功将基于 NDK 的应用移植到 x86。移植到 x86 后。您的应用将可以供一种全新类型的 Android* 设备进行下载、购买和使用。假设您在移植过程中遇到问题,请随时在本文中发表评论。
我们将很乐意回答您的问题,为您提供帮助。
相关文章与资源:
声明
* 其它的名称和品牌可能是其它全部者的资产
英特尔公司 © 2011 年版权全部。 全部权保留。
英特尔、Atom 和凌动是英特尔在美国和/或其它国家的商标。
本文件里包含关于英特尔产品的信息。 本文件不构成对不论什么知识产权的授权,包含明示的、暗示的,也不管是基于禁止反言的原则或其它。
英特尔不承担不论什么其它责任。英特尔在此作出免责声明:本文件不构成英特尔关于其产品的使用和/或销售的不论什么明示或暗示的保证。包含不就其产品的(i)对某一特定用途的适用性、(ii)适销性以及(iii)对不论什么专利、版权或其它知识产权的侵害的承担不论什么责任或作出不论什么担保。
除非经过英特尔的书面允许认可,英特尔的产品无意被设计用于或被用于下面应用:即在这种应用中可因英特尔产品的故障而导致人身伤亡。
英特尔有权随时更改产品的规格和描写叙述,恕不另行通知。设计者不应信赖不论什么英特产品所不具有的特性,设计者亦不应信赖不论什么标有“保留权利”或“没有定义”说明或特性描写叙述。对此。英特尔保留将来对其进行定义的权利,同一时候。英特尔不应为因其日后更改该等说明或特性描写叙述而产生的冲突和不相容承担不论什么责任。此处提供的信息可随时更改,恕不另行通知。请勿依据本文件提供的信息完毕一项产品设计。
本文件所描写叙述的产品可能包括使其与宣称的规格不符的设计缺陷或失误。这些缺陷或失误已收录于勘误表中。可索取获得。
在发出订单之前。请联系当地的英特尔营业部或分销商以获取最新的产品规格。
如欲获得本文涉及的带编号文档的副本或其它英特尔文献。可致电 1-800-548-4725。或訪问:http://www.intel.com/design/literature.htm