NDK Android* 应用移植方法

13 篇文章 0 订阅

NDK Android* 应用移植方法

概述

本指南用于帮助开发人员将现有的基于 ARM* 的 NDK 应用移植到 x86。如果您已经拥有一个正常运行的应用,需要知道如何能够快速让 x86 设备在 Android* market 中找到您的应用,本文将可以为您提供一些入门信息。同时本指南还提供了一些技巧和指南,以帮助您解决在移植过程中可能会遇到的编译器问题。

目录

  1. 概述
  2. NDK 概述
  3. 移植概述
  4. 移植技巧:从 ARM* 到 x86
  5. 结论

NDK 概述

原生开发套件(NDK)是一款强大的工具,将原生 x86 代码的强大功能和 Android* 应用的图形界面结合在一起。通过使用该工具,开发人员将能够提升某些应用的性能优势,但同时开发人员也需要十分谨慎,因为在某些情况并不能实现预期的效果。

NDK 致力于支持开发人员完成以下工作:

  • 编译Android* 应用包中使用(由上层封装程序 Java* 代码调用)的原生 C/C++ 库。
  • 基于x86(英特尔® 凌动™ 处理器)重新编译 ARM* 原生库,并在必要时进行移植。

对于以上提到的第二点,部分情况下可能只需要简单地修改 Build 标记并重新编译即可,但有时却并不这么简单。例如,如果原生库涉及到 C 代码内的内嵌汇编,那么代码将无法通过简单的汇编来实现在两种不同的架构自如运行的目标,此时将需要进行重新编写(更多信息请参见比较 ARM* 的 NEON* 与英特尔的 SSE 的部分)。

JNI 的性能影响和开销

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

开发人员可通过以下网址获取 NDK 的最新版本: http://developer.android.com/sdk/ndk/index.html. 在最新版本 NDK r6b 中,NDK 可用于构建基于 ARM* 和基于 x86(英特尔® 凌动™)的原生库。这为开发人员在一个应用包内进行原生代码移植提供了方便。

NDK 内部组件  Makefiles简介

开发人员将需要为工程创建一个 Android.mk 文件和一个 Application.mk 文件(可选)。其中,Application.mk 文件用于描述您的应用需要哪些原生模块。Android.mk 文件用于控制如何和从哪里构建一个模块(静态/共享库)。以下是一个简单 Android.mk 文件的片段:


图 1:简单 Android.mk 文件的内容

 

构件系统将在前端添加“lib”,生成名为 libtest.so 的库。在 LOCAL_SRC_FILES 中,开发人员将提供工程源文件的名称。LOCAL_LDLIBS 和 LOCAL_CFLAGS 分别用于指定链接标记和编译标记。

命令行构建

后面命令行提供了一个有关如何指定构建目标为 x86 架构的示例: ndk-build APP_ABI=x86

调用原生库可采用以下两种方法:System.loadLibrary(“relative_path_and_name”)和 System.load(“full_path_to_lib_file”)。前者更常用且更稳定。使用前者时,Android.mk 指定的库名称中的“lib”部分可丢弃。调用示例如下:


图 1:调用原生代码示例

 

此外,对于原生代码,开发人员需要确保原生代码的输入方法具有正确的 JNIEXPORT 方法签名,而不是典型的 C/C++ 标头。前面提及的 JNI 链接包含有更多相关信息。

处理原生库文件

开发人员可通过两种方式加载原生库:1) 在 Android* apk 包中提供该库并在运行时对其进行引用;2) 在 Android* 文件系统上提供通往该库的绝对路径。采用以上方式中的哪一种取决于开发人员的偏好。但无论采用哪种方式,均应进行相应的正确处理。

运行时验证

通过使用 adb logcat 命令,开发人员可确保在运行时成功加载目标原生库。以下提供了一个描述原生库已加载的系统日志的示例。注意提供了通往原生库文件的完整路径。


图 2:调用原生代码示例

总结

以上各部分提供了有关如何使用 NDK 的入门知识。如欲了解更加复杂的细节,请阅读 NDK 库中包含的相关文档。这些文档提供有出色的教程和针对各种应用的源代码示例。

移植概述

对于大多数应用而言,将现有的 NDK 应用移植到 x86 非常简单。除非原生代码使用 ARM* 特有的特性,否则移植应用只需进行重新编译、重新打包和重新发布操作即可完成。

以下内容向您介绍了将 NDK 应用移植到 x86 涉及到的步骤。

  1. 获取最新的 NDK 工具。x86 支持最先在 android-ndk-r6 中提供,但当时仍存在一些问题,之后谷歌很快进行了修复。确保您已经从 Android NDK 网站下载和安装了最新的NDK(在本文撰写之时,最新的 NDK 版本为 android-ndk-r6b)。 Android* NDK site.

     

  2. 如果您已有一个 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
  3. 在前一步中,我们可以发现包含每种架构的二进制代码的文件夹在 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
  4. 下一步为在英特尔架构设备或 x86 模拟器上进行运行和测试。验证所有二进制代码均已正确打包的最后一步为使用 zip 存档工具打开 APK 并确保其中包含二进制代码。以下是包括 x86 二进制代码时 APK 结构外观的截屏。

移植技巧:从 ARM* 到 x86

将应用移植到 x86 应当非常简单,尽管很多人可能都有这样的想法,但在实际的代码中,仍需要谨慎注意并解决英特尔® 凌动™ 和 ARM* 架构之间的差异。以下主题介绍了您在移植过程中可能遇到的问题以及如何予以解决。

工具链兼容性

您的构建环境有可能直接使用了工具链,而不是 Android* 构件脚本。在 ARM* 中,所用的路径如下:

android-ndk\toolchains\arm-linux-androideabi-4.4.3
x86所用的路径:
android-ndk\toolchains\x86-4.4.3
更多信息请参考NDK文档android-ndk/docs/STANDALONE-TOOLCHAIN.html.

内存对齐影响:比较 ARM* 与英特尔® 凌动™

在 ARM* 和 凌动™ 之间移植 C/C++ 代码时可能出现内存对齐不匹配的情况。以下文章针对这一点提供了一个典型的示例:/en-us/blogs/2011/08/18/understanding-x86-vs-arm-memory-alignment-on-android. 需要注意的是,开发人员应当在设计代码时,在必要的地方考虑对数据进行明确的强制对齐。否则,开发人员将无法保证数据在不同的平台能够得到正确的处理。

浮点预算:比较 ARM 与英特尔® 凌动™

目前,在构建 NDK 库时,可以使用三种支持的应用二进制接口(ABI):

  1. ‘armeabi’ – 默认接口,可创建出指向基于 ARM* v5TE 设备的二进制代码。具有这种目标的浮点运算使用软件浮点运算。使用此 ABI 创建的二进制代码将可以在所有 ARM* 设备上运行。
  2. ‘armeabi-v7a’ – 可创建出支持 ARM* v7 设备的二进制代码,将使用硬件 FPU 指令。
  3. ‘x86’ – 生成的二进制代码可支持包含基于硬件的浮点运算的 IA-32 指令集。

所有这些 ABI 选项均支持浮点运算。除非使用的是特定于 ARM* 的汇编指令,否则在将代码移植到 x86 时不会发生问题。其优势在于,如果碰巧您的应用仅针对‘armeabi’进行编译,而现在需要支持 x86,则您在进行大多数浮点运算时均能感觉到性能提升。

将 ARM* 的 NEON* 指令移植到面向英特尔® 凌动™ 的 SSE

尽管这篇短小的文章不可能包罗万象,但是以下提供的信息将能够让您大致了解在英特尔架构和 ARM* 中,SIMD 扩展的实施有何不同。这篇概述文章同时还为开发人员提供了一些工具,以便于开发人员开始进行一些简单的编码练习。

什么是 NEON?

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:英特尔推出的类似 NEON 的工具

SSE 指面向英特尔架构(IA)的SIMD 流指令扩展。目前,英特尔® 凌动™ 最高支持 SSSE3(补充 SIMD 流指令扩展 3)。凌动™ 暂不支持 SSE4.x。后者也是一个 128 位引擎,用于打包浮点数据。这一执行模式开始于 MMX 技术。SSx 是较新的技术,取代了 MMX。。如欲了解详细信息,请参阅英特尔《IA-32 和 IA-64 软件开发人员手册》中的“第一卷:基础架构”部分。网址为:http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html。目前,SSE 概述部分在 5.5 节。这一章节提供了 SSE、SSE2、SSE3 和 SSSE3 的 Opcode(操作码)。注意,数据运算通常会涉及到处理基于精度的打包浮点数值;并且需要在 XMM 寄存器之间,或在这些寄存器与内存之间批量传输数据。XMM 寄存器主要用于取代 MMX 寄存器。

NEON 与 SSE 在汇编层面的比较

在推荐使用前述《英特尔架构软件开发人员手册》来了解所有单个 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 

细粒度。在引用的简单汇编代码示例(再次注意,此处列举的事项并不代表开发人员在 NEON 和 SSE 之间可能发现的所有差异)中,请将面向 SSE(英特尔)的 ADDPS 指令与面向 NEON 的 VADD.ix (即 x = 8 或 16)进行比较。注意对于将被用作助记符一部分的数据,后者进行更精细的处理。

NEON 与 SSE 在C/C++ 层面的比较

在将 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 英特尔公司。保留所有权利。

英特尔、Intel、凌动和 Atom 是英特尔公司在美国和其他国家(地区)的商标。

本文所提供之信息均与英特尔产品相关。本文不代表英特尔公司或其它机构向任何人明确或隐含地授予任何知识产权。除相关产品的英特尔销售条款与条件中列明之担保条件以外,英特尔公司不对销售和/或使用英特尔产品做出任何其它明确或隐含的担保,包括对适用于特定用途、适销性,或不侵犯任何专利、版权或其它知识产权的担保。

除非经英特尔书面同意,英特尔产品并非设计或有意用于任何英特尔产品故障可能会引起人身伤亡事故的应用领域。

英特尔可以随时在不发布声明的情况下修改规格和产品说明。设计者不得依赖于带有“保留”或“未定义”的任何特性或说明。英特尔保留今后对其定义的权利,对于因今后对其进行修改所产生的冲突或不兼容性概不负责。此处信息可能随时更改,恕不另行通知。请勿使用本信息来对某个设计做出最终决定。

文中所述产品可能包含设计缺陷或错误,已在勘误表中注明,这可能会使产品偏离已经发布的技术规范。英特尔提供最新的勘误表备索。

订购产品前,请联系您当地的英特尔销售办事处或分销商了解最新技术规范。

如欲获得本文或其它英特尔文献中提及的带订单编号的文档副本,可致电 1-800-548-4725,或访问: http://www.intel.com/design/literature.htm

Optimization Notice in Chinese
Categories: 
For more complete information about compiler optimizations, see our  Optimization Notice.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值