* 包含 Java 代码、资源文件、C/C++ 源代码(有时也包含汇编码)的 Android NDK 应用。 所有原生代码被编译成一个动态链接库 (.so 文件),然后在主程序中通过使用 JNI 机制的 Java 进行调用。
图 1: 两种安卓应用
2. 安卓原生开发套件
2.1 简介
安卓原生开发套件(NDK)是 Android SDK 的附带工具。 NDK 是强大的安卓应用开发工具,理由如下:
* 以原生代码的形式构建应用中对性能要求极高的部分。 在使用 Java 代码时,基于 Java 的源代码需要使用一台虚拟机解释成机器语言。 相比之下,原生代 码可以在执行之前直接编译和优化成二进制的形式。 正确使用原生代码,您就能在您的应用中构建出高性能的代码,如硬件视频编码和解码、图形处理和算 术运算。
* 重复使用传统的原生代码。 C/C++ 代码可以编译成一个动态库,然后通过使用 JNI 机制的 Java 代码进行调用。
2.2 工具概述
在开发期间,您可以使用英特尔® 硬件执行管理器(HAXM)改善安卓模拟器的性能。 HAXM 是一个硬件辅助虚拟化引擎(管理程序),该引擎可以使用英特尔® 虚拟化技术(Intel®VT)加快主机上的安卓应用模拟速度。 配合英特尔提供的 Android x86 模拟器图像和官方的 Android SDK 管理器一起使用时,HAXM 可以在支持英特尔虚拟化技术的系统上提供更快的安卓模拟体验。 如欲了解更多有关 HAXM 的信息,请访问:http://software.intel.com/zh-cn/android/articles/intel-hardware-accelerated-execution-manager.
2.3 安装 HAXM
使用 Android SDK 管理器安装 HAXM (推荐),或者从英特尔网站下载安装程序以后手动安装 HAXM。 如果您希望它可以自动更新,请如图 2 所示使用 Android SDK 管理器进行安装。[1]
图 2: 使用 Android SDK 管理器安装英特尔 HAXM
您也可以访问 http://www.intel.com/software/android,找到正确的安装包后将其下载到您的主机平台上, 然后根据说明逐步安装。
2.3.1 设置 HAXM
运行 HAXM 时需要使用英特尔提供的 Android x86 系统图像。 您可以使用 Android SDK 管理器下载该系统图像,或者从 英特尔® 开发人员专区网站手动下载。
图像成功安装之后,英特尔® x86 Android 模拟器图像将通过与 Android SDK 一同提供的 “模拟器-x86” 二进制代码自动执行。 安卓模拟器被英特尔 VT 加速之后可以加快您的开发进程。
3. 为英特尔凌动架构开发并移植 NDK 应用
3.1 为搭载英特尔凌动处理器的设备开发 NDK 应用
成功安装完 NDK 之后,请花费几分钟的时间阅读 <ndk>/docs/ 目录下的文件,尤其是 OVERVIEW.html 和 CPU-X86.html,以便您能完全了解 NDK 机制及使用方法。
如图 3 所示,NDK 应用开发可分为五个步骤:
图 3: NDK 应用开发流程
创建一个新的安卓项目并将您的原生源代码放在 <project>/jni/ 下。 项目内容显示在图 4 中。该演示在原生代码中包含了一个名为 Java_com_example_hellojni_HelloJni_stringFromJNI() 的简单函数。 如源代码所示,它能从 JNI 中返回一个简单字符串。
图 4: 创建原生代码
3.1.2 创建 MakeFile ‘Android.mk’
默认情况下,构建的 NDK 应用仅用于 ARM 平台。 要想构建用于英特尔凌动平台的 NDK 应用,您需要把 APP_ABI := x86 添加到 MakeFile 中。
图 5: 创建 Makefile
3.1.3 编译原生代码
通过运行该项目目录的 'ndk-build' 脚本构建原生代码。 该脚本位于顶级 NDK 目录中。 结果如图 6 所示。
图 6: 已编译的原生代码
构建工具将把剥离的共享库自动拷贝到应用的项目目录中的正确位置上。
3.1.4 从 Java 调用原生代码
成功部署完共享库之后,您可以从 Java 端调用函数。 代码如图 7 所示。一个名为 stringFromJNI() 的公用原生函数在 Java 代码中创建出来,该函数可以使用 System.loadlibrary() 加载共享库。
图 7: 从 Java 调用原生代码
3.1.5 通过 GDB 调试
如果您想要通过 GDB 调试 NDK 应用,必须满足以下条件:
* 通过“ndk-build”构建 NDK 应用
* 在 Android.manifest 中将 NDK 应用设置为“debuggable”
* 在 Android 2.2 (或更高版本) 上运行 NDK 应用
* 只运行一个目标
* 将 adb 的目录添加到 PATH 上
使用 ndk-gdb 命令调试应用。 如图 8 所示,您可以设置一个端点或者通过逐步调试的方法跟踪一个变量值的变化记录。
图 8: 通过 GDB 调试 NDK 应用
3.2 将现有的 NDK 应用移植到搭载英特尔凌动处理器的设备上
在本节中,假定您有一个可用于 ARM 平台的安卓应用,您需要首先移植该应用然后将其部署到英特尔凌动平台上。
将安卓应用移植到英特尔凌动平台上类似于开发流程。 步骤如图 9 所示。
图 9: 将安卓应用移植到英特尔凌动平台上
3.2.1 移植 Dalvik 应用
Dalvik 应用可以直接运行在搭载英特尔凌动处理器的设备上。 用户界面需要根据目标设备进行调整。 就高分辨率设备而言,诸如分辨率为 1280*800 或更高的平板电脑,默认的内存分配可能不会满足应用的要求,此时可能会导致无法启动应用。 此时建议您为高分辨率设备增加默认内存分配。
3.2.2 移植 Android NDK 应用
与移植 Dalvik 应用相比,移植 NDK 应用会稍微复杂一些。 根据以下的原生代码属性,所有的 NDK 应用都能分成三种类型:
* 仅包含与硬件无关的 C/C++ 代码
* 使用第三方动态链接库
* 包含与非 IA 平台高度相关的汇编码
仅包含与硬件无关的 C/C++ 代码的原生代码
1. 重新编译原生代码,使其可以成功运行英特尔凌动平台上的应用。
2. 打开 NDK 项目并搜索 Android.mk 文件,将 APP_ABI := armeabi armeabi-v7a x86 添加到 Android.mk 中然后使用 ndk-build 重新构建原生代码。
3. 如果未发现 Android.mk 文件,请使用 ndk-build APP_ABI="armeabi armeabi-v7a x86" 命令构建项目。
4. 使用支持的 x86 平台再次打包应用。
如果原生代码使用的是一个第三方动态链接库, 共享库必须被重新编译成适合英特尔凌动平台的 x86 版本。
如果原生代码包含与非 IA 平台高度相关的汇编码, 必须使用 IA 汇编或 C/C++ 重写代码。
4. 原生代码开发的最佳设计方案
4.1 强制性内存对齐
由于各种架构、平台和编译器间存在差异,同一种数据结构在不同的平台上可能会具备不同的数据大小。 如果不使用强制性内存对齐,不一致的数据大小就可能会导致加载错误。 [2]
以下例子对不同平台上的同一种数据结构的数据大小进行解释:
struct TestStruct {
int mVar1;
long long mVar2;
int mVar3;
};
这个简单的结构中包含三个变量,分别为 mVar1、mVar2 和 mVar3。
mVar1 是一个整数,占用 4 个字节
mVar2 是双长整数,占用 8 个字节
mVar3 也是个整数,占用 4 个字节。
在 ARM 和英特尔凌动平台上需要多少空间?
图 10 中显示了为 ARM 和带有一个默认的编译器开关的英特尔凌动平台编译的数据大小。ARM 自动使用双重对齐并占用 24 个字节,而 x86 则占用 16 个字节。
图 10: 通过默认编译标识分配内存
由于 ARM 要求对诸如 mVar2 等 64 位变量执行 8 字节对齐,因此 8 字节(64 位) mVar2 在 TestStruct 中会具备一个不同的布局。 在大多数情况下,这不会导致问题,因为与构建 ARM 相比,构建 x86 需要完全重新构建。
然而,如果应用对类或结构执行了序列化,则会导致大小不匹配。 例如,如果您在一个 ARM 应用中创建了一个文件,ARM 应用会将 TestStruct 写入该文件。 如果您稍后将该文件中的数据加载到一个 x86 平台中,该应用中的类大小将与文件中的不同。 类似的内存对齐问题也会出现在具有特定内存布局的网络流量上。
GCC 编译器选项“-malign-double”将在 x86 和 ARM 上实现相同的内存对齐。
4.2 将 NEON* 指令移植到 SSE [3]
4.2.1 NEON
ARM NEON* 技术主要用于诸如智能手机和 HDTV 应用等多媒体。 ARM 文档显示其基于 128 位 SIMD 引擎的技术 — ARM Cortex*(一种串行扩展)可提供比 ARMv5 架构至少高 3 倍的性能,以及比 ARMv6 至少高 2 倍的性能。 如欲了解有关 NEON 技术的更多信息,请访问:http://www.arm.com/products/processors/technologies/neon.php.
4.2.2 SSE: 英特尔推出的类似工具
SSE 指面向英特尔架构(IA)的 SIMD 流指令扩展。 英特尔凌动处理器目前支持 SSSE3 (补充 SIMD 流指令扩展 3)和更早的版本,但还不支持 SSE4.x。SSE 是一个可以处理浮点数据打包的 128 位引擎。 这一执行模式开始于 MMX 技术。SSx 是较新的技术,取代了 MMX。 如欲了解详细信息,请参阅“第一卷: 基础架构”部分 — 英特尔 64 和 IA-32 架构软件开发人员手册。 章节 5.5 中的 SSE 概述针对 SSE、SSE2、SSE3 和 SSSE3 提供了说明。 这些数据运算会在 XMM 寄存器之间或者在 XMM 寄存器和内存之间移动基于精度的打包浮点数值。 XMM 寄存器主要用于取代 MMX 寄存器。
4.2.3 NEON 与 SSE 在汇编级的比较
在推荐使用《英特尔架构软件开发人员手册》来了解所有单个 SSE(x) 助记符的同时,我们也鼓励开发人员通过以下链接了解各种 SSE 汇编级指令,网址为: http://neilkemp.us/src/sse_tutorial/sse_tutorial.html. 使用目录访问示例代码或详细了解背景信息。
此外,以下由 ARM 提供的手册也提供了有关 NEON 的信息,同时在章节 1.4 — “NEON 开发”中包含了一些汇编小片段: http://infocenter.arm.com/help/topic/com.arm.doc.dht0002a/DHT0002A_introducing_neon.pdf.
NEON 和 SSE 汇编代码间的主要差异:
* 字节存储次序。 英特尔仅支持低位优先汇编,而 ARM 则同时支持高位或低位优先顺序(ARM 支持两种顺序)。 在提供的代码示例中,同英特尔一样,ARM 代码采用的也是低位优先顺序。 注: 在 ARM 中可能会存在一些编译器影响。 例如,使用 GCC 为 ARM 进行编译时包含 mlittle-endian 和 –mbig-endian 标记。 更多信息请访问:http://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html.
* 粒度。 如果引用的是简单的汇编代码示例,对 SSE (英特尔) 的 ADDPS 指令与 NEON 的 VADD.ix 指令进行比较,例如 x = 8 或 16。注意,后者在作为部分引用助记符处理的数据上放入一些粒度。
注: 这些差异并未涵盖所有内容。 您可能会发现 NEON 和 SSE 间的一些其他差异。
4.2.4 NEON 与 SSE 在 C/C++ 层面的比较
在将 C/C++ 代码和 NEON 代码移植到 SSE 时,可能会出现很多 API 问题。 此处请注意本文的一个假设,即这里未使用内嵌汇编,而使用的是真正的 C/C++ 代码。 NEON 指令也提供一些原生 C 库。 尽管这些指令是 C 代码,它们并不能在英特尔凌动平台上执行,因此必须重写。
5. 优化应用性能
5.1 性能优化
在编码的过程中,请使用以下方法对英特尔凌动平台上的应用进行性能优化。
5.1.1 使用内联函数代替频繁使用的短函数
内联函数最好用在小函数上,例如访问专用数据成员。 短函数对函数调用的开销十分敏感。 较长的函数在调用和返回序列上花费的时间相应减少,同时几乎很少从代码嵌入中受益。 [4]
内联函数节省了以下方面的开销:
* 函数调用(包含参数传递和把对象的地址放置在堆栈上)
* 保存调用程序的堆栈帧
* 新堆栈帧设置
* 返回值通信
* 原堆栈帧恢复
* 返回
5.1.2 使用浮点代替二重点
浮点运算单元是计算机系统的一部分,专门设计用来执行浮点数上的运算,诸如: 加、减、乘、除和平方根。 一些系统(尤其是基于微码的较老架构)也可以执行各种超越函数,诸如指数计算或三角计算。 当前的处理器通过软件库例程执行这些计算。 在大多数的现代通用计算机架构中,通常有一个或多个浮点运算单元和 CPU 集成在一起[6]。
英特尔凌动平台支持使用浮点运算单元。 在大多数情况下,使用浮点代替二重点会加快数据计算处理速度并为搭载英特尔凌动处理器的设备节省内存带宽。
5.1.3 多线程编码
借助多线程编码,您可以使用英特尔凌动处理器的超线程功能提高吞吐率和整体性能。 更多有关多线程技术的信息,请参见: http://www.intel.cn/content/www/cn/zh/architecture-and-technology/hyper-threading/hyper-threading-technology.html.
5.2 使用编译器标识构建高性能应用
如您所知,原生代码是由安卓应用中的 GCC 构建而成的。 然而,您是否知道 GCC 的默认目标设备呢? 它就是奔腾® 专业处理器。 如果您在编译原生代码时没有添加任何标识,目标二进制代码最好运行在奔腾专业平台上。 大多数安卓应用运行在英特尔凌动平台上,而不是奔腾专业平台。 我们建议您根据自己使用的目标平台添加特定的标识。 您在编译的过程中可以在英特尔凌动平台上添加以下建议标识:
-march=atom
-msse4
-mavx
-maes
更多有关编译器参数的信息,请参见: http://gcc.gnu.org/onlinedocs/gcc/i386-and-x86-64-Options.html
6. 结论
本文讨论了如何在英特尔凌动平台上开发和优化安卓应用,以及如何开发和移植 NDK 应用。
要点总结:
大多数安卓应用可以直接在英特尔凌动平台上执行。 NDK 应用需要重新编译原生代码。 如果应用中包含了汇编码,这部分代码必须重写。
充分利用 IA ( 英特尔架构)特性提升您的安卓应用性能。
添加面向特定平台的编译开关,使 GCC 构建代码更加有效。
参考资料
1.http://software.intel.com/en-us/articles/installation-instructions-for-intel-hardware-accelerated-execution-manager-windows/
2.http://software.intel.com/en-us/blogs/2011/08/18/understanding-x86-vs-arm-memory-alignment-on-android/
3.http://software.intel.com/zh-cn/android/articles/ndk-android-application-porting-methodologies
4.http://msdn.microsoft.com/en-us/library/1w2887zk.aspx
5.http://developer.android.com/sdk/ndk/index.html
6.http://en.wikipedia.org/wiki/Floating-point_unit
关于作者
Dawei 是一名专注于移动应用支持的应用工程师,负责为 x86 设备进行安卓应用开发和优化以及 Web HTML5 应用开发。 此外,Dawei 在移动应用 UI 和 UX 设计方面也拥有着丰富的经验。
声明
本文件中包含关于英特尔产品的信息。 本文件不构成对任何知识产权的授权,包括明示的、暗示的,也无论是基于禁止反言的原则或其他。 除相关产品的英特尔销售条款与条件中列明之担保条件以外,英特尔公司不对销售和/或使用英特尔产品做出其他任何明确或隐含的担保,包括对适用于特定用途、适销性,或不侵犯任何专利、版权或其他知识产权的担保。
除非经过英特尔的书面同意认可,英特尔的产品无意被设计用于或被用于以下应用:即在这样的应用中可因英特尔产品的故障而导致人身伤亡。
英特尔有权随时更改产品的规格和描述而毋需发出通知。 设计者不应信赖任何英特产品所不具有的特性,设计者亦不应信赖任何标有“保留权利”或“未定义”的说明或特性描述。 对此,英特尔保留将来对其进行定义的权利,同时,英特尔不应为因其日后更改该等说明或特性描述而产生的冲突和不相容承担任何责任。 本文信息可能随时更改,恕不另行通知。 请勿使用本信息来对某个设计做出最终决定。
本文件所描述的产品可能包含使其与宣称的规格不符的设计缺陷或失误。 这些缺陷或失误已收录于勘误表中,可索取获得。
在发出订单之前,请联系当地的英特尔营业部或分销商以获取最新的产品规格。
如欲获得本文涉及的带编号文档的副本或其他英特尔文献,可致电 1-800-548-4725,或访问: http://www.intel.com/design/literature.htm。性能测试中使用的软件和工作量可能专为英特尔微处理器进行了优化。 诸如SYSmark和MobileMark等测试均系基于特定计算机系统、硬件、软件、操作系统及功能。 上述任何要素的变动都有可能导致测试结果的变化。 请参考其他信息及性能测试(包括结合其他产品使用时的运行性能)以对目标产品进行全面评估。
对本文件中包含的软件源代码的提供均依据相关软件许可而做出,任何对该等源代码的使用和复制均应按照相关软件许可的条款执行。
英特尔、Atom、凌动、Pentium、奔腾和 Intel 标识是英特尔在美国和/或其他国家的商标。
英特尔公司 © 2012 年版权所有。 所有权利受到保护。
*其他的名称和品牌可能是其他所有者的资产。
** 该示例源代码根据英特尔示例源代码许可协议发布