安卓-x86-教程-全-

安卓 x86 教程(全)

原文:Android TV apps development

协议:CC BY-NC-SA 4.0

零、简介

我们撰写了 Android on x86:英特尔架构优化简介,为该主题的最佳实践和程序提供了一站式的详细资源。这本书涵盖了安装问题、硬件优化问题、软件需求、编程任务,以及在考虑为基于 x86 的 Android 设备编程时出现的性能优化。我们自己参与过相关的项目,我们致力于将我们的经验和信息收集到一本书中,这本书可以作为任何项目特定需求的指南。我们进行了微调优化、本机代码调整、硬件加速和多媒体应用的高级配置。

这本书不仅仅致力于代码,尽管你会在里面找到大量的代码样本和案例研究。相反,我们已经为 x86 平台上的 Android 提供了您需要的信息,以便充分利用 x86 架构。我们将指导您安装面向英特尔架构的 Android 软件开发套件,帮助您了解商用 Android 设备可用处理器之间的异同,教您创建和移植应用,调试现有 x86 应用,提供 NDK 和 C++ 优化解决方案,并介绍英特尔硬件加速执行管理器。我们收集的信息为您快速、出色地完成开发工作提供了最有用的帮助。

为什么在 x86 上使用 Android?

2011 年,我们在沟通方式上经历了一次范式转变。智能设备销量首次超过个人电脑销量。卫兵换岗有三个原因:

  • 我们对开放、持续交流的日益增长的专业和社会需求
  • 智能手机和平板电脑的低成本和引人注目的新功能
  • 移动应用的易用性和可用性提高

在接下来的几年里,手机上网很可能会超过笔记本电脑和台式机;我们用来交流的硬件可能会改变,但我们对随时随地连接的热情肯定会继续。

谷歌的 Android 操作系统占据了全球智能手机出货量 80%以上的市场份额,已经被证明是这场移动革命的领导者。Android 成功的关键原因是其开放的平台和灵活的合作伙伴关系。Android 开发者可以获得的大量开源资源刺激了更多应用的开发,给了消费者更多的选择。此外,开放平台支持竞争性和多样化的硬件环境。

随着高性能移动设备市场的扩大,谷歌已经与英特尔合作,展望 Android 的下一个前沿:让操作系统在内置英特尔架构的设备上运行。英特尔架构上的 Android 之旅非正式地始于 2009 年,当时一群开发人员启动了开源 Android-x86 计划,以便将 Android 移植到运行英特尔 x86 处理器的设备上。不久之后,随着官方 Android on Intel 架构项目的推出,英特尔开始为 Android 开源项目(AOSP)贡献代码和资源。2012 年,首款搭载英特尔处理器的安卓智能手机在全球范围内发布上市;到 2013 年底,具有前所未有处理能力的 Android 智能手机和平板电脑正在进入美国市场。最近,这两个组织承诺让 Android 在 64 位设备上运行,包括上网本、笔记本电脑和传统的台式电脑,这意味着在 2014 年,Android 将打入一个历史上由微软 Windows 和苹果 OSX 主导的市场。Android 将把其庞大的、蓬勃发展的应用开发人员社区带到广泛的设备和硬件架构上。

这种合作给双方都带来了很多好处。英特尔的 x86 架构拥有 35 年的优秀处理能力、成熟的开发者生态系统和一套成熟的开发工具。在性能方面,英特尔的最新芯片在高性能和低功耗之间取得了平衡,非常适合智能手机、平板电脑和上网本。原生 x86 仿真器支持是最新 Android SDK 版本的一个关键特性,英特尔致力于为开发人员提供大量工具,用于优化他们芯片的 Android 应用性能。

通过扩展到 32 位和 64 位架构,Android 的前景正在变得广阔。每天,越来越多搭载安卓系统和英特尔处理器的移动设备出现在货架上和我们的指尖上,而即将到来的基于英特尔技术的上网本和笔记本电脑将会把环境塑造成令人惊叹的东西。一种新的 Android 体验将会形成,它将保持多样性,并针对更大的屏幕、强大的多窗口和更快的处理器速度进行优化。这是一个令人兴奋的时刻,我们希望开发者能够抓住这个新的机会,拓展 Android 的视野。

这本书是给谁的?

这本书主要针对两类人群:开发者和那些有兴趣选择 Android x86 作为应用平台的人。考虑到这一点,开始的章节关注更高层次的非技术问题,这样来自所有技术背景的人都可以做出明智的选择。后面的章节重点关注开发人员方面,从微处理器架构和 Android 开发环境的基础开始,然后构建非常高级的、注重性能的内容。我们的目标是接触到对 x86 平台上的 Android 感兴趣的所有人,并尽最大努力为您提供您需要的答案。

我们真的希望你喜欢这本书。我们当然很喜欢探索这个话题,并期待着在未来几年看到这个快速发展的领域会发生什么。我们还想指出的是,虽然我们可能对 Android 略知一二,但我们认识到我们肯定不是对所有事情都最了解的。请随意质疑您在本书中找到的任何信息——我们鼓励您使用外部资源,并真正参与到围绕该技术的社区中来!

一、Android 操作系统的历史和演变

我要毁掉安卓,因为它是偷来的产品。我愿意为此进行热核战争。

—史蒂夫·乔布斯,苹果公司

Android,Inc. 的创始人有着明确的使命。根据 Android 创始人之一安迪·鲁宾的说法,Android Inc. 是为了开发“更智能的移动设备,更了解其所有者的位置和偏好。”鲁宾进一步指出,“如果人们聪明的话,这些信息开始被整合到消费产品中。”那是 2003 年,地点是加州的帕洛阿尔托。这是 Android 诞生的一年。

虽然安卓公司开始秘密运作,但今天全世界都知道了安卓。众所周知,Android 是现代智能手机、平板电脑和即将问世的笔记本电脑的操作系统,但这到底意味着什么呢?Android 以前是什么样子的?它是如何走到今天这一步的?所有这些问题以及更多的问题都将在这简短的一章中得到解答。

起源

2005 年,当价值数十亿美元的技术公司谷歌收购安卓公司时,安卓首次出现在技术雷达上。当时,人们对安卓以及谷歌打算用它做什么知之甚少。直到 2007 年,当谷歌宣布世界上第一个真正的移动设备开放平台时,信息才变得稀少。

Android 第一次发行

2007 年 11 月 5 日,开放手机联盟发布了一份新闻稿,为 Android 平台的未来奠定了基础。该联盟将 Android 的一些目标表述为:“促进移动设备的创新,并为消费者提供比当今移动平台上的大部分产品更好的用户体验。”

当时,全球使用的手机数量超过 20 亿部,而截至 2010 年使用的手机数量为 46 亿部。然而,提供移动设备的各个公司之间没有平台的协调。随着 Android 的引入,单一操作系统消除了重新实现手机应用和中间件的需要。开发新设备的公司现在可以更专注于硬件和底层组件。

但是这些公司并不是唯一从安卓发布中受益的公司;软件开发人员现在可以将应用发布到多种设备上,只需对底层代码做很少的修改。这使得开发人员可以花更多的时间在这些手机运行的应用上,并创建我们都习惯的丰富而令人印象深刻的应用。这部分是由于 Android 背后的开源哲学和 Apache 许可证,这是大多数 Android 源代码使用的许可证。

开源 Apache 许可

Apache 许可证只是开源社区中存在的许多不同许可证之一。尽管所有这些许可证都有所不同,但它们都促进了相同的开源思维,这可以总结如下:

“自由软件”是自由的问题,而不是价格的问题。为了理解这个概念,你应该把“免费”理解为“言论自由”,而不是“免费啤酒”。

理查德·史泰曼

Apache 许可证特别授予出于任何目的使用软件的自由,以及分发、修改或分发修改版本的能力。Apache 许可证也是许可的,这意味着修改后的版本不必屈从于 Apache 许可证。有关 Apache 许可的更多信息,请访问http://www.apache.org/licenses/LICENSE-2.0

安卓是什么?

那么 Android 到底是什么?Android 操作系统是运行在全球超过 4 亿台设备上的开源技术栈。这个技术堆栈由各种组件组成,允许开发人员和设备制造商独立工作。这可以分为五个主要部分——应用、应用框架、本地库、Android 运行时和 Linux 内核——如图图 1-1 所示。

9781430261308_Fig01-01.jpg

Android OS(维基百科)http://en.wikipedia.org/wiki/File:Android-System-Architecture.svg

图 1-1 。Android 系统架构

应用

应用存在于最高层。这些都是每个使用 Android 的人最熟悉的工具。Android 配备了各种支持日常电话需求的强大应用,如消息传递、电子邮件、互联网浏览和各种第三方应用。这些应用主要用 Java 编程语言编写。在最近一次与甲骨文的法律诉讼中,谷歌的安卓主管安迪·鲁宾解释了他为什么选择 Java 作为开发者的使用语言。Rubin 的主要观点是 Java 有一个众所周知的品牌名称,而且全世界几乎所有的大学都教授 Java。

这些应用通过各种方式分发,最常见的是来自谷歌 Play 商店(以前的 Android market place);然而,Android 操作系统也支持通过 USB 连接和 SD 卡安装应用。

应用框架

Android 为开发人员提供了为用户创建广泛的、交互式的、丰富的图形应用的能力和工具,并且旨在将这些应用部署到谷歌 Play 商店。开发人员可以访问核心应用内部使用的相同 API,也可以访问几乎所有现有的 Java 库。Android 应用的开发流程请参考 第六章:安装 Android SDK 进行英特尔应用开发。

本机库

下一层是岔路口。原生库和 Android 运行时存在于大致相同的空间。本机库是 Android 系统所依赖的编译和预装的 C/C++ 二进制文件。这些包括图 1-1 绿色部分的所有库。接下来的章节描述了 Android 中一些比较突出的本地库和它们的功能。

地面管理器

这通常被称为 Android 的窗口管理器。Surface Manager 用于合成任何单个屏幕的外观。它还做一些更微妙的事情来帮助 Android 顺利运行,如屏幕外缓冲和过渡。

SQLite〔??〕〔??〕

这是一个用于在 Android 设备的会话间保存信息的数据库。在 Android 上,SQLite 数据库存储在设备的内部存储器中,因此 SD 卡可以互换,而不会丢失设备特定的信息。

WebKit

WebKit 允许快速有效地将 HTML 渲染并显示到 Android 上。这是 Android 系统中的默认浏览器引擎,可供系统和第三方应用使用。

OpenGL/ES

OpenGL 引擎处理 Android 中的图形。OpenGL 可以在 Android 上渲染 2D 和 3D 对象。这也支持带有专用图形芯片的设备上的硬件加速。

Android 运行时

Android 运行时内部有两个主要组件:Android 提供的核心 Java 库和 Dalvik 虚拟机。Dalvik 虚拟机是 Google 对 Java 的实现,它被优化用于移动设备。Dalvik 内部更具体的差异是非常技术性的,不在本书中讨论。

Linux 内核

最后一层是 Linux 内核。Android 最初基于 Linux 2.6 内核,并针对移动使用进行了一些优化。当前版本的 Android 基于 Linux 3.1 内核。Linux 内核提供了尽可能接近硬件的访问。因此,驱动程序是在内核空间中编写的,以尽可能快速高效地运行。这些包括控制内部无线电、打开立体声和摄像头、处理电源和电池充电,以及操作设备上的物理键盘或按钮。与 Android 一样,Linux 内核也是一个开源项目,并被广泛使用,尤其是在企业环境中的服务器上。

开放手机联盟

2007 年 11 月,开放手机联盟(OHA) 由致力于开发开放移动标准的 34 个创始成员成立,包括谷歌、移动设备制造商、应用开发商、嵌入式系统开发商和商业化公司。网站中描述的该联盟的目标如下:

Open Handset Alliance 是一个由 84 家技术和移动公司组成的团体,旨在加速移动领域的创新,为消费者提供更丰富、更便宜、更好的移动体验。

目前,OHA 有 84 家公司正在开发和从事该联盟迄今为止唯一的主要项目——Android。由于 OHA 成员提供的服务和产品,设备和相关服务以更低的价格生产出更高的质量。

Android 开源项目

收购安卓公司后,安卓开源项目(AOSP)成立,并由谷歌领导。AOSP 负责 Android 软件栈的开发和维护。如 Google 所述,该项目的目标如下:

Android 开源项目的目标是创造一个成功的现实产品,改善最终用户的移动体验。

Android 的设计和维护考虑到了向后功能。这意味着新设备可以运行早在 Android 的 Cupcake (1.5)上开发的应用。Android SDK 的官方支持只追溯到 Cupcake (1.5),所以为 Cupcake 之前的设备编写的应用不能保证在最新的 Android 设备上运行。

在 AOSP 的发展过程中,已经有许多不同版本的 Android 发布在移动设备上。当新的 Android 版本发布时,移动设备所有者可以选择是否升级他们的操作系统。随着 Android 的每一次迭代,新的 SDK 都可供开发人员使用,各种新功能也添加到受支持的设备中。软件开发人员在开发新的应用时,需要注意以前版本的遗留特性。

天文 (1.0)

Astro 是 Android 的起点,于 2007 年 11 月作为测试版发布,并于 2008 年 9 月在 HTC Dream 上向公众发布。Astro 展示了 Android 操作系统的各种核心功能,包括许多 Android 用户现在知道和喜欢的应用。这些应用包括安卓市场、网络浏览器、电子邮件/Gmail、谷歌地图、信息服务、媒体播放器、YouTube 以及其他各种应用。

纸杯蛋糕(1.5)

2009 年 4 月 30 日发布的 Cupcake 是 Android 下一个登陆商业市场的主要版本。Cupcake 基于 Linux 内核 2.6.27,并为用户和开发人员提供了许多新特性。主要的变化是支持虚拟键盘,支持主屏幕上的小部件,在不同地方添加动画,以及支持蓝牙设备的自动配对和立体声。有趣的是,从纸杯蛋糕开始,迄今为止所有的安卓版本都以甜点命名。

甜甜圈(1.6)

2009 年 9 月 15 日,谷歌发布 Android 版本,命名为 Donut。Donut 带来了从 2.6.27 到 2.6.29 的 Linux 内核更新,以及一些新特性和支持的设备。主要功能包括联系人/网页/书签的语音和文本搜索,支持 WVGA 屏幕,以及相机功能和速度的改进。Donut 是 1.x 系列中最后一个发布的 Android 版本。

闪电(2.0/2.1)

clair 于 2009 年 10 月 26 日发布,它继续构建在 Linux 内核版本 2.6.29 上。SDK 2.0 版为开发者和消费者带来了许多新的特性和功能。Android 在有能力的设备上的外观和感觉发生了巨大的变化,包括许多不同应用的速度显著提高。Android 2.0 的首要设备是威瑞森无线的摩托罗拉的 droid。

2009 年 12 月 3 日,谷歌将 Android 更新到 2.0.1 版本,努力修复一些小错误,并为开发者更新 API。直到 2010 年 1 月 12 日,Android 才搬到 2.1 版本。与 12 月的更新类似,2.1 版本主要包括对底层 API 的更新和错误修复。

弗罗约(2.2.x)

2010 年 5 月 20 日,Android SDK 版(Froyo)发布,搭载 Linux 内核 2.6.32。谷歌的 Nexus One 是市场上第一款展示 Froyo 及其新功能的设备。Froyo 增加了非常重要的功能,包括 Adobe Flash 支持、Android 云到设备的消息传递、Wi-Fi 热点功能和显著的性能优化。需要注意的是,Android SDK 建议选择 Froyo 作为您的基础开发版本,以达到 Android 用户的最大当前用户群。

Android 2.2 SDK 随后发布了三个更新:2011 年 1 月 18 日的 2.2.1、1 月 22 日的 2.2.2 和 11 月 21 日的 2.2.3。这些更新主要是对 Android 的错误修复和安全更新。

姜饼(2.3.x)

Gingerbread 于 2010 年 12 月 6 日发布,基于 Linux 内核 2.6.35。与 Froyo 的发布类似,谷歌的 Nexus S 也是为了展示姜饼而推出的。Gingerbread 的功能包括支持 WXGA 和其他超大屏幕尺寸,改进虚拟键盘,支持更多内部传感器(即陀螺仪和气压计),支持多个和前置摄像头,以及读取近场通信 (NFC)标签的能力。

从 2011 年 2 月到 9 月,Gingerbread 2 . 3 . 3–7 发布了五个更新。这些更新带来了各种功能、安全更新和错误修复。引入的最重要的功能之一是开放附件支持,它允许兼容设备作为兼容软件平台的 USB 外设。

蜂巢(3.x)

2011 年 2 月,摩托罗拉 Xoom 上发布了第一个仅支持平板电脑的 Android 版本 Honeycomb。因为 Honeycomb 是专门为平板设备设计的,所以 Android 被调整以允许更大的屏幕空间带来更愉快的体验。这包括对屏幕键盘的重新设计,允许快速访问通知和导航的系统栏,允许更轻松地使用网络的多个浏览器选项卡,以及对多核处理器的支持。

在当前的生命周期中,Honeycomb 已经更新了六次,其中两次是主要的。第一次更新是 2011 年 5 月 10 日的 Android SDK 版本 3.1,即增加了对 USB 附件的支持,如键盘、操纵杆和其他人机界面设备(hid)。SDK 的第二次重大更新是 2011 年 7 月 15 日的 3.2。3.2 最显著的特点是兼容不是为平板电脑设计的 Android 应用的显示模式。Honeycomb 的最近四次更新是小的改进、错误修复和安全更新。

冰淇淋三明治(4.0.x)

冰淇淋三明治(ICS) 发布于 2011 年 10 月 19 日,基于 Linux 内核 3.0.1。三星的 Galaxy Nexus 是上市时与 ICS 一起发布的设备。ICS 对 Android 用户界面(UI)进行了大量的功能和改进。一些功能包括可定制的启动器、选项卡式网络浏览器、解锁设备的面部识别、内置照片编辑器、UI 的硬件加速以及最初在 3.x (Honeycomb)中引入的软件按钮。值得注意的是,ICS 将 3.x 版(蜂巢)和 2.3.x 版(姜饼)合并为一个支持手机和平板电脑的单一操作系统。

从 2011 年 11 月到 2012 年 3 月,已经发布了四个针对 ICS 设备的小更新。这些更新集中在稳定性改进、相机性能和错误修复上。

糖豆(4.1.x)

果冻豆于 2012 年 7 月 9 日发布,基于 Linux 内核 3.1.10。华硕的 Nexus 7 平板设备是果冻豆的旗舰用户。Jelly Bean 发布了许多对 Android 用户界面和音频的改进和性能升级。2012 年 11 月 13 日发布的基于 Linux 内核 3.4.0 的 4.2 版增加了可访问性改进。4.3 版本于 2013 年 7 月 24 日发布,添加了 OpenGL ES 3.0 支持,以获得更好的游戏图形、安全性增强和升级的数字版权管理 API。Jelly Bean 版本的其他功能包括可定制的键盘布局、可扩展的通知、特定于应用的通知过滤和多声道音频。

KitKat (4.4.x)

撰写本文时,Android 的最新版本 KitKat 发布于 2013 年 9 月 3 日。它的功能包括内存更少的设备的性能优化,扩展的可访问性 API,无线打印能力,以及一个新的实验性运行时虚拟机,称为 ART,它可能会取代 Dalvik。KitKat 于 10 月 31 日在谷歌自己的 Nexus 5 智能手机上首次亮相。2013.

概观

通过所有这些版本的 Android,操作系统的功能和变化带来了丰富的以用户为中心的体验。对该设备的技术方面知之甚少甚至一无所知的普通用户可以像第二天性一样操作该设备。现在,您已经对使这成为可能的底层系统和架构有了更多的了解,剩下唯一要问的是,下一步是什么?

二、移动设备和操作系统的前景

到 2020 年,联网设备将达到 500 亿台。

—爱立信,2010 年

联网计算机。连接的设备。移动设备。机器对机器(M2M)。移动中(OTG)。便携式计算。智能服务。这份名单越列越多。用来描述向其他机器发送数据的机器的术语似乎每天都在变化。所有这些不同类型的设备看似相似,很容易混淆。

Android 肯定不是当今市场上设备上使用的唯一操作系统。各种手机操作系统已经存在,很多还在和安卓竞争。虽然这本书的重点是 Android 市场,但对竞争的理解——iOS、Windows Phone 等等——对任何成功的商业冒险都很重要,因此也是本章的重点。中国著名的将军和战略家孙子在他的《孙子兵法》中最好地表达了这一点:“如果你了解敌人和了解自己,你就不必害怕一百场战斗的结果。”

移动领域的竞争

Android 操作系统是当前商业市场中非常受消费者欢迎的选择。如今有超过 2.5 亿台安卓设备在使用。但 Android 不是唯一的选择,也不是第一个实现的移动操作系统。还有几个额外的移动操作系统,包括苹果的 iOS,英特尔和诺基亚的 MeeGo,微软的 Windows Phone 等等。这些操作系统的实现有许多不同之处,使用这些平台也有许多不同的原因。本节通过讨论它们的优缺点,提供了这些其他移动操作系统的简要概述。

iOS

由苹果公司开发和发行的 iOS 最初于 2007 年在最初的 iPhone 和 iPod Touch 上发布。仅次于 Android,iOS 目前是移动操作系统领域市场份额最接近的竞争对手。截至 2012 年 5 月,Android 拥有 50.9%的用户,而 iOS 平台为 31.9%。值得注意的是,iOS 最初被称为 iPhone OS,是基于它的主要启动设备 iPhone。

概观

与 Android 不同,iOS 从一开始就是一个封闭的源代码,只在有限的几个平台上发布。iOS 的每个新版本都包括主要由苹果公司开发和制造的新 iOS 设备。iOS 大致基于苹果的桌面操作系统 OS X,而后者又部分基于 UNIX 操作系统。(OSX 和 Linux,以及 iOS 和 Android,在 BSD Unix 中共享一个共同的开发祖先,BSD Unix 是由加州大学伯克利分校开发和发布的开源 Unix 操作系统变体)。

应用

iOS 附带了各种系统应用,如基本的电话操作、网络浏览器(Safari)、媒体播放器和电子邮件客户端。iOS 还能够运行开发者使用苹果 iOS 软件开发工具包(SDK ) 开发的各种第三方应用。应用可以访问所有设备外设,通常包括各种摄像头、用于检测设备移动的加速度计、麦克风、用于硬件加速的板载图形芯片和触摸屏。

Android 操作系统的第三方应用供应存在一些显著差异。为了开发 iOS 设备的应用,您需要购买 SDK 的开发者许可证。iOS 设备的应用通常用 Objective-C 编写,通常用 Xcode 开发,Xcode 是 OS X 平台的开发环境。此外,使用 SDK 创建的应用在 iOS 市场上销售之前,会经过苹果公司的筛选和验证。这使得苹果能够阻止开发者发布可能伤害其用户群的应用,如恶意软件或信息窃取者。

曾经有过苹果停止发布有效应用的情况。在一个案例中,一个第三方应用使用设备的音量按钮作为按下屏幕按钮的替代方法来用相机拍照。意识到这一点后,苹果从 App Store 中移除了该应用,声称这一功能“违反了苹果的政策”。第三方公司删除了这一功能,苹果最终发布了包含这一功能的机载摄像头更新。

平台

运行 iOS 的设备是通过苹果开发和销售的,因此差异很小。尽管这看起来有局限性,但是硬件多样性的缺乏使得应用能够被标准化。例如,由于只有几种可能的屏幕尺寸和图形硬件,应用开发人员只需处理几种不同的情况。iOS 主要出现在三个主要平台上——iPhone、iPad 和 iPod Touch。

  • iPhone——iPhone 是苹果的智能手机版本,最初于 2007 年 1 月发布。iPhone 的每一个新版本都包含了 iOS 的增量更新和新的主要功能。iPhone 的特点是一个口袋大小的设备,一个多点触摸屏,背面有一个摄像头(在新版本中前面有),还有一个用于音频的麦克风。
  • iPad——发布于 2010 年 4 月,iPad 是苹果公司创造和销售的平板电脑。它大约是一本标准杂志的大小。iPad 的屏幕比 iPhone 大得多,硬件也有所升级。每一代 iPad 都增加了重要的硬件升级和新功能。iPad 运行与 iPhone 和 iPod Touch 相同的应用;但是,如果需要,可以专门为 iPad 创建应用。
  • iPod Touch——几乎在所有方面都与 iPhone 相似,第一代 iPod Touch 于 2007 年 9 月上市。iPod Touch 和 iPhone 的主要区别在于 iPod Touch 缺少蜂窝通信功能。大多数为 iPhone 开发的应用都可以在 iPod Touch 上运行,开发者只需修改很少的代码,甚至不需要修改。iPod Touch 提供了一个在 iOS 上玩游戏的选项,无需支付 iPhone 手机套餐的订阅费或 iPad 的额外费用。

黑莓

黑莓手机有时被称为原始智能手机,于 2003 年推出。截至 2013 年第三季度,黑莓占据了 3%的移动智能手机市场份额。最初的黑莓手机有一个小的彩色屏幕,一个完整的 QWERTY 键盘,一个轨迹球和一个摄像头。与苹果的做法类似,黑莓设备由 Research in Motion (RIM) 在内部开发制造,该公司于 2013 年初更名为黑莓。黑莓最初的营销目标是为普通商务人士创造设备。这一重点包括检查电子邮件、访问互联网以及轻松高效地安排会议的能力。

Windows Phone

Windows Phone 由微软开发,作为 Windows Mobile 的继任者,是移动操作系统领域的第四大竞争对手。Windows Phones 于 2010 年 11 月进入消费者市场,与 Windows Mobile 不同,它的目标是远离企业市场。截至 2013 年第三季度,Windows Phone 和 Windows Mobile 占据了 2%的移动市场份额。Windows Phone 的布局与传统的智能手机用户界面有很大不同。微软非常注重易用性,以及与现有 Windows 服务(如 Windows Live)的连接。

Symbian

塞班操作系统是由埃森哲为诺基亚开发的,埃森哲是世界上最大的咨询和技术服务公司之一。截至 2012 年 5 月,塞班已经从 2009 年 2 月的 47%下降到 1.1%。Symbian 的功能包括各种应用、多点触摸屏、Wi-Fi、蓝牙和多任务处理能力。

MeeGo

2010 年 2 月,英特尔和诺基亚在世界移动通信大会上宣布了他们最新的冒险产品 MeeGo。MeeGo 是一个基于 Linux 的开源操作系统,面向各种移动设备。MeeGo 旨在运行在低性能设备上,如上网本、平板电脑、车载信息娱乐设备、智能电视和各种其他嵌入式系统。MeeGo 的用户界面非常类似于 Android,有各种各样的应用。2011 年 9 月,MeeGo 项目被取消,英特尔团队将他们的经验和技能带到了 Tizen,这是英特尔和三星之间的一个新的联合项目。

安卓之前

自从支持 Android 的设备出现在世界上和我们的口袋里以来,这似乎是一个漫长的过程。然而,大量的脚步和几位前辈导致了 Android 的创造和创新。尽管在 Android 出现之前,没有什么东西与它非常相似,但它的常见和受欢迎的功能显然是有启发的。

智能手机历史

在开放手机联盟移动设备出现之前,运行在这些设备上的软件是专门为每部新手机开发的。为 Android 操作系统做出的一些决定可以追溯到 21 世纪初的手机。

西蒙个人通讯器

许多人认为 IBM 和 BellSouth 的 Simon Personal Communicator (1994)是第一部智能手机。Simon 将个人数字助理(PDA)的许多功能与现有蜂窝设备的功能相结合。除了能够进行蜂窝通信,Simon 还有一个触摸屏和各种应用,如日历、游戏、记事本、计算器和触摸屏键盘。西蒙在拉斯维加斯的一次电脑交易会上启动了智能手机市场,西蒙的样机引起了人们的极大兴趣。西蒙的原型如此受欢迎,以至于在贸易展的第二天就出现在了今日美国的财经版的头版。

诺基亚 9000(诺基亚通信器)

1996 年推出的诺基亚 9000 在许多方面与西蒙相似,延续了智能手机的愿景和方向。诺基亚 9000 采用了双重方式——合上时看起来像一部笨重的手机,打开时显示出完整的 QWERTY 键盘和更大的水平屏幕。像西蒙一样,诺基亚 9000 的特色是各种应用,允许超出常规蜂窝设备的功能。

京瓷 6035

五年后于 2001 年发布的京瓷 6035 看起来更像现代智能手机。当关闭时,京瓷有物理按钮用作拨号键盘。当打开时,它有一个更大的垂直屏幕,包含各种应用和工具。京瓷的特色是 Palm OS,可以收发电子邮件和浏览网页。

黑莓 5810

RIM 发布的第一款黑莓手机是黑莓 5810 (2002 年)。它的特色外观一直伴随着黑莓直到今天。黑莓 5810 针对电子邮件和商务用途进行了优化,面向商务专业人士销售。功能包括一个大触摸屏,一个完整的 QWERTY 键盘和一个内置天线。

移动市场:成功与失败

在任何一个发展中市场,新的想法和创新可能会引起消费者的极大兴趣,也可能会让他们完全失去兴趣。移动领域也不例外。尽管许多移动设备销量很好,但也有许多设备损失惨重。本节重点介绍了一些最近在商业市场上成功和失败的移动设备。

摩托罗拉 i1

2010 年 6 月发布的摩托罗拉 i1 就是一个不太成功的移动设备的例子。虽然它是在 Boost Mobile 上发布的,没有必要签订合同,但 i1 只能运行 Android OS v1.5 (Cupcake),只能处理 2G 数据速度。与摩托罗拉 2010 年的其他设备 Droid X 相比,i1 卖得很差。

Droid X

Droid X 于 2010 年 7 月发布,远非一次失败。Droid X 采用 Android OS v 2.1–2.3,4.3 英寸多点触摸屏,8GB 内部闪存。随着 iPhone 4 的即将发布,摩托罗拉用 Droid X 进行了一场积极的营销活动,在 iPhone 4 上市的前一天宣布了这款设备。这似乎得到了回报,因为 Droid X 在网上和许多零售点销售一空。

黑莓火炬

黑莓 Torch 搭载了黑莓 OS 6 操作系统,RIM 首席执行官吉姆·巴尔西利(Jim Balsillie)称之为“超越任何现有产品的巨大飞跃”。然而,火炬在发布的前三天只卖出了 15 万台。相比之下,苹果 iPhone 3G 和 3GS 的销量都超过了 100 万部。在一个非常好的产品市场中,火炬是一个好产品的典型例子。虽然 150,000 台是一个相当大的数字,但这意味着黑莓无法维持其市场份额。

iPhone

作为有史以来最成功的设备之一,苹果的 iPhone 设备几乎每一次发布都能卖出数百万台,是市场成功的一个典型例子。专注于易用性和演示,iPhone 提供了不同的移动体验。苹果对 iPhone 的营销重点可以用简单来形容,用苹果的 logo 作为俘获过往 iPod 粉丝的手段。

移动市场:趋势

虽然不能保证未来,但明显的趋势有助于预测市场下一步的走向。移动领域也不例外;事实上,在某些情况下,它们甚至更容易辨认。移动设备及其用户之间的联系是一种不断发展的情况,创造了一个充满新可能性的世界。这种“网络纤维”可以让用户随时与周围的世界保持联系。

位置

大多数现代设备都有某种 GPS 或其他方法来定位你在世界上的位置。许多更受欢迎的应用已经利用这一特性来鼓励用户在旅途中使用他们的设备。无论您是在查看当前温度,在脸书更新中标记您的位置,还是试图找到回家的路,定位服务的使用越来越频繁。

随着 iOS 5 的发布,苹果在其核心操作系统中引入了位置功能。例如,当你路过某个地点时,你的移动设备可以提醒你需要去取杂货。这个功能的吸引力是显而易见的——不用考虑具体的时间,下次你经过药店时,系统会提醒你需要买更多的阿司匹林。在智能手机开发的所有领域,将定位功能嵌入应用是当前的热门话题。

当前手机用途

有了 Android 和所有其他操作系统,用户对于如何使用他们的移动设备有了无限的选择。但是消费者是如何使用他们的设备的,他们在设备上花了多少时间?

根据 2013 年 5 月皮尤互联网与美国生活研究,56%的美国成年人现在是智能手机用户。手机最常见的两种用途是浏览网页和搜索特定信息,这两种用途都占据了手机使用时间的绝大部分。脸书和 YouTube 拥有非常大的流量,截至 2013 年,脸书拥有超过 8 亿移动用户。

在所有智能手机用户中,约 59%的人每天在智能手机上使用网络应用和工具的时间超过 30 分钟。然而,每天 30 分钟内真正通过电话和短信交流的人的比例要低得多,只有 32%。随着我们的手机应用变得越来越丰富,电话等旧的交流形式已经转变为更新的社交信息形式,如脸书、Twitter 和 MySpace。

商业

由于移动设备几乎可以做笔记本电脑或家用电脑能做的任何事情,移动设备直接用于商业只是时间问题。无论是从亚马逊购买新产品,从应用商店购买应用,还是购买周日比赛的门票,移动设备都已经成为在旅途中购买商品和服务的一种方式。

移动商务市场正处于起步阶段。专家认为,到 2015 年,我们在手机上的花费将从不到 10 亿美元增加到超过 990 亿美元。

概观

连接现代社会的“网络纤维”非常明显。我们的移动设备让我们随时随地与世界联系。让我们如此轻松地访问这个丰富环境的应用和操作系统就像胶水一样将我们的世界粘在一起。下一章将详细讨论 Android 设备如何与现有技术交互,以及开发者可以使用什么样的界面。

三、超越移动应用——技术基础

在这一行,当你意识到自己陷入困境时,已经来不及自救了。除非你一直害怕,否则你就完了。

—比尔·盖茨

移动设备与我们的生活息息相关,不仅仅是简单的通信。联网设备就在我们身边:Wi-Fi 相框、汽车上的蓝牙接收器,甚至无线耳机。这些设备让我们能够以一种前所未有的方式与时俱进,与周围的一切保持联系。手机让我们以一种全新的方式与世界互动。

连接的设备

据英特尔公司副总裁 Kirk Skaugen 预测,未来几年将有超过 150 亿台设备连接到互联网。互联网目前支持超过 40 亿台联网设备,允许几乎不间断的通信。

支持这些连接的计算机芯片价格低廉,催生了一波联网设备的浪潮。以前没有无线功能的设备现在有了,这改变了我们与它们交互的方式。例如,现代电视包括无线芯片,允许它们直接通过互联网接收流媒体电视和电影。

家庭计算

这可能感觉像是旧闻,但家庭计算仍然是一个非常合法和有利可图的业务。在美国能源信息管理局进行的一项研究中,只有不到 3000 万美国人没有某种类型的家用电脑。与拥有一台或多台个人电脑的 8000 万美国人相比,美国比以往任何时候都更加互联互通。

自从智能手机问世并进入市场以来,个人电脑上的应用已经走过了漫长的道路。从智能手机使用和控制家用电脑的能力已经起飞,反之亦然。您可以在旅途中查看桌面,将文件和联系人从手机同步到笔记本电脑,将手机用作遥控器,或将手机中的视频直接传输到电视机。

汽车

现代汽车装载了大量的新技术。你可以坐在后座上看电视,通过蓝牙传输音乐,或者用免提与世界上的任何人通话。

智能手机为我们日常依赖的交通工具增加了更多的功能。通过蓝牙进行免提通话的功能通常包含在现代智能手机中。这种功能允许您在旅途中与他人进行合法交流。GPS 是智能手机上使用的另一种车内应用。你不再需要一个单独的 GPS 设备来绘制你的路线,这大大损害了独立的 GPS 设备市场。将这些设备的所有功能直接移植到智能手机上增加了便利性和易用性。此外,您还可以通过辅助端口将音乐传输到汽车的扬声器,找到您所经过的城市中最便宜的汽油,并找到正在发生的事情。

与汽车直接互动的应用才刚刚起步。随着手机变得越来越强大,汽车推出了更多的技术接口,新的智能手机应用将为此而创建。

数字娱乐

一个新的数字娱乐时代即将到来。现代世界的互联本质为娱乐设备和服务创造了新的可能性和用途。现在有多种方式可以随时通过互联网传输电视、电影和音乐。娱乐业接受并鼓励这些服务,通常提供付费点播内容。

随着这些功能的增加,智能手机应用开发人员为手机创造了新的用途。您可以将手机用作电视遥控器、从特定设备的 HDMI 端口播放电影的媒体中心,或者从收音机播放音乐的便携式媒体播放器。

特殊要求

市场的公共部分并不是唯一使用智能手机的地方。现代通信设备有许多军事和私人用途。无论是专门构建的加固设备、超远程功能还是绝密级通信,这些类型的设备都有许多特殊要求。

加固

有时也被称为硬化,加固 涉及改变设备,以便它可以在比普通用户可能遇到的更激烈的条件下使用。这些设备经常用于军事应用,可能需要在水下、沙尘暴中或雨中使用,或者可能需要在从很远的地方坠落后经受住冲击。技术规范准确地记录了这些设备必须能够处理什么,才能获得军事应用许可。

入口防护等级

入口防护(IP)等级代码 是一种针对外力防护等级进行分类的标准。IP 编码标准规定每个分类包含两个代表其物理和液体保护程度的字符。如果没有保护措施,则在该位置放置一个 X。

第一个数字是它对固体颗粒和物体的防护。你对第一个角色的最高评价是 6 分。得分为 6 意味着该设备具有完全的接触保护,以及没有灰尘进入。第二个字符描述了对液体,特别是水的保护。最高的水等级是 8 级,这表明该设备可以完全浸没在水下超过一米,任何时间都不会受到内部损坏。

IP 代码只是各种保护分类标准中的一种。在美国国内,也有国家电气制造商协会(NEMA) ,发布保护等级。在美国以外,有 IP 代码规范的扩展,以及全新的系统。例如,称为 IP69K 的德国标准适用于高压和高温环境。

医疗

医学界有自己的要求清单。医疗数据的信息和使用必须保密。在美国,医患保密协议非常重要,这些设备必须受到保护。在全国范围内选定的医院中,Android 平板电脑运行并包含敏感信息。患者的电子病历(EMR ) 包含了患者的整个病史,属于敏感信息。

为了保护这些设备,信息被加密和锁定。使用该设备需要密码认证,以及可能的二级认证形式以确保身份。这些设备还具有屏幕和设计,使医疗专业人员的生活更容易。这通常意味着大屏幕,易于阅读,应用专门迎合医疗情况。

虚拟化

硬件价格昂贵,在单个硬件设备上创建虚拟平台已经成为一种常见的做法。这节省了资金和资源,因为您可以随时替换虚拟实例,而无需接触硬件。虚拟实例还允许您与系统交互,而不需要任何特定的硬件。例如,运行在 Android 模拟器上的虚拟 Android 设备允许用户与它进行交互,就像应用部署在实际设备上一样。

安全通信

保持军事通信安全一直是一个挑战。在野外发送和接收安全通信是一个非常困难的问题。安全对话的能力是对抗力量的强大优势。安全通信的困难是一个多部分问题,很少有答案。您必须建立一种加密、解密和传输通信的方法,尽可能减少延迟。此外,您必须提供一些方法来确定谁该相信谁不该相信。

由于所有这些复杂因素,军方使用的硬件和软件往往比消费级产品落后好几年。在 Android 设备上,各公司一直在努力开发将用于战场和政府建筑的安全通信。

类型 1

美国国家安全局(NSA ) 已经认证了一种类型 1 的设备,或系统,用于安全信息。类型 1 认证是一个严格的过程,涉及广泛的测试和形式分析。一些分析领域包括密码安全、防篡改、制造和辐射测试。

联邦信息处理标准

联邦信息处理标准(FIPS )认证授予软件和计算机系统使用敏感和高度机密信息的权限。FIPS 有不同的分类,处理不同的标准。例如,FIPS 46-3 是美国数据加密标准(DES ) 的代码,而 FIPS 197 则处理高级加密标准(AES ) 。

我们互联世界的网络纤维

现代世界已经变得非常依赖于持续的连接,人们很容易忘记存在多少种连接形式。这些连接方法都有不同的好处和限制。为了找到符合您业务需求的正确匹配,对这些方法如何操作有一个坚实的技术理解是很重要的。

蜂窝网络

蜂窝网络是难以置信的技术和复杂的系统;即使是小型网络也需要专门的硬件和软件。蜂窝网络通过使用蜂窝塔从一点到另一点传输数据。蜂窝网络有两种主要的通信协议——全球移动通信系统(GSM ) 和码分多址(CDMA ) 。这些协议允许多种类型的数据从一个设备流向另一个设备。他们的数据包括语音通话、短消息服务(SMS ) 、多媒体消息服务(MMS ) 。开放移动联盟(OMA)对蜂窝网络上提供的许多协议和服务的技术细节和规范进行了标准化。

开放手机联盟

开放移动联盟 成立于 2002 年 6 月,是移动电话行业的标准组织。OMA 负责维护和创建移动领域的许多标准,并与各种标准团体合作。其中比较著名的有第三代合作伙伴计划(3GPP ) 、第三代合作伙伴计划 2 (3GPP2 ) 、互联网工程任务组(IETF ) 和万维网联盟(W3C ) 。

无线通信

无线通信在两个或更多没有物理连接的端点之间传输数据。这种连接的距离可以短至几英寸,也可以远至几十万英里。存在许多形式的无线通信,其中最流行的是 Wi-Fi、蓝牙和射频(RF)。每种方法都有不同的优点和问题。

Wi-Fi

如今生产的几乎每一个电子通讯设备上都有 wi-Fi;它允许从无线接入点(WAP)进行数据连接。根据环境的不同,Wi-Fi 网络的覆盖范围在 120 英尺(36 米)到 300 英尺(91 米)之间。常用的两种主要类型的 Wi-Fi 是 802.11b 和 802.11g。大多数 Android 设备都支持连接到互联网和本地网络的 Wi-Fi。在 Android 设备上使用 Wi-Fi 时,任何蜂窝数据连接都将暂停,直到连接丢失或 Wi-Fi 被禁用。在 Android 设备上使用 Wi-Fi 通常意味着更快的连接速度和更少的延迟。

蓝牙

蓝牙类似于 Wi-Fi,但它是一种更新的无线技术,由电信供应商爱立信于 1994 年创建。它适用于短距离传输数据,安全性高。蓝牙设备主要通过配对来工作,侧重于一对一的通信。通过在配对过程中要求物理干预以确保用户可以访问设备的两侧,安全性得到了提高。蓝牙支持直接数据传输,并且由于短距离和更高的数据速度,典型的蓝牙设备充当现有系统的接口或控制器。这些系统包括耳机、键盘、鼠标和移动设备。

大多数现代 Android 设备都支持蓝牙,支持蓝牙 2.0 或更高版本。这些 Android 设备可以播放音乐,在手机之间聊天,甚至使用蓝牙创建互联网热点。使用蓝牙时,Android 允许您的启用设备充当服务器或客户端。

手机界面

随着新技术被集成到智能手机中,我们与移动设备通信的方式已经发生了变化。现代手机不只是一个简单的带有某种显示器的物理拨号盘,而是包含多点触摸屏、振动马达、LED 通知灯、复杂的噪声检测、扬声器、加速度计和物理按钮。Android 开发者使用 Android SDK 提供这些技术。

触摸屏

全彩色触摸屏是一种更新、流行的技术,已经占领了智能手机市场。移动设备中使用的最好的触摸屏支持高分辨率、多点触摸和无刮盖。目前在移动领域使用的触摸屏主要有两种类型——电容式和电阻式。

电容性的

电容式触摸屏由绝缘体组成,通常是玻璃,涂有导电材质。当手指触摸表面时,电场会发生扭曲,测量电容可以找到按压的位置。由于这项技术,屏幕相当准确,只需要最轻微的触摸。电容屏最明显的缺点是需要某种电子传导材质来操作屏幕。这意味着触控笔必须导电才能工作(您的手套也必须导电)!

电阻的

电阻式触摸屏,顾名思义,有两个覆盖着电阻材质的柔性薄片。其中一张纸上有水平运行的传感器,另一张纸上有垂直运行的传感器。当屏幕接触时,精确的点是基于哪些线交叉来定位的。任何物体都可以操作电阻式触摸屏;然而,与电容式触摸屏相比,所需的力量要大得多,精度也不太准确。

苹果公司选择在其所有采用触摸屏的产品中仅使用电容式触摸屏。在 Android 设备市场领域,设备制造商在电容性和电阻性之间的选择上一直存在分歧。

振动电机

触摸屏只是与移动设备交互的众多方法之一。几乎所有现代智能手机都有某种振动马达。振动马达由一个较小的电动马达组成,该电动马达带有一个特意以不平衡方式附加的重物。当马达试图旋转时,重量的不平衡性质会导致设备摇动和振动。在安卓系统中,开发者可以完全使用这个马达,并且可以随时打开和关闭它。

LED 灯

现代智能手机包含一个或多个灯,可用作辅助通知手段。例如,在 Droid 2 上,收到未读文本信息后,指示灯会亮起绿色,如果更改了无线电状态,指示灯会亮起蓝色。灯光颜色和状态可以由任何 Android 开发者随意修改。

加速度计

许多现代智能手机都内置加速度计。加速度计是一种测量一个或多个方向加速度的电子芯片。Android 操作系统使用内置加速度计作为检测屏幕方向的手段。第三方开发人员通过开发将测量结果作为输入形式的应用,扩展了这项技术。

倾斜传感器

在许多方面与加速度计相似,倾斜传感器是一些现代智能手机中包含的嵌入式设备。Android 操作系统中使用倾斜传感器作为加速度计的替代,以确定设备的当前方向。

硬件按钮

除了触摸屏,许多手机都有物理按钮,可以更快地完成任务,如调节音量、让手机进入睡眠状态、返回主屏幕和使用摄像头。当 Android 应用获得设备的焦点时,这些按钮通常可以被重新编程,作为指定应用的输入。例如,在许多媒体应用中,音量按钮会改变以调节媒体控制器的音量,而不是电话铃声的音量。

概观

Android 设备不仅仅是通信和娱乐设备。它们被用于许多不同的方面,例如与标准个人电脑的交互、控制我们的交通工具、与数字娱乐系统交互等等。潜力超出了普通消费者市场。为了适应所有可能使用这些设备的情况,您必须满足一些特殊要求。设备可以被强化以在水下和极端任务环境中工作,可以被加密和保护以用于敏感区域,可以被虚拟化以用于测试和开发,甚至可以被安全地用于军事部队。这一切之所以成为可能,是因为网络光纤可以让设备随时保持连接。正是这种网络纤维创造了现代丰富多变的数字世界。我们可以通过各种媒介与我们的设备互动。高分辨率触摸屏提供了灵敏而精确的控制;振动马达可以通知我们新的事件;内置传感器可以检测设备方向的实时变化。所有这些界面,甚至更多,都可供 Android 开发者以任何可能的方式使用。

四、Android 开发——业务概述和考虑因素

数据是一种珍贵的东西,将比系统本身持续更久。

—蒂姆·伯纳斯·李

Android 行业有自己的准入要求。当你决定是否进行一项商业冒险时,充分理解这些要求和规范是很有帮助的。如前几章所述,Android 操作系统已经是移动设备的行业领导者,其市场份额逐年增长。随着 x86 在 Android 软件堆栈中的引入,现在支持为 x86 系统编写的应用,这进一步扩大了市场。

安卓市场份额

根据国际数据公司(IDC) 的数据,Android 2013 年第二季度的出货量已经达到 1.87 亿台。之前的纪录——1 亿台的销量——在 2012 年被安卓打破。这些数字使 Android 在智能手机操作系统中的市场份额高达 79%。与去年的 1.36 亿台相比,Android 增长了 74%以上。Android 的增长很大程度上可以归功于三星在上一季度的成功,三星占据了智能手机制造市场 39%以上的份额。IDC 的高级分析师 Kevin Restivo 这样评价 Android 的成功:

自从安卓推出以来,不叫 iOS 的智能手机操作系统的份额下降并不是巧合。智能手机操作系统不是一个孤立的产品,它是一个更大的技术生态系统的重要组成部分。谷歌拥有欣欣向荣、多元化的产品组合。它的许多竞争对手与移动操作系统的联系较弱,不具备这种能力。这一因素和其他因素导致了竞争对手的份额损失,很少有例外。

Android 市场份额的未来是一个更有争议的话题。一些行业专家声称,这是 Android 统治地位的巅峰。专家认为,随着新 iPhone 型号的发布,以及新的 iOS 更新,苹果可能会在未来几年从谷歌手中夺回市场份额。其他人声称这仅仅是开始,约翰·科特西耶对此做了最好的阐述:

安卓是一列已经离站的火车,它不为任何人而停。仅这一季度售出的 Android 手机数量就超过了 2007 年全年售出的各类智能手机的总数。

很难说智能手机操作系统市场的未来会是什么样子。没有人能肯定地说,但是市场份额的成功如何转化为利润呢?Android 是一个免费的开源平台,谷歌是如何从中赚钱的?

Android 如何赚钱

谷歌是一家巨大成功的公司;不可否认。但是谷歌如何从 Android 项目中赚钱呢?苹果不仅对他们的操作系统收费,还对升级和应用收费。谷歌的战略符合他们公司的主要收入来源,广告。

谷歌有各种途径从 Android 和它的软件栈中赚钱。首先,谷歌通过在浏览器和 Google Play 商店中做广告向安卓收取费用。这条广告渠道可能看起来很小;然而,这是谷歌大部分收入的来源。谷歌还从 Google Play 上的应用中收取版税,并对向 Google Play 商店添加内容收费。

这个行业领导者每年从谷歌网站上的广告以及通过 AdSense 在联网的“合作伙伴”网站上的广告中获得数十亿美元的收入。2012 年第三季度,谷歌的总广告收入超过 140 亿美元。谷歌首席执行官拉里·佩奇(Larry Page)将谷歌的移动业务估值为 80 亿美元。自去年的 25 亿美元以来,这一数字几乎增长了三倍。佩奇没有进一步细分这笔收入来自哪些部门;他只是对增长发表评论说,“用户在 Google Play 中为内容和应用付费。”谷歌的 SVP 和首席财务官帕特里克·皮切特对这一增长发表评论说:“显然我们没有细分类别。广告仍然是 80 亿美元中的大部分,是其中的绝大部分。”

Android 成功的原因

到底是什么让 Android 如此成功并具有商业可行性?一些专家声称,它的产品背后是强大的金融公司;其他人声称这是开源社区和免费共享思想的后盾;还有一些人声称是因为它的特性和广泛的开发能力。很难找到一个单一的原因,但指出成功的领域要容易得多。

免费

Android 一直是,也将永远是免费的。这使得所有相关方都能获得巨额奖金。制造商可以放心地构建硬件,因为 Android 堆栈将是免费的,开发人员可以编写应用而无需担心复杂的平台成本,研究人员可以发现缺陷并改进底层系统,而无需购买许可证和协议。

开源

开源社区是一个庞大的开发者和志同道合的个人的集合,他们相信共享信息可以带来更强大更好的产品。Android 是这个社区的孩子,多亏了 Google,它才得以开源。这允许来自世界各地的程序员贡献和扩展他们每天使用的软件。

定制

Android 软件栈允许你将你的设备个性化到你觉得舒服的程度。您可以更改颜色、手机响应通知的方式、删除和添加应用,甚至更改保护设备的方式。这种级别的定制允许用户享受和利用设备的最大潜力。

应用基础

Google Play 商店有超过 100 万个不同价格的 Android 应用。这些应用可以以开发人员可以想象的任何方式使用设备。与 iOS 的应用商店相比,Google Play 商店主要由免费应用组成。这是苹果应用商店产生更高收入的主要原因。考虑到这一点,2013 年第二季度,Google Play 商店的应用下载量超过了苹果应用商店。开发者可以从应用内部的广告中获得收入,这使得消费者可以免费使用应用。

硬件选择

与其他移动操作系统不同,Android 有最多的设备可供选择。目前市场上有超过 3900 款 Android 设备。消费者可以找到一款拥有他们需要的所有功能的设备。这些选择还覆盖了美国和欧洲国家的所有主要蜂窝服务提供商,提供了更大的覆盖范围。

设备价格

Android 设备为用户提供了各种价位。消费者可以根据对他们来说重要的功能来选择他们想要为 Android 设备支付多少钱。一些第三方移动服务提供商以现收现付的方式提供 Android 设备。对于更高的价格和服务合同包,最新和最棒的 Android 设备是可用的。你花的钱越多,你的设备包含的硬件和功能就越多。

传统和未来平台支持

Android 已经上市五年了,并且已经产生了大量的软件版本和硬件平台。由于平台的健壮性,Android 必须就如何处理过去的版本以及未来的修订做出选择。向后兼容是所有大型平台都面临的问题。

传统支持

Android 从 1.5 版本开始,被打包到硬件平台上,并存在于商业市场中。Android 软件完全向后兼容。这意味着为 1.5 版构建的应用可以在 4.2 版设备上充分使用。这缓解了开发者对失去新设备市场份额的担忧。然而,需要注意的是,这种兼容性没有利用 Android 的新功能。为了获得最好的质量和功能,应该为新版本重写应用。

Android 也有一些有趣的硬件要求。为了获得谷歌的 Android 认证,制造商必须满足某些条件,如拥有蜂窝无线电、GPS 功能和 Wi-Fi 芯片。然而,用户认为理所当然的许多事情并没有被强制或监管。这个列表包括屏幕尺寸,屏幕分辨率,内部存储大小,GPU 速度,甚至处理器规格。这为制造商提供了制造高性能设备、廉价的消费者友好型设备以及介于两者之间的任何设备的自由。

未来支持

谷歌对 Android 未来版本的策略是迎合向后兼容性,争取最大的市场份额。新版 Android 将拥有运行多年前开发的应用的机制。需要注意的是,新的应用不能在旧的系统上运行。如果您使用 Android 软件开发工具包 4.1 版开发应用,该应用无法在 2.3 版本的 Android 设备上运行。然而,为 2.3 Android 设备编写的应用无需修改就可以在 4.1 设备上运行。

为什么 x86 和 Android 适合您

Android 和 x86 家族 是一个进入门槛低,成功概率大的行业。这些低准入要求甚至给了最小的公司成功的机会,主要是因为启动成本是最低的。问题不是 Android 和 x86 是否适合你,而是你需要采取什么步骤才能成功。

交叉兼容性

x86 体系结构提供了在许多行业中部署的多样化系统网络。从收银机到电视,再到移动设备,甚至主要的公用事业控制系统,x86 平台无处不在。世界上很少有行业没有以某种方式整合 x86 体系结构。结合 Android 平台,拓展范围更大。

为 Android 编写的应用将在所有版本相同的 Android 设备上运行,而不管底层处理器是什么。这意味着,在 ARM Android 设备上编写和测试的应用几乎不需要任何努力就可以在英特尔 x86 Android 设备上发挥其全部功能。使用安卓 NDK 的应用是个例外。但是,通过简单的重新编译,应用应该已经启动并运行了。有关软件迁移的更多信息,请参见第七章:创建和移植基于 NDK 的 Android 应用。

进入壁垒

从经济学角度来说,用乔·S·贝恩的经典定义来说,“进入壁垒是一个行业中的现有卖家相对于潜在进入卖家的优势,这反映在现有卖家可以持续地将价格提高到竞争水平以上,而不会吸引新公司进入该行业。”

进入壁垒是许多公司能够积极主导某些行业的原因。例如,石油行业的准入门槛非常高。创办一家成功的石油公司的成本高得惊人,因为你需要拥有如此多的资源和工具来与现有的领导者竞争。

Android 行业的进入门槛非常低。在 Android 领域创建成功企业的成本和要求远低于大多数其他技术行业。最大的挑战包括找到一个产品创意和建立一个开发团队。有了强有力的想法和基础团队,成功只是开发和营销的问题。事实上,成本非常低,甚至个人也能在这个市场上取得成功。

Android 的安全性

就软件系统而言,安全性通常是提供商比客户更关心的问题。一个好的安全系统,可以深入讲解,不打折扣。Android 就是这些系统中的一个。围绕平台及其组件的安全性已被很好地记录和研究。有关该系统的更多信息和技术细节,请参考x86 上的 Android 安全指南。

应用安全

随着应用市场的引入,Android 的安全模型变得异常复杂。Android 必须保护自己的应用,并为第三方应用提供一定程度的安全性。它的安全系统必须足够简单,普通用户能够理解应用,并且它必须允许用户决定是否使用它们。

Android 对该问题的解决方案是使用权限。为了访问设备的某些功能,您必须注册相关权限。例如,要在应用中使用数据或 Wi-Fi 服务,您必须注册以使用互联网权限。当向用户显示应用时,也会显示不同的权限。如果一个应用试图在没有注册许可的情况下使用某个特性,这个应用就会崩溃。

应用安全性的另一个主要部分是应用之间的信息分离。如果一个应用可以自由地与手机上的其他应用进行交互,恶意的事情就可能发生。用户需要应用能够相互发送消息的情况有很多。Android 的内部应用消息系统使用 intents 的概念在整个操作系统中传递信息。

意图只是由应用产生并传递给 Android 的自由格式的消息。这些消息可以有各种类型的数据,主要有两种类型。一个隐含意图是一个为任何可以访问它的应用而存在的消息。例如,在许多 Android 应用上,当你点击一个链接时,会出现一个窗口,让你从所有几个可以查看该网站的应用中进行选择。在后台,应用触发一个隐式意图,然后传递给所有可用的应用。另一方面,明确的意图是针对一个非常具体的应用。这种意图只有为其设计的应用才能看到和处理。

第三方应用也作为底层操作系统上的独立用户运行。这意味着第三方应用不能访问另一个应用拥有的文件和资源。例外情况是系统应用。系统应用可以访问设备运行所需的所有部分。

平台安全性

围绕手机及其机载功能的安全性是一个广泛讨论的话题。Android 提供了许多不同的安全功能来帮助保护用户及其数据。其中一些功能包括屏幕锁定、文本和电子邮件加密、多种类型的密码,以及当您访问设备的某些部分时的额外密码提示。这些功能验证经过身份验证的用户。

还创建了第三方应用来帮助这项工作。如果您的设备被盗,现有的应用可以帮助您找到设备,从您的手机中删除数据,远程锁定设备,并为您通过设备进行身份验证的方式添加更多定制。

许可

在软件开发行业,许可费非常普遍。从库许可成本,到平台许可成本,甚至设备许可成本,在很多情况下,你需要花一点钱来开发和销售产品。由于 Android 是一个开源和开发者友好的社区,它的目标是保持低成本。

安卓授权费用

没有。Android 是在 Apache 2.0 许可下开源的,允许完全免费的商业使用、修改和分发。这意味着任何人都可以玩 Android 操作系统的源代码,并从中创造出一个全新的产品。

应用许可成本

为 Android 创建应用是一个稍微复杂一些的过程。开发套件和软件开发工具包对任何想下载的人都是免费的。使用这些工具,您可以从源代码构建、测试和部署任何 Android 应用到开发设备。发布你的 Android 应用是一个不同的故事。

Android 设备可以通过 USB 和 SD 卡安装第三方应用,前提是该选项已启用,但这些媒介对消费者来说不是很方便。进入谷歌 Play 商店,这是一个 Android 应用市场,开发者可以快速轻松地上传和更新应用,供所有 Android 用户查看和购买。

在谷歌 Play 商店销售应用的过程非常简单。第一步是建立一个谷歌账户。这使您可以访问 Android 开发网站,在那里您可以管理您的应用。一旦你创建了你的账户,谷歌需要一次性支付 25 美元的费用来在 Google Play 市场内分发应用。然后,您可以上传并配置您的应用,以在市场上进行分发。

现在你有了一个想要分发的应用,你如何赚钱,那看起来像什么?要从您的 Android 应用销售中收款,您需要一个 Google Checkout 商家帐户。在 Google Play 商店购买您的应用将会存入您的 Google Wallet,该钱包可以通过各种方式转换为美元。谷歌确实会从你的所有利润中抽取一定比例,这是基于很多因素,比如应用销售额和销售率。

物理开发成本

当你开始从事软件行业时,重要的是要记住你要创建的虚拟产品是有物理需求的。与传统工程不同,被出售的东西不一定有物理组件。要创建和全面测试 Android 应用,需要几个物理平台。

软件开发系统

开发者使用软件开发系统来编写将在 Android 设备上运行的代码。Android 软件开发工具包(SDK)可以在 Windows、Linux 和 Mac 上运行,因此选择操作系统取决于开发人员。说到硬件选择,有几件事需要考虑。

Android SDK 是一个相对较大的应用,尤其是在它下载了运行不同版本 Android 所需的文件之后。获得一个足够大的硬盘来容纳所有需要的工具和文件是必须的,但是硬盘速度也是一个严重的问题。随着固态硬盘价格的下降,值得考虑开发一个。固态硬盘的读写速度通常是传统硬盘的两倍以上。这相当于节省了大量时间——系统启动速度更快,集成开发环境(ide)运行速度更快,应用运行速度也更快。

如果您计划在笔记本电脑上运行 Android 模拟器,那么为笔记本电脑投资大量 RAM 也是值得的。4GB 或更高应该足够了。如果小于 2GB,就会有问题。随着系统内存的增加,可以同时运行更多的应用。

最后,您需要一个足够强大的处理器来运行 SDK 和所需的开发工具。选择取决于您的开发团队;但是,越新越好。多核处理器总是一个优势。

安卓测试系统

Android 模拟器能够实现物理 Android 设备提供的几乎所有功能。即便如此,在真正的硬件上进行测试也是至关重要的。真实的硬件系统将完全按照客户的预期做出响应,因此在真实的硬件上进行测试可以获得更加自然的体验。

单个 Android 设备是不够的。在与您的目标版本相同的 Android 版本的多个设备上进行测试非常重要。如果应用是为以前版本的 Android 开发的,那么在有新版本的设备上进行测试也是必不可少的。你用来测试软件和确认可用性的设备种类越多,你在发布前发现的错误和问题就越多。在客户处理问题之前发现并解决问题将有助于建立你的产品。

概观

Android 和 x86 是一个强大的组合。凭借较低的准入门槛、强大的安全后盾、较低的许可成本以及部署应用的简单机制,Android 是一个容易涉足且有利可图的行业。得益于 Android 的成功和谷歌的利润,Android 堆栈得到了越来越多的支持。Android 和 x86 是开发的正确选择。通过对开发团队和产品的仔细考虑,可行的策略和成功的管理,Android 生态系统可以赚很多钱。

五、英特尔移动式处理器

如果通用汽车能像计算机行业一样跟上技术的发展,我们都会开着 25 美元、每加仑能跑 1000 英里的汽车。

—比尔·盖茨

英特尔是最初的微处理器设计者:第一个商业化的微处理器是 1971 年的英特尔 4004。英特尔处理器目前主导着高性能市场,占领了几乎所有的现代高端服务器。英特尔的奔腾系列处理器在 20 世纪 90 年代的个人计算中无处不在,其 Core i 系列是当今笔记本电脑和超极本最受欢迎的中央处理器。此外,英特尔凭借移动专用微处理器产品进入移动市场,与移动领域的市场领导者 ARM Ltd .展开竞争。结合 Android 操作系统的健壮性和灵活性,x86 系列处理器的强大功能和兼容性为移动市场带来了极具竞争力的新设备家族。

英特尔的 x86 产品线

x86 构成了庞大的英特尔处理器家族的基础架构,从最早的英特尔 8086 到奔腾系列、I 系列、最新的配备虚拟化管理程序的服务器处理器、专为移动和嵌入式应用设计的低功耗 Atom 和 Haswell 微处理器 ,以及面向可穿戴计算的微型 Quark 片上系统。最初,8086 架构是为嵌入式系统设计的。但是,英特尔 8086 架构的早期实现非常成功,导致了一长串的修改和升级,增加了功能和丰富的特性。

x86 架构是一个复杂指令集计算(CISC)系统 ,由更复杂的指令构建而成,便于使用和简化实现。英特尔移动处理器的主要竞争对手 ARM 是精简指令集计算(RISC)系统 ,没有这些特性。例如,在 RISC 系统中,将一个给定的值加载到内存中可能需要三到四条指令,而在 CISC 系统中,只有一条专门编写的指令来完成这个任务。x86 架构也是基于寄存器到内存的,这意味着指令可以影响寄存器和内存。

历史

英特尔是世界上历史最悠久的半导体制造公司之一,以在计算机硬件和相关行业开发创新和功能技术而闻名。该公司由鲍勃·诺伊斯和戈登·摩尔于 1968 年创办。风险投资家亚瑟·洛克(Arthur Rock)用 1 万美元的初始投资和后来的 250 万美元的出资巩固了这家公司,从而获得了董事长的职位。

英特尔在 1969 年发布了他们的前两款产品:3101 肖特基双极随机存取存储器,以及 1101,世界上第一款金属氧化物半导体(MOS) 。如前所述,第一款英特尔处理器发布于 1971 年,它被称为 4004。

1978 年,英特尔首次发布了将改变世界的 8086 系列处理器架构。仅仅五年后,英特尔可以正式称自己为十亿美元公司。英特尔是全球最大的半导体公司,根据 2012 年年终报告,其市场份额为 15.7%,收入为 475 亿美元。最初的 x86 体系结构已经拆分、多样化、添加了新的规范,并被重新塑造成更小的外形,继续在世界各地的产品中使用。对英特尔来说,在 x86 平台上整合 Android 只是又向前迈进了一步。

因为 x86 体系结构已经在很多技术中使用,从服务器到个人电脑、移动电话、笔记本电脑和平板电脑,所以汇编其设备的完整列表将非常困难。它的广泛使用导致开发人员创建了专用于 x86 平台的工具、应用、框架和库。

这一切都始于 1978 年的英特尔 8086,最初是作为英特尔 8080 8 位微处理器的实验性 16 位扩展而构建的。8086 是驱动“IBM 个人电脑”及其所有复制品的处理器。x86 一词源自 8086 的后继产品,所有产品都以“86”结尾 1985 年,英特尔继续采用 x86 架构,推出首款 32 位处理器英特尔 80386。直到 2005 年,随着奔腾 4 的发布,x86 64 位处理器才进入市场。

英特尔基于 x86 架构的最新家庭计算处理器系列被戏称为英特尔酷睿 I 系列。该系列支持 64 位操作,专注于性能和速度。所有处理器都支持超线程,并拥有多个内核,支持并发处理。与个人计算核心 I 系列微处理器并行运行的是面向移动设备的基于 x86 的凌动系列。

优势和劣势

作为半导体市场的行业领导者,英特尔处理器拥有独特的优势。首先也是最重要的一点,英特尔处理器拥有其他处理器中最高的性能。该性能包括处理器速度以及内核和虚拟内核的数量。x86 体系结构还允许开发人员访问最大的可用软件集合。最后一个主要优势是使用英特尔 CPU 的高端系统的可扩展性;处理器的增加直接提高了性能。

表 5-1 强调了英特尔凌动处理器家族的一些差异,这些处理器家族采用 x86 指令集。英特尔凌动处理器家族针对每种平台类型包含许多不同的品种,包括平板电脑、智能手机、上网本和其他移动消费电子产品,而表 5-1 代表了每种平台的可比高端型号。

表 5-1 。英特尔凌动处理器家族对比

image

有些情况下,x86 系列不是微处理器的正确选择。英特尔家族在物理上比其他品牌的 CPU 大得多,在 Core 系列中占据了超过一英寸的空间。直到英特尔凌动系列,英特尔处理器的功耗对嵌入式设备的要求太高;然而,领先的凌动处理器与 ARM 竞争电池寿命。最后,英特尔处理器的成本非常高,而且在有些系统中,4 核 3GHz 处理器是多余的。在这些情况下,可能需要使用 ARM 或其他低性能 CPU。

商业模式

在家庭计算方面,英特尔一直在为笔记本电脑、超极本和台式机平台生产功能强大、高能效的处理器。最接近的竞争对手是半导体公司 Advanced Micro Devices,Inc .在 2006 年,台式机市场曾一度接近被 AMD 和 Intel 瓜分,但现在不再是这样了。截至 2012 年 11 月,英特尔 CPU 的市场份额约为 71%,而 AMD 为 28%。

随着笔记本电脑和平板电脑越来越受欢迎,英特尔发布了凌动系列处理器。凌动处理器平衡了热量和功率,其性能专门针对需要长时间使用电池的产品。Atom 系列可用于超过 1 亿台设备,目前正在扩展到移动市场。

移动巨头的冲突:ARM 对 Intel

ARM 于 1983 年进入微处理器市场,并在某些领域成为强有力的竞争对手。英特尔及其基于 x86 的处理器已经成功占领了大部分桌面和家庭计算市场。另一方面,ARM 是目前移动和嵌入式设备市场的领导者。下一节详细讨论每个公司的处理器的特性,包括它的优点和缺点。

手臂

如果你看看单位销售额,ARM 是目前移动领域的赢家。目前市场上有超过 300 亿台设备,每天售出 1600 万台,ARM 每年的收入超过 9 亿美元。ARM 的历史、业务战略和未来计划都与 ARM 的成功有关。

历史

ARM 的故事始于英国个人电脑公司 Acorn。最初的 Acorn RISC 机器是在 1984 年到 1985 年间开发的。1982 年,在 ARM 之前,英国广播公司(BBC)与 Acorn 签约开发一种家用电脑,后来被称为 BBC 微型计算机。BBC micro 取得了巨大的成功,并使 Acorn 从一个只有几名员工的小公司成长为拥有数百名员工的中型企业。

在 BBC 微型时代结束时,Acorn 开始寻找下一个处理器来推进他们的新个人电脑。Acorn 尝试了各种 16 位和 32 位处理器,包括苹果 IIGS 中使用的 65C816,但没有找到一款具有 Acorn 所需性能的处理器。Acorn 解决这个问题的方法很简单,就是开发一种新的处理器,ARM1。

尽管 ARM1 拥有令人难以置信的能力和性能,但直到第一个真正基于 ARM 的平台 Archimedes 的发布,ARM 1 才得到广泛使用。阿基米德是一台台式电脑,于 1987 年中期发布,主要用于学校和其他教育环境。尽管取得了不太大的成功,并得到了消费者的响应,但 ARM 团队继续努力,开发了 ARMv3,专注于提高性能,以与英特尔和摩托罗拉工作站竞争。

1990 年,Acorn RISC 机器成为高级 RISC 机器,高级 RISC 机器有限公司成立。在创始合作伙伴苹果、Acorn 和 VLSI Technology 的帮助下,该公司成立的唯一目的是继续开发 ARM 处理器。在此基础上,ARMv6 诞生了,并于 2002 年 10 月发布给授权厂商。ARMv6 架构及其最近的亲戚 ARMv7 和 ARMv8 如今广泛用于嵌入式和移动设备。

优势和劣势

ARM 处理器有一些非常吸引人的品质。首先,它们非常小。事实上,最现代的 ARM11 系列处理器还不到 2 毫米 2 。由于外形小巧,使用时产生的热量通常很低,足以避免任何类型的散热器或冷却系统。即使很小,ARM 芯片也可以在一片硅片中包含许多核心系统组件。这些组件包括 CPU、GPU 和 DSP。最后一个主要优势是相对于竞争对手而言功耗极低;一些报告声称节省了高达 66%的成本。用电量越少,电池寿命越长,电费就越便宜。

表 5-2 展示了目前在移动市场上使用的一些较为流行的 ARM 处理器。该表只是 ARM 为移动设备提供的众多选项中的一个示例,但是与表 5-1 的比较表明了与英特尔处理器家族的显著差异。可比的 ARM 移动处理器提供的处理器速度要低得多,即使在 A15 的高端也是如此。

表 5-2 。ARM Cortex-A 系列对比

image

尽管 ARM 芯片有很多优点,但也有很多缺点。首先,ARM 芯片缺乏任何繁重处理情况所需的严肃性能。ARM 处理器本身的可扩展性也较差,尤其是与现代英特尔 CPU 相比。ARM 的软件需要专门为架构创建;幸运的是,ARM 已经有了一些更常用的工具和工具。

商业模式

对 ARM 公司决策的分析有助于揭示他们在处理器市场的关注点。显而易见,人们看重的是 RISC 架构、高性能、低功耗和低价位。这些差异使 ARM 非常适合移动市场,也是 ARM 处理器几乎只用于智能手机的关键原因。

然而,ARM 处理器不是由 ARM 有限公司销售或制造的。相反,处理器架构被授权给相关方。ARM 有限公司提供各种条款和不同的费用。对于所有的被许可方,ARM 提供了深入的文档、完整的软件开发工具集,以及带许可 CPU 的制造芯片的销售权。

这种商业模式为公司带来了好处;在 2013 年第二季度,ARM 报告将 51%的收入归类为版税,39%来自许可。该报告继续详细说明了版税和许可证的数量。每单位版税的平均成本约为 0.07 美元,超过 26 亿单位。另一方面,该季度签署了 25 份新许可证,平均每份许可证价值约 184 万美元。

将来的

ARM 公开发布的最新处理器是 ARM7,有各种修改的实现。ARM7 广泛应用于现代智能手机市场。公司内部有传言称,ARM 将在处理器方面寻求更多发展方向。

随着面向 x86 的 Windows 8 的发布,微软为 ARM 处理器创建了一个名为 Windows RT 的 Windows 版本。Windows RT 几乎完全是从零开始编写的,并且已经设法消除了许多(但不是全部)现代向后兼容 Windows 版本的瓶颈。测试表明,RT 应用的运行速度比竞争对手英特尔芯片上的相同应用快 20%。

专家还预测 ARM 将进入服务器和数据中心市场。在基于 ARM 的 Linux 服务器操作系统的支持下,这越来越成为现实。可以想象,在 ARM 上运行高性能系统意味着更低的功耗。这在高端 ARM 系统的当前性能与高端 Intel 系统的性能之间还有待观察。

英特尔的凌动系列微处理器

凌动处理器是移动设备的特色。典型设备包括小型笔记本电脑、上网本、平板电脑、电视和新型智能手机。Atom 平衡了性能和功耗,使设备的电池续航时间更长。

随着超过 1 亿个 Atom CPUs 的出货,Atom 的影响力显而易见。与所有英特尔处理器一样,凌动是英特尔架构(IA)家族 的一员。IA 系列独特的交叉可移植性允许处理器之间快速轻松的转换。

英特尔凌动进化

英特尔凌动处理器是主要用于笔记本电脑的低功耗处理器英特尔 A100 和 A110 的继任者。A100 和 A110 的代号为 Stealey,最初的尺寸为 90 纳米。表 5-3 和 5-4 强调了 Atom 在平板电脑和智能手机上的一些迭代,从 2008 年 4 月处理器家族的婴儿期到其现代版本。

表 5-3 。英特尔凌动智能手机处理器

image

表 5-4 。英特尔凌动平板电脑处理器

image

乍一看,表 5-3 中列出的处理器似乎只是稍微好一点,但是为了真正理解发生了什么,您需要考虑所有的变量。Penwell 是英特尔今天为智能手机生产的处理器的先驱,尺寸仅为 32 纳米,支持多核,具有嵌入式 GPU 支持的顶级操作频率。对于现代设备制造商来说,这显然是英特尔的选择。

与表 5-3 中的现有处理器相比,表 5-4 中列出的平板电脑处理器能力更强。这些平板电脑处理器支持更多内核,具有更快的 GPU 速度,有助于容纳更大且通常高分辨率的显示组件。

英特尔凌动安全技术

在当今时代,随着技术的发展,安全性始终是一个问题。英特尔凌动处理器支持许多安全特性。其中包括安全启动、英特尔平台信任技术、硬件增强加密和操作系统级密钥存储。安全启动是当前统一可扩展固件接口(UEFI)规范的一部分,用英特尔自己的话说就是最好的描述:

启用并完全配置后,安全引导有助于计算机抵御恶意软件的攻击和感染。安全引导通过验证数字签名来检测对引导加载程序、关键操作系统文件和未授权选项 rom 的篡改。检测在攻击或感染系统之前被阻止运行。

英特尔平台信任技术(简称 PTT)是平板电脑上的虚拟智能卡读卡器,允许通过 CPU 进行基于证书的认证。

英特尔凌动特性

英特尔凌动处理器支持其他英特尔处理器中存在的大量特性。能效是英特尔世界的一个新概念,而凌动将这一概念推向了前沿。凌动处理器可以定制,以实现超低功耗与不同性能可扩展性选项之间的正确平衡。在性能方面,凌动支持英特尔超线程和英特尔突发技术,以帮助处理所需的性能和能效。英特尔推出的凌动处理器的最后一个主要特性是移动性概念,支持 NFC、高级相机成像、3G 和 4G LTE。

Android 和 Atom

Atom 处理器是当前 Android 平台首选的 x86 处理器。Atom Android 团队带来了一个装满顶级功能的衣柜。这包括支持多种格式的 1080p 高清 3D 图形、屏幕共享和设备配对、优化的网页渲染和简单的交叉计算能力。Atom Android 平台支持 Android SDK 应用的开箱即用。在大多数情况下,Android NDK 应用只需要重新编译就能得到完全支持。关于兼容性和转换过程的更多信息可以在以下标题为应用兼容性的章节和 第七章:创建和移植基于 NDK 的 Android 应用中找到。

Medfield 片上系统内部

英特尔的 Medfield 平台旨在用于运行 Android 操作系统的智能手机和平板电脑。一款名为英特尔凌动 Z2610 片上系统(SOC) 的 Medfield 模型将在稍后详细讨论(见图 5-1 )。如前所述,英特尔最近开始生产独立的移动处理器,包括一个代号为 Penwell 的处理器。虽然 Penwell 处理器包含一些与 Medfield SoC 相同的部分,即 Saltwell 系列微处理器架构,但 Penwell 是一款主要针对智能手机的独立处理器,而不是 Medfield 针对智能手机和平板电脑的多部分高性能系统。

9781430261308_Fig05-01.jpg

图 5-1 。梅菲尔德框图

这个 Medfield 模型,Z2610,在物理上分为两个复合体,北复合体和南复合体。North Complex 包括一个 Saltwell 系列单核处理器、一个 32 位双通道 LPDDR2 内存控制器、一个 3D 图形内核、视频解码和编码引擎、一个能够支持多达三台显示器的 2D 显示控制器,以及一个用于摄像机输入的图像处理器。South Complex 包括完成智能手机设计所需的所有 I/O 接口,如安全引擎、支持 SD/eMMC 存储卡的存储控制器、USB OTG 控制器、3G 调制解调器、免费无线解决方案(CWS)接口、SPI 和 UART。参见图 5-1 。

放大 Saltwell CPU 架构

Saltwell CPU 架构相当简单。该设计的理念是创建一种在优化性能和高效功耗之间取得平衡的处理器。该处理器使用有序架构,这不同于市场上的大多数其他处理器,它们使用无序执行。该处理器有一个 64kb 的 L1 缓存和一个 512k b 的 L2 缓存。该处理器支持英特尔突发性能技术,可让处理器动态提高 CPU 速度。Saltwell 有三种频率模式:低频模式(LFM) 运行在 600MHz,高频模式(HFM) 运行在 900MHz,突发频率模式(BFM) 运行在 1.6GHz。在电源优化特性中,Saltwell 有一个超低功耗智能 L2 高速缓存,在 CPU 处于 C6 状态时保存数据,以降低 C 状态恢复期间的延迟。此外,Saltwell 为内核和 SoC 的其余部分提供了单独的电源层和时钟输入,这使得电源和时钟门控可以通过英特尔智能闲置技术(英特尔 SIT)轻松配置。该技术可使 CPU 在 SoC 仍处于开启状态(S0 状态)时完全关闭。

英特尔的 Saltwell 和 ARM 的 Cortex A15 的架构差异

如书中所列,脱离英特尔凌动处理器:架构迁移指南, 1 英特尔凌动架构与 ARM 架构在各方面都有很大不同。表 5-5 显示了 Saltwell 和 ARM Cortex 架构之间的高级差异列表。

表 5-5 。Saltwell 和 ARM 之间的高级别差异(Cortex A15)

|

特征

|

索尔特韦尔

|

手臂皮层

|
| --- | --- | --- |
| 技术 | 32 纳米 | 28 纳米 |
| 体系结构 | 按顺序 | 无序的 |
| 整数流水线 | Sixteen | Fifteen |
| L1 高速缓存 | 64KB | 可配置高达 64KB |
| L2 高速缓存 | 512KB | 最大 4MB |
| 指令组 | IA32,英特尔流 SIMD 扩展,英特尔补充流 SIMD 扩展 3 | 手臂,拇指 |
| 多核/线程支持 | 采用英特尔超线程技术的单核 | 多核 |
| 安全技术 | 英特尔智能安全技术(英特尔 S&ST) | TrustZone*技术 |

架构

如前所述,Saltwell 的架构与英特尔凌动系列的其他处理器相似。它使用有序执行设计。对于有序处理器,所有指令都按照它们被提取的顺序来执行,而无序处理器能够同时执行多条指令,并且稍后在流水线中对它们进行重新排序。ARM 处理器使用乱序架构,这种架构的优势是以最小的延迟执行指令。然而,这增加了核心设计的复杂性。取消重新排序逻辑是英特尔凌动处理器的节能举措之一。

整数流水线

英特尔凌动管道分为六个阶段:详情列于表 5-6 中。

表 5-6 。英特尔凌动指令阶段和流水线阶段

|

阶段

|

流水线阶段

|
| --- | --- |
| 取指令 | three |
| 指令解码 | three |
| 指令发布 | three |
| 数据存取 | three |
| 执行 | one |
| 回复 | three |

这种指令架构导致英特尔凌动处理器中共有 16 条整数流水线,并且需要三个额外的阶段来执行浮点指令。最新的 ARM 处理器有 15 个整数流水线。ARM 处理器中漫长的流水线牺牲了能量和性能。Saltwell 每个时钟周期可以解码多达两条指令,而最新的 ARM 处理器是三重超标量体系结构。

指令集

ARM 指令集始终是 32 位的,并在一个 4 字节的边界上对齐,而 IA32 指令集大小不同,不需要任何对齐。ARM 指令和 IA32 指令的另一个区别是指令的执行方式。对于 ARM,所有指令都有条件地执行,以减少分支开销和分支期间的误预测。每个指令都需要满足条件标志才能生效,否则该指令将作为 NOP 并被丢弃。英特尔架构中也有条件指令;这些被称为条件 MOV 指令。IA32 中的其他指令不是有条件执行的。

多核/线程支持

如前所述,Saltwell 支持英特尔超线程技术(英特尔 HT 技术),通过使用共享资源来完成任务。该技术的细节将在下一节中进一步讨论。ARM 多核架构拥有独特的资源,可在每个内核上执行任务。内核的一致性由 AMBA 4 AXI 处理,这是一个兼容的从接口,直接与内核接口。

安全技术

Medfield 中有一个名为英特尔智能安全技术(英特尔 S&ST)的安全子系统。它是一个完整的硬件和软件安全架构。该子系统符合行业标准,支持 AES、DES、3DES、RSA、ECC、SHA-1/2 和 DRM。它还支持 1,000 位 OTP 并支持 SecureBoot。安全系统在 ARM 处理器中的实现是不同的。英特尔实施的安全子系统没有单独的控制器。ARM 处理器使用 TrustZone 技术,其中系统中的资源(如处理器和内存)被分为两个世界:正常世界(??)和安全世界(??)。这种信任区体系结构有三个动机:

  • 提供一个安全框架,允许设计人员根据用例定制所需的功能。
  • 在不需要为安全任务配备专用处理器的情况下,节省芯片面积和功耗。
  • 通过提供单个调试组件,防止在调试期间对安全世界中的安全敏感任务或正常世界中的非安全敏感任务的入侵。

英特尔超线程技术

英特尔超线程技术(英特尔 HT 技术)支持软件查看物理处理器封装中的多个逻辑处理器。Saltwell CPU 架构使用英特尔超线程技术来提升其性能。在单个有序架构处理器中拥有第二个线程,可以使 Saltwell 在一个时钟周期内执行多个指令,并在两个线程之间共享执行资源,与单线程处理器相比,性能提高了 50%,如图图 5-2 所示。

9781430261308_Fig05-02.jpg

图 5-2 。英特尔超线程技术的优势

在英特尔超线程技术中,处理器拥有由通用寄存器、控制寄存器、高级可编程中断控制器(APIC)寄存器和一些机器状态寄存器组成的架构状态副本。架构状态的复制是软件可以将一个内核处理器视为两个逻辑处理器的原因。两个线程共享缓存、执行单元、分支预测器、控制逻辑和总线。这就产生了一个问题,即线程之间可能存在资源争用和工作负载不平衡。然而,大多数当前的开发套件(如 Dalvik 和 JavaScript)已经具备支持多线程环境的能力,为开发人员提供了一种简单的方法来生成利用英特尔超线程技术优势的应用。Android 上的应用开发人员还可以利用英特尔 VTune 性能工具来分析工作负载,并对他们的应用执行资源调整。

应用兼容性:原生开发套件和二进制翻译器

Android 已经移植到 x86,所有进一步的版本都将在 x86 和 ARM 架构中提供。在英特尔凌动平台上运行操作系统不成问题。然而,在某些情况下,现有的 Android 应用可能需要在修改或不修改源代码的情况下重新编译。

据信,谷歌 Play 商店大约 75–80%(通常引用的数字)的 Android 应用运行在 Dalvik VM 之上,并使用 Android 框架(见图 5-3 )。使用 Android 软件开发工具包(SDK)用 Java 语言编写的绝大多数 Dalvik VM 应用都是与处理器无关的。它们在英特尔凌动平台上透明运行,无需移植。对于包含 C 和 C++ 代码的应用子集,开发人员需要使用最新的 Android 原生开发工具包 (NDK)重新编译他们的代码。

9781430261308_Fig05-03.jpg

图 5-3 。Android 框架

在大多数情况下,NDK 应用开发人员只需重新编译项目,该项目支持 x86 (x86)、ARMv5 (armeabi)和 ARMv7 (armeabi-v7a)。针对 x86 的编译(通过带有编译器标志-march=i686 –msse3 –mstackrealign –mfpmath=sse的 GCC)将生成完全符合英特尔凌动 CPU 特性集的代码。只有使用 ARM 供应商特定功能的应用才需要重写源代码,然后重新编译。

所得到的 APK 应用包可以包括用于 x86、ARMv5 和 ARMv7 的三个版本的机器代码。在安装时,只有适当版本的代码被解包并安装到目标平台上。

其余的应用要么是使用为 ARM 构建的 Java 本地接口(JNI)库的 Dalvik VM 应用,要么是未针对 x86 编译的本地开发工具包(NDK)应用。由于调用了本地库(尤其是特定于 ARM 的本地库),这些应用无法在英特尔凌动平台上运行。

英特尔和谷歌携手合作,确保在英特尔凌动平台上“按原样”执行原生应用,无需进行移植。英特尔提供二进制翻译(BT),在执行过程中将 ARM 代码动态翻译为 x86 代码,如图 5-4 中的所示。这种转换减轻了尚未移植到 x86 的 JNI 库和 NDK 应用的不便。它允许设备公开自己支持两个应用二进制接口(ABI):x86 和 ARMv5。这可以从build.prop观察到,如图图 5-4 所示。

ro.product.cpu.abi=x86
...
ro.product.cpu.abi2=armeabi

9781430261308_Fig05-04.jpg

图 5-4 。二进制翻译

如果 NDK 应用没有为 x86 平台重新构建,二进制翻译器会在本地将 armeabi 版本翻译成 x86。这同样适用于请求基于 ARM 的 JNI 库的 Dalvik VM 应用。翻译流程经过优化,对最终用户完全透明。

所有这些努力的结合应该会导致 Google Play 中大约 90%的应用立即工作。另外 10%的应用可能需要一些额外的配置和设置才能完全正常工作。在 第七章:创建和移植基于 NDK 的 Android 应用中,我们将涉及更多关于使用 x86 进行本地代码开发的细节,这将提供一些通用的建议来帮助任何适合这一类别的应用。

概观

本章从公司和处理器的角度简要介绍了英特尔和 ARM 的历史。您了解了英特尔和 Arm 的一些特定处理器,并了解了它们各自带来的优势。在简要介绍了每家公司之后,我们花了一些时间讨论了英特尔凌动处理器以及其现代版本的特性和特点。最后,我们进入了关于英特尔凌动 Medfield 架构的技术讨论,这是最新 x86 手机和平板电脑的特色。我们已经讨论了整数流水线如何流动,采用了哪些安全系统,甚至英特尔超线程技术如何优化性能。我们详细讨论了二进制翻译,并解释了基于 NDK 的应用必须如何准备才能移植到英特尔平台。

1 马塔萨、洛里和马克斯·多梅卡、脱离英特尔凌动处理器:架构迁移指南。英特尔出版社,2010 年。

六、为英特尔应用开发安装 Android SDK

在一个有英特尔和微软的国家,2000 年问题怎么会成为问题呢?

—阿尔·戈尔

本章涵盖在英特尔架构处理器上开发 Android 应用的必要信息。第一步是安装软件开发套件(SDK ),并为在基于英特尔架构的 Android 设备上运行的应用开发设置合适的环境。SDK 包括工具和平台组件,供开发人员开发、构建、测试、调试和优化他们的 Android 应用,以及管理 Android 平台组件安装。SDK 还提供了与构建和开发环境集成的简单方法,例如与 Eclipse 或 Apache Ant 集成。

准备安装 SDK

下一节将致力于建立一个可用的开发环境,以便您可以开始在英特尔平台上开发 Android 应用。如果您已经有了 Android 开发环境设置,您可以跳过这一部分。

支持的操作系统

支持以下操作系统:

  • Windows XP (32 位)、Vista (32 位或 64 位)、Windows 7 (32 位或 64 位)和 Windows 8 (32 位或 64 位)
  • Mac OS X (32 位或 64 位)
  • Linux (Ubuntu,Fedora);需要 GNU C 库(glibc ) 2.7 或更高版本
  • 在 Ubuntu Linux 上,需要 8.04 或更高版本
  • 在 Fedora 上,目标版本是 F-12 和更高版本
  • 64 位发行版必须能够运行 32 位应用

硬件要求

Android SDK 要求为您选择安装的所有组件提供磁盘存储。运行模拟器需要额外的磁盘空间,例如,为 Android 虚拟设备(avd)创建 SD 卡。

安装 JDK

SDK 至少需要 Java JDK 5 或 JDK 6。JDK 7 也受支持。仅有 JRE (Java 运行时环境)是不够的。如果您的系统没有安装 JDK 5、6 或 7,您可以从http://www.oracle.com/technetwork/java/javase/downloads/index.html下载 JDK SE 7 并安装在您的系统上。

安装 Eclipse

强烈推荐使用 Eclipse 的 SDK 来开发 Android 应用。可以去http://www.eclipse.org/downloads/下载或者更新 Eclipse。我们建议使用以下 Eclipse 设置来开发面向英特尔架构的 Android 应用:

  • Eclipse 3.5(伽利略)或更高版本
  • Eclipse Classic(版本 3.5.1 和更高版本)
  • Android 开发工具插件(推荐)

安装 Apache Ant(可选)

强烈推荐使用 Eclipse 等集成开发环境开发 Android 应用。但是作为替代,您可以使用 Apache Ant 与 SDK 一起构建 Android 应用。您可以访问http://ant.apache.org/下载二进制发行版并安装 Ant。要使用 SDK,需要 Ant 1.8 或更高版本。

下载 SDK 启动包并添加 SDK 组件

您可以在http://developer.android.com/sdk/index.html下载 SDK starter 包。SDK starter 包不包含开发 Android 应用所需的特定于平台的组件。它只提供核心 SDK 工具供您下载其余的平台组件。

安装 SDK 启动包后,运行 Android SDK 和 AVD 管理器。

  • 在 Windows 上,选择开始image所有程序image Android SDK 工具image SDK 管理器
  • 在 Linux 上,运行your-android-sdk-directory/tools/android

在 Android SDK 和 AVD 管理器对话框的左侧面板中,选择可用的包,在右侧面板中,点击并展开 Android Repository 节点,选择要安装的包,如图图 6-1 所示。

9781430261308_Fig06-01.jpg

图 6-1 。在 Linux 中安装 Android SDK 和 AVD 管理器

image 注意如果你试图从防火墙后下载,你可能会看到一条错误信息。如果出现这种情况,请在防火墙外重试。如果您仍然看到错误消息,请关闭 SDK 管理器,然后在开始菜单中右键单击它,并选择以管理员身份运行。这两个步骤将解决您在尝试下载时看到的大多数错误信息。

设置 Eclipse 以使用 SDK

如果您使用 Eclipse IDE 开发软件,我们强烈建议您安装并设置 Android 开发工具(ADT)插件。

为 Eclipse 安装 ADT 插件

要安装 Eclipse 的 ADT 插件,请遵循以下步骤:

  1. 启动 Eclipse,在安装对话框中选择 Help image安装新软件,点击 Add 按钮。

  2. In the Add Repository dialog box, enter ADT Plugin in the Name field and enter https://dl-ssl.google.com/android/eclipse/ in the Location fields, as shown in Figure 6-2. Then click OK.

    9781430261308_Fig06-02.jpg

    图 6-2 。存储库对话框

  3. It will go back to the Install dialog box, connect to the Google repository server, and display the available ADT packages, as shown in Figure 6-3.

    9781430261308_Fig06-03.jpg

    图 6-3 。ADT 软件包列表

  4. 选择下一步,接受许可协议,然后选择完成。

  5. 重启 Eclipse。

配置 ADT 插件

要配置 ADT 插件,请执行以下步骤:

  1. 启动 Eclipse 并选择 Windows image首选项。
  2. 在首选项对话框中,从左侧面板中选择 Android。在右侧面板中,使用浏览按钮导航到您的 Android SDK 安装目录,然后单击应用。您已经安装的 SDK 目标列表将会出现,如图 6-4 所示。此时,单击确定。

9781430261308_Fig06-04.jpg

图 6-4 。ADT SDK 目标列表

完成这些步骤后,您将拥有开始 Android 开发的必要工具。现在,您已经具备了编写第一个应用所需的一切,但最好还是在此时安装英特尔架构(x86)仿真器 ,这样您就可以在应用准备就绪后立即对其进行测试。下一节将带您完成英特尔架构(x86)仿真器的安装。我们讨论用 AOSP 源代码构建一个仿真器映像,并仿真 x86 的最终系统映像。

Android 开发者工具 会定期更新以包含最新的 API。构建仿真器映像时,构建最新 Android 版本的工具唾手可得。如果您选择下载预构建的仿真器系统映像,您的选择将会过时几个月。在本章中,我们使用模拟 Android 2.3(代号为姜饼,涵盖 API 级别 9 和 10)和 Android 4.0(代号为冰激凌三明治,涵盖 API 级别 14 和 15)的案例研究。姜饼是第一个在 ARM 以外的平台上发布的 Android 版本,它的新功能直接来自 Android 开源项目的努力,这在 第一章:Android OS 的历史和演变中讨论过。

Android 虚拟设备仿真概述

Android 可以在各种不同屏幕尺寸、硬件功能和特性的设备上运行。典型的设备具有多种软件(Android API)和硬件功能,如传感器、GPS、摄像头、SD 卡和特定尺寸的多点触摸屏。

该仿真器非常灵活,可配置不同的软件和硬件配置选项。开发者可以使用名为 Android 虚拟设备(AVD) 的仿真器配置定制仿真器。AVD 基本上是一组指定不同 Android 软件和设备硬件功能的配置文件。Android 模拟器使用这些 AVD 配置在模拟器上配置和启动适当的 Android 虚拟映像。

正如 Android 网站上记录的那样(参见http://developer.android.com/guide/developing/devices/index.html),典型的 AVD 配置具有:

  • 指定所有设备功能(如照相机和传感器)的硬件配置文件。
  • 一个系统映像,这个 AVD 的模拟器使用它(指定目标 API 级别,比如 10 代表 Gingerbread,19 代表 KitKat)。
  • 一个数据映像,作为用户数据、设置和 SD 卡的专用存储空间。
  • 其他选项包括模拟器皮肤、屏幕尺寸和 SD 卡大小。

开发人员被鼓励针对不同的 API 级别、屏幕尺寸和硬件功能(比如摄像头、传感器和多点触控)。AVD 配置可用于根据需要定制仿真器。开发人员可以根据需要创建任意数量的 avd,每个 avd 都针对不同的基于英特尔架构的 Android 设备。例如,开发人员可以创建基于英特尔架构的姜饼 AVD,内置类似 WVGA800 的皮肤,或者手动指定屏幕分辨率的自定义皮肤。

Android SDK 从版本 r12 开始就支持基于英特尔架构的 Android 仿真。SDK 将这种支持集成到所有开发人员工具中,包括 eclipse ADT 插件。图 6-5 是运行 Gingerbread 的 x86 Android 模拟器的示例截图。突出显示型号,并显示 x86 模拟器上的完整 Android。

9781430261308_Fig06-05.jpg

图 6-5 。安卓模拟器

关于如何使用模拟器的详细说明,请参考以下 Android 文档:http://developer.android.com/tools/devices/emulator.html

你应该使用哪个模拟器

在撰写本文时,仿真器映像可用于 Android 2.3.7(姜饼)、Android 4.0.4(冰淇淋三明治)和 Android 4.3(果冻豆)的英特尔架构(x86)。您可以在http://software.intel.com/en-us/articles/android-43-jelly-bean-x86-emulator-system-image找到最近的图像。

尽管为最新的 Android 操作系统版本开发有许多优势,但许多开发人员更喜欢以 Android 2.x 或 Android 4.x 为目标,因为大多数 Android 手机都运行 Android 4.x 或更高版本。这个百分比会随着时间的推移而变化,因此强烈建议您在确定目标操作系统时牢记市场条件。

要了解更多特定于 Gingerbread 的操作系统信息,下面的文章可能会很有用:http://blogs.computerworld.com/17479/android_gingerbread_faq

冰淇淋三明治资讯,用这篇文章:http://www.computerworld.com/s/article/9230152/Android_4.0_The_ultimate_guide_plus_cheat_sheet_

为什么要使用模拟器

首先,它是免费的。Android SDK 及其第三方插件完全不需要任何成本,并且允许开发人员模拟他们不拥有并且可能无法访问的设备。这一点很重要,因为并非所有手机都通过空中下载(OTA)更新获得最新的 Android 操作系统版本,而且开发者购买每一款预计支持其软件包的设备也不可行。

  • 开发和测试。 开发者可以使用 SDK 创建几个 Android 虚拟设备(AVD)配置,用于开发和测试目的。每个 AVD 可以有不同的屏幕尺寸、SD 卡大小,甚至是 Android SDK 的版本(这对于测试与以前的 Android 版本的向后兼容性很有用)。
  • 玩新版本。该模拟器允许开发人员尽情享受新版本的 Android,并对其有更多的了解。

现在让我们浏览一下在 x86 构建上构建和运行模拟器映像所需的步骤。为了简单起见,这一节重点介绍姜饼模拟器,尽管大部分内容也适用于冰淇淋三明治模拟器。

构建仿真器映像

第一步是遵循这里列出的设置说明:http://source.android.com/source/initializing.html

Google 已经在http://source.android.com/source/downloading.html发布了姜饼文档。

目前,默认的回购初始化指令仅用于获取最新的主分支或特定分支,例如使用命令android-2.3.7_r1时。没有给出构建 Gingerbread x86 模拟器映像的 repo 和 branch 指令。

使用标签为gingerbread的分支下载最新的 Android 源码分支,如清单 6-1 所示。这个分支拥有构建 x86 SDK 模拟器映像所需的构建文件和模块。

清单 6-1 。设置资源库

$ repo init –u
https://android.googlesource.com/platform/manifest-b gingerbread
$ repo sync

初始化构建环境,如清单 6-2 所示。

清单 6-2 。构建环境初始化

$ source build/envsetup.sh
including device/htc/passion/vendorsetup.sh
including device/samsung/crespo4g/vendorsetup.sh
including device/samsung/crespo/vendorsetup.sh

使用lunch命令配置构建 x86 SDK 的版本,如清单 6-3 所示。这将为 x86 构建模拟器映像,以及其他 SDK 模块。

清单 6-3 。午餐命令

$ lunch sdk_x86-eng
=====================================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=2.3.7
TARGET_PRODUCT=sdk_x86
TARGET_BUILD_VARIANT=eng
TARGET_SIMULATOR=false
TARGET_BUILD_TYPE=release
TARGET_BUILD_APPS=
TARGET_ARCH=x86
TARGET_ARCH_VARIANT=x86_atom
HOST_ARCH=x86
HOST_OS=linux
HOST_BUILD_TYPE=release
BUILD_ID=GINGERBREAD
=====================================================

最后,发出make sdk命令来开始构建,如清单 6-4 中的所示。

清单 6-4 。制作 SDK

$ make sdk
=====================================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=2.3.7
TARGET_PRODUCT=sdk_x86
TARGET_BUILD_VARIANT=eng
TARGET_SIMULATOR=false
TARGET_BUILD_TYPE=release
TARGET_BUILD_APPS=
TARGET_ARCH=x86
TARGET_ARCH_VARIANT=x86_atom
HOST_ARCH=x86
HOST_OS=linux
HOST_BUILD_TYPE=release
BUILD_ID=GINGERBREAD
=====================================================

此外,您可以使用–j参数为make指定并行作业的数量,以加快构建过程。(建议您使用至少大于或等于系统中 CPU 总数的值。)

构建将创建三个映像,包括 QEMU Android 内核image:t system.img (Android)、userdata.imgramdisk.img

当构建完成后,图像会在build文件夹$ANDROID_BUILD_TOP/out/target/product/generic_x86/中,如图图 6-6 所示。

$ ls $ANDROID_BUILD_TOP/out/target/product/generic_x86/ -Altr

9781430261308_Fig06-06.jpg

图 6-6 。图像位置

QEMU ( kernel-qemu)的 Android 内核映像来自 Android 源代码。位于预建文件夹($ANDROID_BUILD_TOP/prebuilt/android-x86/kernel)下,如图图 6-7 所示。

$ ls $ANDROID_BUILD_TOP/prebuilt/android-x86/kernel -Altr

9781430261308_Fig06-07.jpg

图 6-7 。内核映像

现在,您已经拥有了在 Android x86 模拟器上运行 x86 Android Gingerbread 映像所需的所有映像文件。您需要用 SDK 设置图像文件,这将在下一节中介绍。

设置 SDK 以使用 x86 模拟器映像

Android SDK 工具(Android 和 AVD 管理器)期望 x86 模拟器映像出现在平台映像的默认 SDK 文件夹中,这是/platforms/android-10/images

下面的图片假设$ANDROID_SDK_TOP环境变量被设置为 Android SDK 安装文件夹的位置。

如图 6-8 中的所示,默认情况下,Android-10 自带 ARM 的模拟器图像。为了在 SDK 中设置 x86 模拟器映像,您需要创建一个x86文件夹,并将您构建的映像复制到该文件夹中。您也可以将手臂图像移动到它们自己的文件夹中,如清单 6-5 中的所示。

$ cd $ANDROID_SDK_TOP/platforms/android-img/
$ls -l

9781430261308_Fig06-08.jpg

图 6-8 。图像位置

清单 6-5 。手臂折叠器

$ mkdir arm
$ mv *.img kernel-qemu arm/

清单 6-6 显示了 x86 文件夹的指令。

清单 6-6 。x86 指令

$ mkdir x86
$ cp $ANDROID_BUILD_TOP/out/target/product/generic_x86/*img x86/
$ cp $ANDROID_BUILD_TOP/prebuilt/android-x86/kernel/kernel-qemu x86/

$ cp NOTICE.txt arm/
$ cp NOTICE.txt x86/

Android-10 平台的最终图片文件夹如图图 6-9 所示。

$ ls –l *

9781430261308_Fig06-09.jpg

图 6-9 。最终图像文件夹

在 x86 模拟器上使用 Gingerbread 英特尔架构映像之前,您必须创建一个 AVD 配置,指定所需的软件和硬件定制。有关 AVDs 的更多详细信息,请参阅英特尔软件网络文章“面向英特尔架构的 Android 虚拟设备仿真”(http://software.intel.com/en-us/articles/android-virtual-device-emulation-for-intel-architecture)。

此时,您的模拟器和 x86 映像就可以使用了。请注意,如果您将英特尔硬件加速执行管理器(英特尔 HAXM)与此系统映像配合使用,仿真器性能将会显著提高,否则性能可能会有所不同。(英特尔 HAXM 需要支持英特尔 VT-x 的英特尔处理器。有关英特尔 HAXM 的更多信息,请参见 第十一章:使用英特尔硬件加速执行管理器在 x86 仿真上加速 Android

现在是时候使用 Gingerbread x86 模拟器来模拟 x86 映像了。

打开 Android 工具或者直接从 Eclipse 中调出 AVD 创建工具。

图 6-10 和 6-11 显示了使用英特尔凌动(x86) CPU 为姜饼创建 AVD。

9781430261308_Fig06-10.jpg

图 6-10 。新的 AVD 创作

9781430261308_Fig06-11.jpg

图 6-11 。成功对话框

通过选择 AVD 并点击开始来测试 x86 姜饼 AVD,如图 6-12 所示。

9781430261308_Fig06-12.jpg

图 6-12 。发射选项

图 6-13 显示了模拟器 x86 上的英特尔凌动(x86)姜饼的主屏幕。

9781430261308_Fig06-13.jpg

图 6-13 。主屏幕

建议您使用支持英特尔 VT 硬件加速的 x86 仿真器。在 Linux 上,您可以使用 Linux KVM 来完成这项工作。Ubuntu 在https://help.ubuntu.com/community/KVM有更多关于如何配置和使用它的文档。

清单 6-7 。KVM

$ emulator-x86 –avd gbx86 –qemu –m 512 –enable-kvm

使用 KVM,如清单 6-7 ( -enable-kvm)所示,用户可能会注意到 Android 启动期间的性能提升,以及更快的仿真器响应速度。

姜饼的主要特点

下一节重点介绍 Gingerbread 支持的一些关键特性。这些功能在最新版本的 Android 中也得到支持,并计划继续提供支持。

电池使用统计

在关于手机中,如图图 6-14 所示,有一个电池使用部分。电池状态会因仿真设备和版本而异。在开发者可以telnet进入仿真设备的情况下,一些简单的命令可以用来模拟电池消耗。在http://android-er.blogspot.com/2010/09/how-to-set-battery-status-of-android.html看一个这样的例子。

9781430261308_Fig06-14.jpg

图 6-14 。关于电话视图

任务管理器

从设置image应用image运行服务中,选择运行选项卡。它显示了当前正在运行的程序,如你在图 6-15 中所见。

9781430261308_Fig06-15.jpg

图 6-15 。任务管理器的运行选项卡

例如,开发者可以通过点击项目并选择停止来关闭设置过程。

剪切和粘贴文本

打开“信息”应用并选取“新信息”可让您像发送短信一样键入信息。通过在文本字段中点击并在主机键盘上键入,字符会无缝地出现在 Android 屏幕上。输入Hello 2.3.5!后,屏幕看起来像图 6-16 。

9781430261308_Fig06-16.jpg

图 6-16 。信息传递乐趣

如果您将鼠标拖动到找到Hello 2.3.5!的文本字段,然后按住鼠标按钮(或触摸板按钮)大约两秒钟,会出现一个工具提示菜单来编辑文本。如果选择全选,然后重复鼠标操作,可以剪切文本。剪切文本后,可以再次重复鼠标操作,将文本粘贴到其他地方。

冰淇淋三明治模拟

x86 Android 4.0.4 模拟器系统映像使您能够在开发机器上运行 Android 冰激凌三明治的模拟。结合 Android SDK,您可以在基于英特尔架构(x86)的虚拟 Android 设备上测试您的 Android 应用。

为了安装仿真器系统映像,您可以使用 Android SDK 管理器(推荐方法),或者您可以下载二进制 ZIP 文件并将包含的目录解压缩并复制到您的 Android SDK 安装的add-ons目录中。(请注意,此方法不允许自动更新附加组件。)

以下部分提供了 ICS 映像安装指南。

先决条件

Android x86 模拟器映像需要安装 Android SDK。有关安装和配置 Android SDK 的说明,请参考 Android 开发者网站(参见http://developer.android.com/sdk/)。

image 注意可以使用英特尔硬件加速执行管理器(英特尔 HAXM)对面向 Android 的 x86 仿真器映像进行加速。有关更多信息,请参见 第十一章:使用英特尔硬件加速执行管理器在 x86 仿真上加速 Android

通过 Android SDK 管理器下载

  1. 启动 Android SDK 管理器。

  2. Under Android 4.0.4 (some screenshots may refer to older versions), select Intel x86 Atom System Image, as shown in Figure 6-17.

    9781430261308_Fig06-17.jpg

    图 6-17 。英特尔 x86 凌动系统映像

  3. 选中后,单击“安装软件包”按钮。

  4. 查看英特尔公司许可协议。如果您接受这些条款,请选择接受并单击安装。

  5. SDK 管理器将下载系统映像并提取到 Android SDK 目录中的适当位置。

使用系统图像

  1. Start the Android AVD Manager and create a new AVD, setting Target to Android 4.0.X, and CPU/ABI to Intel Atom (x86), as shown in Figure 6-18.

    9781430261308_Fig06-18.jpg

    图 6-18 。设定目标

    image 注意如果英特尔凌动(x86) CPU/ABI 选项不可用,请确保系统映像安装正确。

  2. 单击创建 AVD 按钮。

  3. The AVD has been successfully created and is now ready to use, as shown in Figure 6-19.

    9781430261308_Fig06-19.jpg

    图 6-19 。图像就绪

手动下载

  1. 转到http://www.intel.com/software/android

  2. 下载英特尔 x86 凌动系统映像(位于工具和下载选项卡下)。

  3. Navigate to the directory containing the Android SDK, as shown in Figure 6-20.

    9781430261308_Fig06-20.jpg

    图 6-20 。Android SDK 目录

  4. The system-images directory contains Android’s system images, separated by architecture, as shown in Figure 6-21.

    9781430261308_Fig06-21.jpg

    图 6-21 。分离的图像

  5. Expand android-15 (this directory contains API level 15 system images), as shown in Figure 6-22.

    9781430261308_Fig06-22.jpg

    图 6-22 。API 级

  6. 将下载的系统镜像档案中包含的x86目录直接解压到android-15目录中。

  7. The directory structure should now look like Figure 6-23.

    9781430261308_Fig06-23.jpg

    图 6-23 。预期的目录结构

  8. 系统映像现在已经安装好,可以使用了。

CPU 加速

借助基于硬件的虚拟化和英特尔 VT-x 技术,您可以提高面向 Android 冰激凌三明治的英特尔凌动 x86 映像的性能。如果您的计算机配备了支持 VT-x 的英特尔处理器,建议您将英特尔 HAXM 用于此系统映像。有关英特尔 HAXM 的更多信息,请访问http://int-software.intel.com/en-us/android

image 注意英特尔 HAXM 仅适用于 Windows 和 OS X 操作系统。对于 Linux 主机,您可以使用基于内核的虚拟机(KVM)来提高仿真性能。有关在 Ubuntu 上安装和配置 KVM 的信息,请参考下面的指南https://help.ubuntu.com/community/KVM/Installation

GPU 加速

面向 Android 冰激凌三明治的英特尔凌动 x86 映像可以利用硬件 GPU 特性来提高游戏、图形密集型程序和用户界面元素的性能。

image 注意GPU 加速的功能和性能高度依赖于您计算机的显卡和图形驱动程序。

要使用硬件 GPU 加速,请执行以下步骤:

  1. 打开 Android AVD 管理器。

  2. 选择 AVD 并单击编辑。

  3. The AVD editor window will appear. In the Hardware section, click New, as shown in Figure 6-24.

    9781430261308_Fig06-24.jpg

    图 6-24 。硬件部分

  4. In the Property drop-down box, select GPU Emulation, as shown in Figure 6-25.

    9781430261308_Fig06-25.jpg

    图 6-25 。GPU 仿真选项

  5. 单击确定。

  6. After the GPU Emulation property has been added, change the Value to Yes, as shown in Figure 6-26.

    9781430261308_Fig06-26.jpg

    图 6-26 。值已更改为是

  7. 单击编辑 AVD 保存 AVD。

  8. After the AVD has been modified, a dialog box will appear confirming the AVD settings, shown in Figure 6-27.

    9781430261308_Fig06-27.jpg

    图 6-27 。确认对话框

在确认对话框中,hw.gpu.enabled=yes行表示为该特定 AVD 启用了 GPU 加速。

image 注意必须基于每个 AVD 启用 GPU 加速。

概观

在这一章中,你建立了一个全功能的 Android 开发环境。您还安装了 Android 和 SDK 的先决条件。本章详细讨论了 Android 模拟器,您创建了一个 x86 模拟器以便于测试。您甚至在虚拟 x86 平台上创建了一个全功能的 Android 4.0.4(冰激凌三明治)模拟器,用于测试 Android 的最新特性。在下一章中,您将学习如何安装和使用 Android 原生开发套件,以便为英特尔平台创建和移植应用。

七、创建和移植基于 NDK 的 Android 应用

很明显,我们的科技已经超越了我们的人性。

—阿尔伯特·爱因斯坦

Android 应用可以使用本地开发工具包(NDK) 工具集合并本地代码。它允许开发人员重用遗留代码,为低级硬件编程,并通过利用非最佳或不可能的特性来区分他们的应用。

本章深入介绍了如何为英特尔架构创建基于 NDK 的应用。它还涵盖了移植现有的基于 NDK 的应用的案例。它深入讨论了英特尔编译器和默认 NDK 编译器之间的差异,并解释了如何充分利用英特尔 NDK 环境。

JNI 和 NDK 简介

JNI 简介

我们知道 Java 应用并不直接运行在硬件上,而是实际运行在一个虚拟机上。应用的源代码不是被编译以获得硬件指令,而是被编译以获得虚拟机的解释来执行代码。比如 Android 应用运行在 Dalvik 虚拟机上;它的编译代码是 DEX 格式的 Dalvik 虚拟机的可执行代码。这个特性意味着 Java 运行在虚拟机上,确保了它的跨平台能力:这就是它的“编译一次,随处运行”的特性。Java 的这种跨平台能力导致它与本地机器的各种内部组件的连接较少,并限制了它与本地机器的各种内部组件的交互,使得很难使用本地机器指令来利用机器的性能潜力。很难利用基于本地的指令来运行巨大的现有软件库,因此功能和性能受到限制。

有没有办法让 Java 代码和原生代码软件协同工作,共享资源?答案是肯定的——通过使用 Java 本地接口(JNI) ,这是一种 Java 本地操作的实现方法。JNI 是一个 Java 平台,被定义为与本地平台上的代码进行交互的 Java 标准。(一般称为主机平台。但这一章是针对移动平台的,为了和移动交叉开发主机区分,我们称之为本地平台。)所谓“接口”包括两个方向——一个是 Java 代码调用原生函数(方法),一个是本地应用调用 Java 代码。相对来说,前一种方法在 Android 应用开发中使用的更多。因此,本章主要关注 Java 代码调用本地函数的方法。

Java 通过 JNI 调用本地函数的方式是将本地方法以库文件 s 的形式存储,比如在 Windows 平台上,文件在。DLL 文件格式,并且在 UNIX/Linux 机器上文件位于。所以文件格式。通过调用本地库文件的内部方法,Java 可以与本地机器建立密切联系。这被称为各种接口的系统级方法

JNI 通常有两种使用场景:一是能够使用遗留代码(例如 C/C++、Delphi 等开发工具);第二,更直接地与硬件交互以获得更好的性能。当你阅读这一章的时候,你会看到其中的一些内容。

JNI 一般工作流程如下:Java 发起调用,让本地函数的侧代码(比如用 C/C++ 写的函数)运行。这一次,对象是从 Java 端传递过来的,并在本地函数完成时运行。在运行完一个本地函数之后,结果的值被返回给 Java 代码。这里,JNI 是一个适配器,在 Java 语言和本地编译语言(如 C/C++)之间映射变量和函数(Java 方法)。我们知道 Java 和 C/C++ 在函数原型定义和变量类型上有很大的不同。为了使两者匹配,JNI 提供了一个jni.h文件来完成两者之间的映射。这个过程如图图 7-1 所示。

9781430261308_Fig07-01.jpg

图 7-1 。JNI 通用工作流程

通过 JNI 和 Java 程序(尤其是 Android 应用)调用 C/C++ 函数的一般框架如下:

  1. 编译 native 的方式是在 Java 类中声明的(C/C++ 函数)。
  2. 编译包含原生方法的.java源代码文件(在 Android 中构建项目)。
  3. javah命令生成一个.h文件,根据.class文件对应本地方法。
  4. C/C++ 方法是用来实现本地方法的。
  5. 这一步推荐的方法是先将函数原型复制到.h文件中,然后修改函数原型,添加函数体。在此过程中,应注意以下几点:
    • JNI 函数调用必须使用 C 函数。如果是 C++ 函数,别忘了加上extern C 关键字。
    • 方法名的格式应该遵循以下模板:Java_package_class_method,即Java_package名称类名和函数方法名。
  6. C 或 C++ 文件被编译成一个动态库(在 Windows 下这是一个. DLL 文件,在 UNIX/Linux 下是一个. SO 文件)。

使用 Java 类中的System.loadLibrary()System.load()方法加载生成的动态库。

这两个功能略有不同:

  • System.loadLibrary() :加载本地链接库下的默认目录(例如对于 Windows,这是\System32, jre\bin,以此类推)。
  • System.load() :根据添加到交叉链接库的本地目录,必须使用绝对路径。

第一步,Java 调用原生 C/C++ 函数;C 和 C++ 的格式不一样。例如,对于 Java 方法,如不传递参数和返回一个String类,C 和 C++ 代码在以下方面有所不同:

c 代码:

Call function:(*env) -> <jni function> (env, <parameters>)
Return jstring:return (*env)->NewStringUTF(env, "XXX");

C++ 代码:

Call function:env -> <jni function> (<parameters>)
Return jstring:return env->NewStringUTF("XXX");

其中两个 Java String对象NewStringUTF函数都是由 JNI 提供的 C/C++ 生成的。

Java 方法及其与 C 函数原型 Java 的对应关系

回想一下,为了让 Java 程序调用代码框架中的 C/C++ 函数,您使用了javah命令,该命令将根据.class文件为本地方法生成相应的.h文件。.h文件是按照一定的规则生成的,从而使正确的 Java 代码找到对应的 C 函数来执行。

例如,下面的 Android Java 代码:

public class HelloJni extends Activity

1.   {
2.      public void onCreate(Bundle savedInstanceState)
3.      {
4.         TextView tv.setText(stringFromJNI() );  // Use C function Code
5.      }
6.      publicnativeString  stringFromJNI();
7.   }

对于第 4 行使用的 C 函数stringFromJNI(),由javah生成的.h文件中的函数原型是:

1.   JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI
2.     (JNIEnv *, jobject);

在这方面,C 源代码文件对于函数代码的定义大致有:

1.     /*
2.     ...
3.     Signature: ()Ljava/lang/String;
4.     */
5.     jstring Java_com_example_hellojni_HelloJni_stringFromJNI (JNIEnv* env,  jobject thiz )
6.     {
7.          ...
8.         return (*env)->NewStringUTF(env, "...");
9.     }

从这段代码中可以看到,函数名相当长,但还是很有规律,完全符合命名约定:java_package_class_methodHello.java中的stringFromJNI()方法对应 C/C++ 中的Java_com_example_hellojni_HelloJni_stringFromJNI()方法。

注意Signature: ()Ljava/lang/String;的注释。()Ljava/lang/String;的括号()表示函数参数为空,这意味着除了两个参数JNIEnv *jobject之外,没有其他参数。JNIEnv *jobject是所有 JNI 函数必须拥有的两个参数,分别针对jni环境和对应的Java类(或对象)本身。Ljava/lang/String;表示函数的返回值是一个 Java String对象。

Java 和 C 数据类型映射

如前所述,Java 和 C/C++ 变量类型非常不同。JNI 提供了一种机制来完成 Java 和 C/C++ 之间的映射。主要类型之间的对应关系如表 7-1 所示。

表 7-1 。Java 到 C 类型映射

|

Java 类型

|

原生类型

|

描述

|
| --- | --- | --- |
| boolean | jboolean | C/C++ 8 位整数 |
| byte | jbyte | C/C++ 无符号 8 位整数 |
| char | jchar | C/C+无符号 16 位整数 |
| short | jshort | C/C++ 有符号 16 位整数 |
| int | jint | C/C++ 有符号 32 位整数 |
| long | jlong | C/C++ 无符号 64 位整数 |
| float | jfloat | C/C++ 32 位浮点 |
| double | jdouble | C/C++ 64 位浮点 |
| void | void | 不适用的 |
| Object | jobject | 任何 Java 对象,或者不对应于java类型的对象 |
| Class | jclass | 类对象 |
| String | jstring | 字符串对象 |
| Object[] | jobjectArray | 任何对象的数组 |
| Boolean[] | jbooleanArray | 布尔数组 |
| byte[] | jbyteArray | 比特阵列 |
| char[] | jcharArray | 字符数组 |
| short[] | jshortArray | 短整数数组 |
| int[] | jintArray | 整数数组 |
| long[] | jlongArray | 长整数数组 |
| float[] | jfloatArray | 浮点数组 |
| double[] | jdoubleArray | 双浮点阵列 |

imageNoteJava 类型与本地(C/C++)类型的对应关系。

当传递一个 Java 参数时,使用 C 代码的思路如下:

  • 基本类型可以直接使用;比如doublejdouble可以互通。基本类型是从表 7-1 中的booleanvoid行所列的类型。在这种类型中,如果用户将一个boolean参数传递给方法,就会有一个名为jboolean的本地方法对应于boolean类型。类似地,如果本地方法返回一个jint,那么 Java 会返回一个int
  • Java 对象用法。一个Object对象有String对象和一个通用对象。这两个对象的处理方式略有不同。
  • String对象。Java 程序传递的String对象是本地方法中对应的jstring类型。C 中的jstring类型和char *不同。所以如果你只是把它当成一个char *,就会出现错误。因此,您需要在使用之前将jstring转换成 C/C++ 中的char *。这里我们用JNIEnv的方法进行换算。
  • Object对象。使用以下代码获取类的对象处理程序:
jclass objectClass = (env)->FindClass("com/ostrichmyself/jni/Structure");

然后使用以下代码获取该类所需的域处理程序:

jfieldID str = (env)->GetFieldID(objectClass,"nameString","Ljava/lang/String;");
jfieldID ival = (env)->GetFieldID(objectClass,"number","I");

然后使用下面类似的代码为jobject对象的传入字段赋值:

(env)->SetObjectField(theObjet,str,(env)->NewStringUTF("my name is D:"));
(env)->SetShortField(theObjet,ival,10);
  • 如果没有传入的对象,那么 C 代码可以使用下面的代码来生成新的对象:

    jobject myNewObjet = env->AllocObject(objectClass);
    
  • Java 数组处理。对于数组类型,JNI 提供了一些可操作的函数。例如,GetObjectArrayElement可以接受传入的数组,并使用NewObjectArray创建一个数组结构。

  • 资源释放的原则。C/C++ new的对象或者malloc的对象需要使用 C/C++ 来释放内存。

  • 如果JNIEnv方法的新对象没有被 Java 使用,就必须释放它。

  • 使用GetStringUTFChars从 Java 转换一个 string 对象得到 UTF,需要打开内存,使用完char *后必须释放内存。使用的方法是ReleaseStringUTFChars

这些是 Java 与 C/C++ 交换数据时类型映射的简要描述。有关 Java 和 C/C++ 数据类型的更多信息,请参考相关的 Java 和 JNI 书籍、文档和示例。

NDK 简介

从前面的描述中,你知道 Java 代码可以使用 JNI 访问本地函数(比如 C/C++)。要达到这种效果,你需要开发工具。有一整套基于核心 Android SDK 的开发工具,您可以使用它们将 Java 应用交叉编译为可以在目标 Android 设备上运行的应用。同样,您需要交叉开发工具来将 C/C++ 代码编译成可以在 Android 设备上运行的应用。这个工具就是安卓原生开发套件,或者安卓 NDK。

在 NDK 之前,Android 平台上的第三方应用是在一个特殊的基于 Java 的 Dalvik 虚拟机上开发的。原生 SDK 允许开发人员直接访问 Android 系统资源,并使用传统的 C 或 C++ 编程语言创建应用。应用包文件(.apk)可以直接嵌入到本地库中。简而言之,通过 NDK,原本在 Dalvik 虚拟机上运行的 Android 应用现在可以使用 C/C++ 等本地代码语言来执行程序。这提供了以下好处:

  • 性能提升。它使用本机代码来开发程序中需要高性能的部分,并直接访问 CPU 和硬件。
  • 重用现有本机代码的能力。

当然,相对于 Dalvik 虚拟机,使用原生 SDK 编程也有一些缺点,比如增加了程序复杂度,兼容性难以保证,无法访问框架 API,调试更加困难,灵活性降低等等。此外,访问 JNI 会导致一些额外的性能开销。

简而言之,NDK 应用开发有其优点和缺点。你需要根据自己的判断使用 NDK。最佳策略是使用 NDK 来开发应用中本机代码可以提高性能的部分。

NDK 包括以下主要部件:

  • 工具和构建文件从 C/C++ 生成本机代码库。这包括一系列的 NDK 命令,包括javah(使用.class文件生成相应的.h文件)、gcc(稍后描述)和其他命令。它还包括ndk-build可执行脚本等等,这些将在后面的会话中详细介绍。
  • 应用包(应用包文件,即.apk文件)中会嵌入一个一致的本地库,可以部署在 Android 设备中。
  • 对所有未来 Android 平台的一些原生系统头文件和库的支持。

NDK 应用开发的流程框架如图图 7-2 所示。Android 应用由三部分组成:Android 应用文件、Java 本地库文件和动态库。这三个部分通过各自的生成路径从不同的源生成。对于一个普通的 Android 应用,Android SDK 生成 Android 应用文件和 Java 原生库文件。Android NDK 生成动态库文件(带有。SO 扩展名)使用非本机代码(通常是 C 源代码文件)。最后在目标机器上安装 Android 应用文件、Java 库文件和本地动态库,并运行完整的协作应用。

9781430261308_Fig07-02.jpg

图 7-2 。安卓 NDK 应用开发流程图

NDK 开发的应用项目(简称 NDK 应用项目)有组件,如图图 7-3 所示。与使用 Android SDK 开发的典型应用相比,在 NDK 开发的项目添加了 Dalvik 类代码、清单文件、公共资源,以及 JNI 和 NDK 生成的共享库。

9781430261308_Fig07-03.jpg

图 7-3 。Android NDK 应用的应用组件

Android 在其关键 API 版本中增加了 NDK 支持。每个版本都包括一些新的 NDK 特性、简单的 C/C++、兼容的 STL、硬件扩展等等。这些特性使得 Android 更加开放,更加强大。Android API 及其与 NDK 的对应关系如表 7-2 所示。

表 7-2 。主要 Android API 与 NDK 版本的关系

|

API 版本

|

支持的 NDK 版本

|
| --- | --- |
| API 级 | Android 1.5 NDK 1 |
| API 级 | Android 1.6 NDK 2 |
| API 级 | Android 2.1 NDK 3 |
| API 级 | Android 2.2 NDK 4 |
| API 级 | Android 2.3 NDK 5 |
| API 级 | Android 3.1 NDK 6 |
| API 级 | Android 4.0.1 NDK 7 |
| API 级 | Android 4.0.3 NDK 8 |
| API 级 | Android 4.1 NDK 8b |
| API 级 | Android 4.2 NDK 8d |
| API 级 | Android 4.3 NDK 9b |

image 提示使用安卓 NDK 生成的每一段原生代码都被赋予了一个匹配的应用二进制接口(ABI) 。ABI 精确地定义了应用及其代码在运行时如何与系统交互。ABI 可以大致理解为类似于计算机架构中的 ISA(指令集架构)。

典型的 ABI 包含以下信息:

  • CPU 指令集应该使用的机器代码。
  • 运行时内存访问排名。
  • 可执行二进制文件的格式(动态库、程序等)以及允许和支持的内容类型。
  • 在应用代码和系统之间传递数据时使用的不同约定(例如,函数调用何时注册和/或如何使用堆栈、对齐限制等)。
  • 枚举类型、结构字段和数组的对齐和大小限制。
  • 运行时应用机器码的可用函数符号列表通常来自一组非常特定的库。每个受支持的 ABI 都有一个唯一的名称。

Android 目前支持以下 ABI 类型:

  • ARM eabi–这是 ARM CPU 的 abi 名称,它至少支持 ARMv5TE 指令集。
  • ARM eabi-v7a–这是基于 ARM 的 CPU 的另一个 abi 名字;它扩展了 armeabi CPU 指令集扩展,如 Thumb-2 指令集扩展和用于向量浮点硬件的浮点处理单元指令。
  • x86——这是 ABI 的名字,一般称为支持 x86 或 IA-32 指令集的 CPU。更具体地说,它的目标在下面的会话中经常被称为 i686 或奔腾 Pro 指令集。英特尔凌动处理器属于这种 ABI 类型。

这些类型具有不同的兼容性。X86 与 armeabi 和 armeabi-v7a 不兼容。armeabi-v7a 机器与 armeabi 兼容,这意味着 armeabi 框架指令集可以在 armeabi-v7a 机器上运行,但不一定相反,因为一些 ARMv5 和 ARMv6 机器不支持 armeabi-v7a 代码。因此,当您构建应用时,应该根据用户对应的 ABI 机器类型仔细选择用户。

NDK 装置

这里我们以 NDK Windows 环境为例来说明 NDK 软件的安装。Windows NDK 包括以下模块:

  • Cygwin 在 Windows 命令行中运行 Linux 命令。
  • 安卓 NDK 包,包括ndk-build等按键命令,是 NDK 软件的核心;它将 C/C++ 文件编译成。所以共享库文件。
  • CDT (C/C++ 开发工具,C/C++ 开发工具)是一个 Eclipse 插件,可以将 C/C++ 文件编译成。所以 Eclipse 中的共享库。这意味着您可以使用它来ndk-build替换命令行命令。

CDT 模块不是必需的,但它确实支持在熟悉的 Eclipse IDE 中进行开发。Cygwin 模块必须安装在 Windows 环境中,但在 Linux 环境中不是必需的。当然,整个开发环境需要支持 Java 开发环境。以下部分分别解释了每个模块的安装步骤。

安卓 NDK 安装

本节介绍如何安装 Android NDK:

  1. Visit the Android NDK official web site at http://developer.android.com/sdk/ndk/index.html and download the latest NDK package, as shown in Figure 7-4. In this case, you click on the file android-ndk-r8d-windows.zip and download the files to the local directory.

    9781430261308_Fig07-04.jpg

    图 7-4 。NDK 包下载页面来自安卓官方网站

  2. 安装安卓 NDK。

Android NDK 安装相对简单。你需要做的就是把下载的android-ndk-r4b-windows.zip解压到指定的目录。在本例中,我们将 Android NDK 安装在目录D:\Android\android-ndk-r8d中。您需要记住这个位置,因为下面的配置需要它来设置环境。

安装 Cygwin

本节介绍如何安装 Cygwin:

  1. Visit Cygwin’s official web site (http://www.cygwin.com/). Download the Cygwin software, as shown in Figure 7-5. Go to the download page, and then click on the setup.exe file to download and install packages.

    9781430261308_Fig07-05.jpg

    图 7-5 。Cygwin 下载页面

  2. Double-click the downloaded setup.exe file to start the installation. The pop-up shown in Figure 7-6 appears.

    9781430261308_Fig07-06.jpg

    图 7-6 。Cygwin 初始安装窗口

  3. The installation mode selection box is shown in Figure 7-7. In this example, select Install from Internet mode.

    9781430261308_Fig07-07.jpg

    图 7-7 。Cygwin 安装模式选择

  4. The display installation directory and user settings selection box is shown in Figure 7-8.

    9781430261308_Fig07-08.jpg

    图 7-8 。安装目录和用户设置选择

  5. You are next prompted to enter a temporary directory to store the downloaded files, as shown in Figure 7-9.

    9781430261308_Fig07-09.jpg

    图 7-9 。Cygwin 下载文件临时目录设置

  6. Next you are prompted to select an Internet connection type, as shown in Figure 7-10. For this example, select Direct Connection.

    9781430261308_Fig07-10.jpg

    图 7-10 。Cygwin 设置互联网连接类型选择

  7. You are now prompted to select a download mirror site, as shown in Figure 7-11.

    9781430261308_Fig07-11.jpg

    图 7-11 。Cygwin Install:提示选择下载镜像站点

  8. Start the download and install the basic parts, as shown in Figure 7-12(a). During the setup, a Setup alert will indicate that this is the first time you are installing Cygwin, as shown in Figure 7-12(b). Click OK to continue.

    9781430261308_Fig07-12.jpg

    图 7-12 。Cygwin 安装包下载安装

  9. Select the packages to install, as shown in Figure 7-13. The default is to install all of the packages.

    9781430261308_Fig07-13.jpg

    图 7-13 。Cygwin 软件包安装选择

    您下载了所有组件,总大小超过 3GB。这需要在正常的宽带网速下花费很长时间;实际上不建议安装所有组件。你需要安装 NDK Devel 组件和 Shells 组件,如图 7-14 所示。

    9781430261308_Fig07-14.jpg

    图 7-14 。NDK 要求的 Cygwin 组件包

    从安装组件包中选择 Devel 和 Shells 的一些技巧。你可以先点击所有旁边的循环图标;它将在安装、默认和卸载之间循环。将其设置为 Uninstall 状态,然后单击 Devel 和 Shells 条目旁边的循环图标,使其保持 install 状态。最后,单击“下一步”继续。

  10. The contents of the selected components are displayed next, as shown in Figure 7-15.

![9781430261308_Fig07-15.jpg](https://gitee.com/OpenDocCN/vkdoc-android-pt2-zh/raw/master/docs/andr-x86/img/9781430261308_Fig07-15.jpg)

图 7-15 。选择 Cygwin 组件包后的依赖提醒
  1. Start to download and install the selected components, as shown in Figure 7-16.
![9781430261308_Fig07-16.jpg](https://gitee.com/OpenDocCN/vkdoc-android-pt2-zh/raw/master/docs/andr-x86/img/9781430261308_Fig07-16.jpg)

图 7-16 。Cygwin 下载并安装选定的组件
  1. Installation is complete. Message boxes appear, as shown in Figure 7-17.
![9781430261308_Fig07-17.jpg](https://gitee.com/OpenDocCN/vkdoc-android-pt2-zh/raw/master/docs/andr-x86/img/9781430261308_Fig07-17.jpg)

图 7-17 。安装完成后,Cygwin 提醒框
  1. 配置 Cygwin Windows path 环境变量。

按照以下步骤将 NDK 包安装目录和 Cygwin bin 目录添加到path环境变量中:

  1. 在桌面上,右键单击我的电脑并选择\属性\高级\环境变量菜单项。
  2. 点击PATH变量中的系统变量。然后点击安装目录后添加的【变量值】NDK 包的对话框中的编辑按钮,在子目录 build\tools\cygwin\bin 下。

例如,如果 NDK 安装在目录D:\Android\android-ndk-r8d中,而 Cygwin 安装在目录D:\cygwin中,则在PATH变量后添加路径,如下所示:

PATH=...;D:\Android\android-ndk-r8d;D:\Android\android-ndk-r8d\build\tools;D:\cygwin\bin

这样配置成功后,就可以使用 Linux 命令下的控制台命令cmd了。例如,图 7-18 显示了一个带有 Windows dir命令和 Linux ls命令的命令行窗口。

9781430261308_Fig07-18.jpg

图 7-18 。安装 NDK 后的命令行窗口

您为 NDK 配置 Cygwin 的内部环境变量,如下所示:

  1. Before configuring the NDK Cygwin internal environment variables, you must run Cygwin at least once, otherwise the \cygwin\home directory will be empty. Click the Browse button in Windows Explorer and select the mintty.exe file under the bin subdirectory of the Cygwin installation directory (in this example, it is located at D:\cygwin\bin). The window is shown in Figure 7-19.

    9781430261308_Fig07-19.jpg

    图 7-19 。第一次启动 Cygwin 时的初始窗口

  2. Then select the Windows menu \programs\Cygwin\Cygwin terminal. You can directly enter the Cygwin window, as shown in Figure 7-20.

    9781430261308_Fig07-20.jpg

    图 7-20 。Cygwin 窗口(如果不是第一次运行)

    这将在empty\cygwin\home下创建一个用户名(在本例中是 Windows 登录用户名hlgu)子目录,并在该目录下生成几个文件。

    D:\cygwin\home\hlgu>dir
    2013-01-30  00:42             6,054 .bashrc
    2013-01-30  00:52                 5 .bash_history
    2013-01-30  01:09             1,559 .bash_profile
    2013-01-30  00:42             1,919 .inputrc
    2012-12-01  08:58             8,956 .mkshrc
    2013-01-30  00:42             1,236 .profile
    
  3. Find .bash_profile in the installation directory cygwin\home\<username>\ file. In this case, it is D:\cygwin\home\hlgu\.Bash_profile. To the end of the file, add the following code:

    NDK=<android-ndk-r4b unzipped_NDK_folder>
    export NDK
    ANDROID_NDK_ROOT=<android-ndk-r4b unzipped_NDK_folder >
    export ANDROID_NDK_ROOT
    

    <android-ndk-r4b unzipped_NDK_folder >对应 NDK 包的安装目录。(本例中是D:\Android\android-ndk-r8d。)Cygwin 提供了一个目录转换机制。在目录前面加上/cygdrive/DRIVELETTER/,表示驱动器中的指定目录。这里,DRIVELETTER是目录的驱动字母。考虑这个例子:

    NDK= /cygdrive/d/Android/android-ndk-r8d
    export NDK
    ANDROID_NDK_ROOT=/cygdrive/d/Android/android-ndk-r8d
    export ANDROID_NDK_ROOT
    
  4. Determine whether the command can be run by testing the make command.

    C:\Documents and Settings\hlgu>make -v
    GNU Make 3.82.90
    Built for i686-pc-cygwin
    Copyright (C) 2010 Free Software Foundation, Inc.
    License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
    This is free software: you are free to change and redistribute it.
    There is NO WARRANTY, to the extent permitted by law.
    

    如果你看到这个输出,这意味着make命令运行正常。确保 Make 的版本为 3.8.1 或更高版本,因为此会话中的所有示例都需要 3.8.1 或更高版本才能成功编译。

    现在您可以测试gccg+gcjgnat命令:

    C:\Documents and Settings\hlgu>gcc -v
    Access denied.
    C:\Documents and Settings\hlgu>g++ -v
    Access denied.
    C:\Documents and Settings\hlgu>gcj
    Access denied
    C:\Documents and Settings\hlgu>gnat
    Access denied.
    

    如果您收到Access denied消息,您需要继续以下步骤。否则,安装成功完成。

  5. 在 Cygwin 的bin目录下,删除gcc.exeg++.exegcj.exegnat.exe文件。

  6. 在同一目录下,选择需要的与版本匹配的gccg++gcjgnat文件。比如版本 4 对应gcc-4.exeg++-4.exegcj-4.exegnat-4.exe。复制这些文件,并将复制的文件重命名为gcc.exeg++.exegcj.exegnat.exe

  7. Now test again to see if gcc and the other commands can run:

    C:\Documents and Settings\hlgu> gcc -v
    

    使用内置规范,您可以看到哪些命令可用:

    COLLECT_GCC=gcc
    COLLECT_LTO_WRAPPER=/usr/lib/gcc/i686-pc-cygwin/4.5.3/lto-wrapper.exe
    Target: i686-pc-cygwin
    Configured with: /gnu/gcc/releases/respins/4.5.3-3/gcc4-4.5.3-3/src/gcc-4.5.3/co
    nfigure --srcdir=/gnu/gcc/releases/respins/4.5.3-3/gcc4-4.5.3-3/src/gcc-4.5.3 --
    prefix=/usr --exec-prefix=/usr --bindir=/usr/bin --sbindir=/usr/sbin --libexecdi
    r=/usr/lib --datadir=/usr/share --localstatedir=/var --sysconfdir=/etc --dataroo
    tdir=/usr/share --docdir=/usr/share/doc/gcc4 -C --datadir=/usr/share --infodir=/
    usr/share/info --mandir=/usr/share/man -v --with-gmp=/usr --with-mpfr=/usr --ena
    ble-bootstrap --enable-version-specific-runtime-libs --libexecdir=/usr/lib --ena
    ble-static --enable-shared --enable-shared-libgcc --disable-__cxa_atexit --with-
    gnu-ld --with-gnu-as --with-dwarf2 --disable-sjlj-exceptions --enable-languages=
    ada,c,c++,fortran,java,lto,objc,obj-c++ --enable-graphite --enable-lto --enable-
    java-awt=gtk --disable-symvers --enable-libjava --program-suffix=-4 --enable-lib
    gomp --enable-libssp --enable-libada --enable-threads=posix --with-arch=i686 --w
    ith-tune=generic --enable-libgcj-sublibs CC=gcc-4 CXX=g++-4 CC_FOR_TARGET=gcc-4
    CXX_FOR_TARGET=g++-4 GNATMAKE_FOR_TARGET=gnatmake GNATBIND_FOR_TARGET=gnatbind -
    -with-ecj-jar=/usr/share/java/ecj.jar
    Thread model: posix
    gcc version 4.5.3 (GCC)
    
    C:\Documents and Settings\hlgu>g++ -v
    

    使用内置的规范,比如gcc,您可以看到哪些命令是可用的:

    COLLECT_GCC=g++
    COLLECT_LTO_WRAPPER=/usr/lib/gcc/i686-pc-cygwin/4.5.3/lto-wrapper.exe
    Target: i686-pc-cygwin
    Configured with: /gnu/gcc/releases/respins/4.5.3-3/gcc4-4.5.3-3/src/gcc-4.5.3/co
    nfigure --srcdir=/gnu/gcc/releases/respins/4.5.3-3/gcc4-4.5.3-3/src/gcc-4.5.3 --
    prefix=/usr --exec-prefix=/usr --bindir=/usr/bin --sbindir=/usr/sbin --libexecdi
    r=/usr/lib --datadir=/usr/share --localstatedir=/var --sysconfdir=/etc --dataroo
    tdir=/usr/share --docdir=/usr/share/doc/gcc4 -C --datadir=/usr/share --infodir=/
    usr/share/info --mandir=/usr/share/man -v --with-gmp=/usr --with-mpfr=/usr --ena
    ble-bootstrap --enable-version-specific-runtime-libs --libexecdir=/usr/lib --ena
    ble-static --enable-shared --enable-shared-libgcc --disable-__cxa_atexit --with-
    gnu-ld --with-gnu-as --with-dwarf2 --disable-sjlj-exceptions --enable-languages=
    ada,c,c++,fortran,java,lto,objc,obj-c++ --enable-graphite --enable-lto --enable-
    java-awt=gtk --disable-symvers --enable-libjava --program-suffix=-4 --enable-lib
    gomp --enable-libssp --enable-libada --enable-threads=posix --with-arch=i686 --w
    ith-tune=generic --enable-libgcj-sublibs CC=gcc-4 CXX=g++-4 CC_FOR_TARGET=gcc-4
    CXX_FOR_TARGET=g++-4 GNATMAKE_FOR_TARGET=gnatmake GNATBIND_FOR_TARGET=gnatbind -
    -with-ecj-jar=/usr/share/java/ecj.jar
    Thread model: posix
    gcc version 4.5.3 (GCC)
    
    C:\Documents and Settings\hlgu>gcj
    gcj: no input files
    
    C:\Documents and Settings\hlgu>gnat
    GNAT 4.5.3
    Copyright 1996-2010, Free Software Foundation, Inc.
    
    List of available commands
    
    gnat bind               gnatbind
    gnat chop               gnatchop
    gnat clean              gnatclean
    gnat compile            gnatmake -f -u -c
    gnat check              gnatcheck
    gnat sync               gnatsync
    gnat elim               gnatelim
    gnat find               gnatfind
    gnat krunch             gnatkr
    gnat link               gnatlink
    gnat list               gnatls
    gnat make               gnatmake
    gnat metric             gnatmetric
    gnat name               gnatname
    gnat preprocess         gnatprep
    gnat pretty             gnatpp
    gnat stack              gnatstack
    gnat stub               gnatstub
    gnat xref               gnatxref
    Commands find, list, metric, pretty, stack, stub and xref accept project file sw
    itches -vPx, -Pprj and -Xnam=val
    
  8. 最后,检查一下 NDK 核心命令ndk-build脚本,看看它是否可以运行。

    C:\Documents and Settings\hlgu>ndk-build
    Android NDK: Your Android application project path contains spaces: 'C:/./ Settings/'
    Android NDK: The Android NDK build cannot work here. Please move your project to a different location.
    D:\Android\android-ndk-r8d\build/core/build-local.mk:137: *** Android NDK: Aborting. Stop.
    

如果您的输出看起来像这样,这表明 Cygwin 和 NDK 已经安装和配置成功。

安装 CDT

CDT 是一个 Eclipse 插件,它将 C 代码编译成。所以共享库。事实上,在安装了 Cygwin 和 NDK 模块后,你可以将 C 代码编译成。所以在命令行共享库,这意味着 Windows NDK 的核心组件已经安装。如果你仍然喜欢使用 Eclipse IDE 而不是命令行编译器来编译本地库,你需要安装 CDT 模块;否则,跳过这一步,直接看 NDK 的例子。

如果需要安装 CDT,请使用以下步骤:

  1. Visit Eclipse’s official web site at http://www.eclipse.org/cdt/downloads.php to download the CDT package. As shown on the download page in Figure 7-21, you can click to download a version of the software. In this case, click cdt-master-8.1.1.zip to start the download.

    9781430261308_Fig07-21.jpg

    图 7-21 。CDT 下载页面

  2. 启动 Eclipse。选择menu \HELP\Install new software开始安装 CDT。

  3. In the pop-up Install dialog box, click Add, as shown in Figure 7-22.

    9781430261308_Fig07-22.jpg

    图 7-22 。Eclipse 安装软件对话框

  4. In the pop-up Add Repository dialog box, enter a name for Name and a software download web site address in Location. You can enter the local address or the Internet address. If you’re using an Internet address, Eclipse will go to the Internet to download and install the package, while the local address will direct Eclipse to install the software from the local package. Enter the local address; then you can click the Archive button in the pop-up dialog box and enter the directory and filename for the downloaded cdt-master-8.1.1.zip file, as shown in Figure 7-23. If the file is downloaded from the Internet, the address is http://download.eclipse.org/tools/cdt/releases/galileo/.

    9781430261308_Fig07-23.jpg

    图 7-23 。Eclipse 软件更新安装地址对话框

  5. After returning to the Install dialog box, click to select the software components that need to be installed, as shown in Figure 7-24.

    9781430261308_Fig07-24.jpg

    图 7-24 。要安装的组件的 CDT 选择框

    在组件列表中,CDT 主要功能是必需的组件。在本例中,我们仅选择该组件。

  6. A list of detailed information about CDT components to install is displayed, as shown in Figure 7-25.

    9781430261308_Fig07-25.jpg

    图 7-25 。CDT 组件安装的详细信息

  7. Review the licenses dialog box. Click “I accept the terms of the license agreement” to continue, as shown in Figure 7-26.

    9781430261308_Fig07-26.jpg

    图 7-26 。CDT 执照审核窗口

  8. The installation process starts, as shown in Figure 7-27.

    9781430261308_Fig07-27.jpg

    图 7-27 。CDT 安装进度

  9. 当安装过程完成时,重启 Eclipse 以完成安装。

NDK 的例子

本节包括一个例子来说明 JNI 和 NDK 的用法。如前所述,NDK 可以从命令行运行,也可以在 Eclipse IDE 中运行。我们将使用这两种方法来生成相同的 NDK 应用。

使用命令行方法生成一个库文件

这个例子的名字是jnitest,它是一个演示 JNI 代码框架的简单例子。以下几节概述了这些步骤。

创建一个 Android 应用项目

首先,你需要创建一个 Android app 项目,编译代码,生成.apk包。在 Eclipse 中创建一个项目,并将项目命名为jnitest。选择 Build SDK 支持 x86 版本的 API(这里是 Android 4.0.3),如图图 7-28 所示。最后,您生成项目。

9781430261308_Fig07-28.jpg

图 7-28 。jnitest 项目参数设置

项目生成后,文件结构被创建,如图 7-29 所示。请注意库文件(在本例中为android.jar)所在的目录,因为下面的步骤将使用该参数。

9781430261308_Fig07-29.jpg

图 7-29 。jnitest 项目的文件结构

修改 Java 文件

接下来修改 Java 文件,使用 C 函数创建代码。在这种情况下,唯一的 Java 文件是MainActivity.java。您需要修改其代码,如下所示:

1.      package com.example.jnitest;
2.      import android.app.Activity;
3.      import android.widget.TextView;
4.      import android.os.Bundle;
5.      public class MainActivity extends Activity
6.      {
7.          @Override
8.          public void onCreate(Bundle savedInstanceState)
9.          {
10.             super.onCreate(savedInstanceState);
11.             TextView tv = new TextView(this);
12.             tv.setText(stringFromJNI() ); // stringFromJNIas a  C function
13.             setContentView(tv);
14.         }
15.     publicnativeString stringFromJNI();
16.
17.         static {
18.                     System.loadLibrary("jnitestmysharelib");
19.         }
20.     }

代码非常简单。在第 11 行到第 13 行,您使用一个TextView来显示从stringFromJNI()函数返回的字符串。但与之前讨论的 Android 应用不同的是,整个项目中没有任何地方可以找到这个功能的实现代码。那么函数的实现发生在哪里呢?在第 15 行中,您声明该函数不是用 Java 编写的,而是由本地(本机)库编写的,这意味着该函数在 Java 之外。既然它是在本地库中实现的,那么问题是什么库呢?答案在第 17–20 行中描述。System类的static函数LoadLibrary的参数描述了库的名称。该库是一个名为libjnitestmysharelib.so的 Linux 共享库。在静态区声明的应用代码将在Activity.onCreate之前执行。该库将在第一次使用时加载到内存中。

有趣的是,当loadLibrary函数加载库名时,它会自动在参数和前加上lib前缀。所以后缀结尾。当然,如果参数指定的库文件的名称以lib开头,该函数不会在文件名前添加前缀lib

在 Eclipse 中生成项目

只建(build),而不跑。这将编译项目,但是.apk文件不会被部署到目标机器上。

当这一步完成后,相应的.class文件将在名为bin\classes\com\example\jnitest的项目目录中生成。这一步必须在下一步之前完成,因为下一步需要合适的.class文件。

在项目根目录下创建一个子目录

将这个子目录命名为jni。例如,如果项目根目录是E:\temp\AndroidDev\workspace\jnitest,可以使用md命令创建jni子目录。

E:\temp\Android Dev\workspace\jnitest>mkdir jni

然后测试目录是否已经建立:

E:\temp\Android Dev\workspace\jnitest>dir
...
2013-02-01  00:45    <DIR>          jni

创建一个 C 接口文件

所谓 C 接口文件,就是配合本地(外部)函数工作的 C 函数原型。特定于这种情况的是stringFromJNI函数的 C 函数原型。您声明您需要使用external函数的原型,但是它是 Java 格式的:您需要将其更改为 C 格式构建 C-JNI 接口文件。这一步可以用javah命令来完成。命令格式是:

$ javah -classpath <directory of jar and .class documents>  -d <directory of .h documents>  <the package + class name of class>

命令参数描述如下:

  • -classpath:表示类路径
  • -d ...:表示生成的头文件的存储目录
  • <class name> : 正在使用的本机函数的完整.class类名,由“包+类的类名”组件组成。

对于此示例,请遵循以下步骤:

  1. 从命令行输入根目录(在本例中是E:\temp\Android Dev\workspace\jnitest)。
  2. 然后运行以下命令:
E:> javah -classpath "D:\Android\android-sdk\platforms\android-15\android.jar";bin/classes  com.example.jnitest.MainActivity

在这个例子中,使用的本机函数的stringFromJNI的类是MainActivity,编译这个类后的结果文件是MainActivity。类,它位于项目的根目录bin \classes\com\example目录下。其类MainActivity.java的源代码文件的第一行显示了该类的包在哪里:

package com.example.jnitest;

在前面的命令中,class name = package name.Class name(注意不要使用.class后缀),-classpath首先需要解释整个包的 Java 库路径(本例中库文件为android.jar;其位置如图图 7-30 所示,即D:\Android\android-sdk\ platforms\android-15\android.jar-classpath还需要说明目标类(MainActivity.class)目录。在本例中,它位于bin\classes目录中,在bin\classes\com\example\ MainActivity.class下(两者都用分号分隔)。

9781430261308_Fig07-30.jpg

图 7-30 。jnitest 应用运行界面

经过前面的步骤后,在当前目录(项目根目录)下生成了.h文件。该文件定义了 C 语言的函数接口。

您可以测试前面步骤的输出:

E:\temp\Android Dev\workspace\jnitest>dir
...
2013-01-31  22:00      3,556 com_example_jnitest_MainActivity.h

显然已经生成了一个新的.h文件。该文件内容如下:

1.      /* DO NOT EDIT THIS FILE - it is machine generated */
2.      #include <jni.h>
3.      /* Header for class com_example_jnitest_MainActivity */
4.
5.      #ifndef _Included_com_example_jnitest_MainActivity
6.      #define _Included_com_example_jnitest_MainActivity
7.      #ifdef __cplusplus
8.      extern "C" {
9.      #endif
10.     #undef com_example_jnitest_MainActivity_MODE_PRIVATE
11.     #define com_example_jnitest_MainActivity_MODE_PRIVATE 0L
12.     #undef com_example_jnitest_MainActivity_MODE_WORLD_READABLE
13.     #define com_example_jnitest_MainActivity_MODE_WORLD_READABLE 1L
14.     #undef com_example_jnitest_MainActivity_MODE_WORLD_WRITEABLE
15.     #define com_example_jnitest_MainActivity_MODE_WORLD_WRITEABLE 2L
16.     #undef com_example_jnitest_MainActivity_MODE_APPEND
17.     #define com_example_jnitest_MainActivity_MODE_APPEND 32768L
18.     #undef com_example_jnitest_MainActivity_MODE_MULTI_PROCESS
19.     #define com_example_jnitest_MainActivity_MODE_MULTI_PROCESS 4L
20.     #undef com_example_jnitest_MainActivity_BIND_AUTO_CREATE
21.     #define com_example_jnitest_MainActivity_BIND_AUTO_CREATE 1L
22.     #undef com_example_jnitest_MainActivity_BIND_DEBUG_UNBIND
23.     #define com_example_jnitest_MainActivity_BIND_DEBUG_UNBIND 2L
24.     #undef com_example_jnitest_MainActivity_BIND_NOT_FOREGROUND
25.     #define com_example_jnitest_MainActivity_BIND_NOT_FOREGROUND 4L
26.     #undef com_example_jnitest_MainActivity_BIND_ABOVE_CLIENT
27.     #define com_example_jnitest_MainActivity_BIND_ABOVE_CLIENT 8L
28.     #undef com_example_jnitest_MainActivity_BIND_ALLOW_OOM_MANAGEMENT
29.     #define com_example_jnitest_MainActivity_BIND_ALLOW_OOM_MANAGEMENT 16L
30.     #undef com_example_jnitest_MainActivity_BIND_WAIVE_PRIORITY
31.     #define com_example_jnitest_MainActivity_BIND_WAIVE_PRIORITY 32L
32.     #undef com_example_jnitest_MainActivity_BIND_IMPORTANT
33.     #define com_example_jnitest_MainActivity_BIND_IMPORTANT 64L
34.     #undef com_example_jnitest_MainActivity_BIND_ADJUST_WITH_ACTIVITY
35.     #define com_example_jnitest_MainActivity_BIND_ADJUST_WITH_ACTIVITY 128L
36.     #undef com_example_jnitest_MainActivity_CONTEXT_INCLUDE_CODE
37.     #define com_example_jnitest_MainActivity_CONTEXT_INCLUDE_CODE 1L
38.     #undef com_example_jnitest_MainActivity_CONTEXT_IGNORE_SECURITY
39.     #define com_example_jnitest_MainActivity_CONTEXT_IGNORE_SECURITY 2L
40.     #undef com_example_jnitest_MainActivity_CONTEXT_RESTRICTED
41.     #define com_example_jnitest_MainActivity_CONTEXT_RESTRICTED 4L
42.     #undef com_example_jnitest_MainActivity_RESULT_CANCELED
43.     #define com_example_jnitest_MainActivity_RESULT_CANCELED 0L
44.     #undef com_example_jnitest_MainActivity_RESULT_OK
45.     #define com_example_jnitest_MainActivity_RESULT_OK -1L
46.     #undef com_example_jnitest_MainActivity_RESULT_FIRST_USER
47.     #define com_example_jnitest_MainActivity_RESULT_FIRST_USER 1L
48.     #undef com_example_jnitest_MainActivity_DEFAULT_KEYS_DISABLE
49.     #define com_example_jnitest_MainActivity_DEFAULT_KEYS_DISABLE 0L
50.     #undef com_example_jnitest_MainActivity_DEFAULT_KEYS_DIALER
51.     #define com_example_jnitest_MainActivity_DEFAULT_KEYS_DIALER 1L
52.     #undef com_example_jnitest_MainActivity_DEFAULT_KEYS_SHORTCUT
53.     #define com_example_jnitest_MainActivity_DEFAULT_KEYS_SHORTCUT 2L
54.     #undef com_example_jnitest_MainActivity_DEFAULT_KEYS_SEARCH_LOCAL
55.     #define com_example_jnitest_MainActivity_DEFAULT_KEYS_SEARCH_LOCAL 3L
56.     #undef com_example_jnitest_MainActivity_DEFAULT_KEYS_SEARCH_GLOBAL
57.     #define com_example_jnitest_MainActivity_DEFAULT_KEYS_SEARCH_GLOBAL 4L
58.     /*
59.      * Class:     com_example_jnitest_MainActivity
60.      * Method:    stringFromJNI
61.      * Signature: ()Ljava/lang/String;
62.      */
63.     JNIEXPORT jstring JNICALL Java_com_example_jnitest_MainActivity_stringFromJNI
64.       (JNIEnv *, jobject);
65.
66.     #ifdef __cplusplus
67.     }
68.     #endif
69.     #endif

在前面的代码中,请特别注意第 63–64 行,这是一个本地函数stringFromJNI的 C 函数原型。

编译相应的。c 文件

这是一个局部函数的真正实现(stringFromJNI)。按照前面的步骤,通过修改.h文件获得源代码文件。

创建新的。项目中jni子目录下的 c 文件。文件名可以随机创建。在本例中,它被命名为jnitestccode.c。内容如下:

1.      #include <string.h>
2.      #include <jni.h>
3.      jstring Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,  jobject thiz )
4.      {
5.          return (*env)->NewStringUTF(env, "Hello from JNI !"); // Newly added code
6.      }

前面的代码定义了函数实现,非常简单。第 3 行是函数stringFromJNI的原型定义中使用的 Java 代码。它基本上是从前面的步骤中获得的.h文件的相应内容的副本(com_example_jnitest_MainActivity.h的第 63–64 行),并稍微做了一些修改。该函数的原型格式是固定的— JNIEnv* envjobject thiz是 JNI 的固有参数。因为stringFromJNI函数的参数为空,所以生成的 C 函数只有两个参数。第 5 行代码的作用是返回字符串"Hello fromJNI!"作为返回值。

第 2 行的代码是包含 JNI 函数的头文件,任何使用 JNI 的函数都需要这个函数。因为它与string函数相关,所以在这种情况下,第 1 行包含相应的头文件。完成前面的步骤后,.h文件不再有用,可以删除。

在 jni 目录下创建 NDK Makefile 文件

这些文件主要包括Android.mkApplication.mk文件,其中需要Android.mk。但是,如果使用应用的默认配置,则不需要Application.mk。四个具体步骤如下:

  1. Create a new Android.mk text file in the jni directory in the project. This file tells the compiler about some requirements, such as which C files to compile, the filename for compiled code, and so on. Enter the following:

       LOCAL_PATH := $(call my-dir)
       include $(CLEAR_VARS)
       LOCAL_MODULE  := jnitestmysharelib
       LOCAL_SRC_FILES  := jnitestccode.c
       include $(BUILD_SHARED_LIBRARY)
    

    接下来解释文件内容。

    第 3 行表示生成的。所以 filename(标识您的Android.mk文件中描述的每个模块)。它必须与 Java 代码中的System.loadLibrary函数的参数值一致。该名称必须是唯一的,并且不能包含任何空格。

    image 注意构建系统会自动生成适当的前缀和后缀。换句话说,如果一个是名为jnitestmysharelib的共享库模块,那么就会生成一个libjnitestmysharelib.so文件。如果您将库命名为libhello-jni,编译器将不会添加lib前缀,也会生成libhello-jni.so

    第 4 行的LOCAL_SRC_FILES变量必须包含要编译并打包成模块的 C 或 C++ 源代码文件。前面的步骤创建了一个 C 文件名。

    image 注意用户不必在这里列出头文件和包含文件,因为编译器会自动为你识别依赖文件。只列出直接传递给编译器的源代码文件。此外,C++ 源文件的默认扩展名是. CPP。只要定义了LOCAL_DEFAULT_CPP_EXTENSION变量,就可以指定不同的扩展名。不要忘记开头的小圆点(。cxx,而不是 cxx)。

    第 3 行到第 4 行的代码非常重要,必须根据每个 NDK 应用的实际配置进行修改。其他行的内容可以从前面的例子中复制。

  2. Create an Application.mk text file in the jni directory in the project. This file tells the compiler the specific settings for this application. Enter the following:

    APP_ABI := x86
    

    这个文件非常简单。您使用由 x86 架构的应用指令生成的目标代码,因此您可以在英特尔凌动处理器上运行应用。对于 APP_ABI 参数,请使用 x86、armeabi 或 armeabi-v7a。

  3. Next, compile the .c file to the .SO shared library file.

    转到项目根目录(AndroidManifest.xml所在的位置)并运行ndk-build命令:

    E:\temp\Android Dev\workspace\jnitest>ndk-build
    D:/Android/android-ndk-r8d/build/core/add-application.mk:128: Android NDK: WARNI
    NG: APP_PLATFORM android-14 is larger than android:minSdkVersion 8 in ./AndroidM
    anifest.xml
    "Compile x86  : jnitestmysharelib <= jnitestccode.c
    SharedLibrary  : libjnitestmysharelib.so
    Install        : libjnitestmysharelib.so => libs/x86/libjnitestmysharelib.so
    

    前面的命令将在项目中添加两个子目录(libsobj)。包括的执行版本。所以将文件(名为libjnitestmysharelib.so的命令执行信息提示文件)放在obj目录下,最终会将最终版本放在libs目录下。

    如果前面的步骤没有定义指定 ABI 的Application.mk文件,使用ndk-build命令将生成 ARM 架构的目标代码(armeabi)。如果您必须生成 x86 体系结构指令,您也可以使用ndk-build APP_ABI = x86命令来补救这种情况。该命令生成的目标代码的架构仍然是 x86。

  4. Deployment: run the project.

    完成这一步后,就差不多可以部署和运行项目了。在目标设备的界面上运行的应用如图 7-30 所示。

在 IDE 中生成库文件

回想一下上一节中描述的将 C 文件编译成动态库的过程。所以可以在 Android 目标设备上运行的文件。您可以在命令行中运行ndk-build命令来完成这个过程。事实上,您也可以在 Eclipse IDE 中完成这一步。

在 IDE 中生成库文件时,前四个步骤中的代码与上一节中的代码完全相同。你只需要编译。c 文件转换成。所以改为共享库文件。这详细解释如下:

  1. Compile the .C file into the .SO shared library file. Right-click on the project name, and select Build Path, Configure Build Path. In the pop-up dialog box, select the Builders branch. Then click the New button in the dialog box. Double-click Program in the prompt dialog box. This process is shown in Figure 7-31.

    9781430261308_Fig07-31.jpg

    图 7-31 。在 Eclipse 中输入编译 C 代码界面的参数设置

  2. In the Edit Configuration dialog box, enter the following for the Main tab settings:

    • 地点:通往小天鹅的路径bash.exe
    • 工作目录:Cygwin 的 bin 目录。
    • 自变量:
    --login -c "cd '/cygdrive/E/temp/Android Dev/workspace/jnitest' && $ANDROID_NDK_ROOT/ndk-build"
    

    其中E/temp/Android Dev/workspace/jnitest是项目的字母和路径。整个设置如图 7-32 中的所示。

    9781430261308_Fig07-32.jpg

    图 7-32 。编辑配置窗口中的主选项卡设置

  3. Then configure the Refresh tab, ensuring that these items are selected—The Entire Workspace and Recursively Include Sub-Folders—as shown in Figure 7-33.

    9781430261308_Fig07-33.jpg

    图 7-33 。编辑配置窗口刷新选项卡设置

  4. Reconfigure the Build Options tab. Check the During Auto Builds and Specify Working Set of Relevant Resources items, as shown in Figure 7-34.

    9781430261308_Fig07-34.jpg

    图 7-34 。编辑配置窗口构建选项选项卡设置

  5. Click on the Specify Resources button. In the Edit Working Set dialog box, select the jni directory, as shown in Figure 7-35.

    9781430261308_Fig07-35.jpg

    图 7-35 。选择相关文件所在的源代码目录

  6. 正确配置前面的步骤后,将保存配置。它会自动编译jni目录下的 C 相关代码并输出相应的。所以库文件放在项目的libs目录下。libs目录是自动创建的。在控制台窗口中,您可以看到构建的输出信息,如下所示:

    /cygdrive/d/Android/android-ndk-r8d/build/core/add-application.mk:128: Android NDK: WARNING: APP_PLATFORM android-14 is larger than android:minSdkVersion 8 in ./AndroidManifest.xml
    Cygwin         : Generating dependency file converter script
    Compile x86    : jnitestmysharelib <= jnitestccode.c
    SharedLibrary  : libjnitestmysharelib.so
    Install        : libjnitestmysharelib.so => libs/x86/libjnitestmysharelib.so
    

NDK 应用开发工作流分析

前面描述的生成 NDK 项目的过程很自然地实现了 C 库与 Java 的集成。在最后一步,你编译。c 文件放入。所以共享库文件。库的中间版本放在obj目录中,最终版本放在libs目录中。项目文件结构创建完成,如图图 7-36 所示。

9781430261308_Fig07-36.jpg

图 7-36 。NDK 图书馆文件生成后的 jnitest 项目结构

共享库。所以文件在主机中的项目目录中,并将被打包在生成的.apk文件中。.apk文件本质上是一个压缩文件。可以使用 WinRAR 之类的压缩软件查看其内容。对于这个例子,您可以在项目目录的bin子目录中找到.apk文件。用 WinRAR 打开,显示文件结构。

.apklib子目录的内容是项目的lib子目录的克隆。在图 7-36 中生成。所以文件显示在lib\x86子目录中。

.apk被部署到目标机器时,它将被解包,在这种情况下。因此文件将被放在/data/dat/XXX/lib目录中,其中XXX是应用包的名称。例如,对于前面的例子,目录是/data/data/com.example.jnitest/lib。您可以在 Eclipse DDMS 下查看目标机器的文件结构;该示例的文件结构如图图 7-37 所示。

9781430261308_Fig07-37.jpg

图 7-37 。NDK 图书馆文件生成后的 jnitest 项目结构

在图 7-37 中,你可以找到。所以库文件放在/data/data/XXX/lib目录下,这样当应用运行时,System.loadLibrary函数可以加载到内存中运行。这里你可以看到。所以文件中有 DDMS 的图形显示。有兴趣的读者可以在命令行上尝试一下,使用adb shell 命令查看目标文件目录中的相应内容。

此外,如果您在模拟器中运行jnitest应用(在这种情况下,目标机器是一个虚拟机),您将在 Eclipse Logcat窗口中看到以下输出:

1.  07-10 05:43:08.579: E/Trace(6263): error opening trace file: No such file or directory (2)
2.  07-10 05:43:08.729: D/dalvikvm(6263): Trying to load lib /data/data/com.example.jnitest/lib/libjnitestmysharelib.so 0x411e8b30
3.  07-10 05:43:08.838: D/dalvikvm(6263): Added shared lib /data/data/com.example.jnitest/lib/libjnitestmysharelib.so 0x411e8b30
4.  07-10 05:43:08.838: D/dalvikvm(6263): No JNI_OnLoad found in /data/data/com.example.jnitest/lib/libjnitestmysharelib.so 0x411e8b30, skipping init
5.  07-10 05:43:11.773: I/Choreographer(6263): Skipped 143 frames!  The application may be doing too much work on its main thread.
6.  07-10 05:43:12.097: D/gralloc_goldfish(6263): Emulator without GPU emulation detected.

第 2–3 行是关于。所以共享库加载到应用中。

NDK 编译器优化

从前面的例子中,您可以看到 NDK 工具的核心作用是将源代码编译到。所以库文件可以在 Android 机器上运行。那个。所以库文件被放在项目目录的lib子目录中,这样当您使用 Eclipse 部署应用时,您可以将库文件部署到目标设备上的适当位置,并且应用可以使用库函数运行。

image NDK 应用的本质是建立一个符合 JNI 标准的代码框架。这将使 Java 应用能够使用超出虚拟机范围的本地函数。

用于将源代码编译成. SO 库文件的关键 NDK 命令是ndk-build。它实际上不是一个单独的命令,而是一个可执行的脚本。它调用 GNU 交叉开发工具中的make命令来编译一个项目,例如调用gcc编译器编译源代码来完成整个过程,如图图 7-38 所示。当然也可以直接用。所以 Android 应用中已经有第三方开发的共享库,从而避免了自己写库(函数代码)的需要。

9781430261308_Fig07-38.jpg

图 7-38 。NDK 工具的工作机理

如图 7-38 所示,核心 GNU 编译器gcc是 NDK 中完成 C/C++ 源代码编译的核心工具。gcc是 Linux 的标准编译器,可以在本地机器上编译链接 C、C++、Object-C、FORTRAN 等源代码。事实上,gcc编译器不仅可以进行本地编译,还可以进行交叉编译。Android NDK 和其他嵌入式开发工具已经使用了这个特性。在编译器用法上,gcc交叉编译兼容原生编译;也就是说,本地编译代码的命令参数和开关本质上可以被移植,而无需修改交叉编译代码。因此,下面描述的gcc编译方法对于本地编译和交叉编译都是通用的。

Chapter 9: Performance Optimizations for Android Applications on x86 中,我们将更详细地讨论编译器优化(即一些优化如何由编译器自动完成)。对于基于 Intel x86 架构处理器的系统,除了 GNU gcc编译器,Intel C/C++ 编译器也是一个不错的工具。相对来说,由于英特尔 C/C ++ 编译器充分利用了英特尔处理器的特性,代码优化结果会更好。对于 Android NDK,无论是 Intel C/C++ 编译器还是gcc都可以完成 C/C++ 代码编译。目前,英特尔 C/C ++ 编译器提供了适当的使用机制。普通用户需要一个专业的许可证,而gcc是开源的、免费的软件,更容易获得。以下部分使用gcc作为实验工具,解释如何为 Android 应用执行 C/C++ 模块编译器优化。

gcc优化由编译器开关的优化选项控制。这些选项有些是独立于机器的,有些是与机器相关联的。这里我们将讨论一些重要的选项。对于与机器相关的选项,我们将只描述与英特尔处理器相关的选项。

独立于机器的编译器开关选项

gcc编译器开关的独立于机器的选项是-Ox选项,它们对应不同的优化级别。详情如下。

-0 或-01

一级优化是默认的优化级别,使用-O选项。编译器试图减少代码大小和执行时间。对于大型函数,需要花费更多的编译时间,使用大量的内存资源进行优化编译。

当不使用-O选项时,编译器的目标是减少编译的开销,以便快速调试结果。在这种编译模式下,语句是独立的。通过在两个语句之间插入断点来中断程序运行,用户可以重新分配变量或修改程序计数器来跳转到其他当前正在执行的语句,这样就可以精确地控制运行过程。当用户想调试时,也可以得到结果。此外,如果不使用-O选项,只有寄存器声明的变量可以进行寄存器分配。

当您指定-O选项时,-fthread-jumps-fdefer-pop选项被打开。在带有延迟槽的机器上,打开-fdelayed-branch选项。即使对于支持无帧指针调试的机器,-fomit-frame-pointer选项也是打开的。一些机器可能还会激活其他选项。

-02

进一步优化。GCC 执行几乎所有支持的优化,不涉及空间速度的权衡。与-O相比,该选项增加了编译时间和生成代码的性能。

-03

进一步优化。选项-O3打开由-O2指定的所有优化,同时打开-finline-functions-funswitch-loops-fpredictive-commoning-fgcse-after-reload-ftree-vectorize-fvect-cost-model-ftree-partial-pre-fipa-cp-clone选项。

-00

减少编译时间并使调试产生预期的结果。这是默认设置。

自动内联函数通常用作函数优化措施。c99(1999 年开发的 C 语言 ISO 标准)和 C++ 都支持inline关键字。inline函数反映了用内联空间换取时间的思想。编译器不把内联描述的函数编译成函数,而是直接为函数体扩展代码,从而省去函数调用,返回call ret指令和参数的push指令执行。例如,在下面的函数中:

inline long factorial (int i)
{
   return factorial_table[i];
}

所有出现的factorial ()调用都被替换为factorial_table []数组引用。

当处于优化状态时,一些编译器会将该函数视为内联函数,即使该函数不使用内联指令。只有在适当的情况下(比如函数代码体相对较短,定义在头文件中),它才会这样做,以换取执行时间。

循环展开是一种经典的速度优化方法,被许多编译器视为自动优化策略。例如,以下循环代码需要循环 100 个周期:

for (i = 0; i < 100; i++)
{
   do_stuff(i);
}

在所有 100 个循环中,在每个循环结束时,必须检查循环条件以进行比较判断。通过使用循环展开策略,代码可以转换如下:

for (i = 0; i < 100; )
{
   do_stuff(i); i++;
   do_stuff(i); i++;
   do_stuff(i); i++;
   do_stuff(i); i++;
   do_stuff(i); i++;
   do_stuff(i); i++;
   do_stuff(i); i++;
   do_stuff(i); i++;
   do_stuff(i); i++;
   do_stuff(i); i++;
}

如您所见,新代码将比较指令从 100 次减少到 10 次,用于条件比较的时间可以减少 90%。

前面描述的两种方法都将提高目标代码的优化。这是时间优化思想的典型空间。

英特尔处理器相关的编译器开关选项

gccm选项是为英特尔 i386 和 x86 - 64 处理器家族定义的。主要命令选项在表 7-3 中解释。

表 7-3 。与英特尔处理器相关的 gcc 开关选项

|

切换选项

|

注意

|

描述

|
| --- | --- | --- |
| -march=cpu-type -mtune=cpu-type | | 为指定类型的 CPU 生成的代码。CPU 类型可以是 i386、i486、i586、奔腾、i686、奔腾 4 等等 |
| -msse | | 编译器自动向量化。使用或不使用 MMX、SSE、SSE2 指令。例如,-msse表示编程入指令,–mno-sse表示未编程入 SSE 指令 |
| -msse2 | |
| -msse3 | |
| -mssse3 | gxx-4.3 new addition |
| -msse4.1 | gcc-4.3 new addition |
| -msse4.2 | gcc-4.3 new addition |
| -msse4 | Include 4.1, 4.2 ,gcc-4.3 new addition |
| -mmmx | |
| -mno-sse | |
| -mno-sse2 | |
| -mno-mmx | |
| -m32 -m64 | | 生成 32/64 机器码 |

在表 7-3 中,-march是机器的 CPU 类型,-mtune是编译器想要优化的 CPU 类型(默认与-march相同)。-march选项是“紧约束”,而-mtune是“松约束”-mtune选项可以提供向后兼容性。

-march = i686, -mtune = pentium4的编译器优化选项针对奔腾 4 处理器进行了优化,但也可以在任何 i686 上运行。

对于-mtune = pentium-mmx编译的程序,可以运行奔腾 4 处理器。

-march=cpu-type

该选项将生成指定机器类型的cpu-type指令。-mtune = cpu-type选项仅可用于优化为cpu-type生成的代码。相比之下,-march = cpu-type为指定类型的处理器生成不能在非gcc上运行的代码,这意味着-march = cpu-type意味着-mtune = cpu-type选项。

与英特尔处理器相关的cpu-type选项值在表 7-4 中列出。

表 7-4 。gcc-March 参数的主要可选值为 cpu-type

|

cpu 类型值

|

描述

|
| --- | --- |
| native | 这通过确定编译机器的处理器类型来选择 CPU 在编译时生成代码。使用-march=native启用本地机器支持的所有指令子集(因此结果可能不会在不同的机器上运行)。使用-mtune=native在所选指令集的约束下产生针对本地机器优化的代码。 |
| i386 | 原装英特尔 i386 CPU。 |
| i486 | 英特尔 i486 CPU。(该芯片未实施任何调度。) |
| i586 | 不支持 MMX 的英特尔奔腾 CPU。 |
| pentium |
| pentium-mmx | 英特尔奔腾 MMX CPU,基于支持 MMX 指令集的奔腾内核。 |
| pentiumpro | 英特尔奔腾 Pro CPU。 |
| i686 | 与-march一起使用时,使用的是奔腾 Pro 指令集,所以代码运行在所有 i686 系列芯片上。当与-mtune一起使用时,它与“通用”具有相同的含义。 |
| pentium2 | 英特尔奔腾 II CPU,基于支持 MMX 指令集的奔腾 Pro 内核。 |
| pentium3 | 英特尔奔腾 III CPU,基于支持 MMX 和 SSE 指令集的奔腾 Pro 内核。 |
| pentium3m |
| pentium-m | 英特尔奔腾 M;支持 MMX、SSE 和 SSE2 指令集的低功耗版本英特尔奔腾 III CPU。由迅驰笔记本使用。 |
| pentium4 | 支持 MMX、SSE 和 SSE2 指令集的英特尔奔腾 4 CPU。 |
| pentium4m |
| prescott | 英特尔奔腾 4 CPU 的改进版本,支持 MMX、SSE、SSE2 和 SSE3 指令集。 |
| nocona | 英特尔奔腾 4 CPU 的改进版本,支持 64 位扩展、MMX、SSE、SSE2 和 SSE3 指令集。 |
| core2 | 具有 64 位扩展的英特尔酷睿 2 CPU,支持 MMX、SSE、SSE2、SSE3 和 SSSE3 指令集。 |
| corei7 | 具有 64 位扩展的英特尔酷睿 i7 CPU,支持 MMX、SSE、SSE2、SSE3、SSSE3、SSE4.1 和 SSE4.2 指令集。 |
| corei7-avx | 具有 64 位扩展的英特尔酷睿 i7 CPU,支持 MMX、SSE、SSE2、SSE3、SSSE3、SSE4.1、SSE4.2、AVX、AES 和 PCLMUL 指令集。 |
| core-avx-i | 支持 64 位扩展、MMX、SSE、SSE2、SSE3、SSSE3、SSE4.1、SSE4.2、AVX、AES、PCLMUL、FSGSBASE、RDRND 和 F16C 指令集的英特尔酷睿 CPU。 |
| atom | 支持 64 位扩展、MMX、SSE、SSE2、SSE3 和 SSSE3 指令集的英特尔凌动 CPU。 |

Traditional gcc是一个本地编译器。这些命令选项可以添加到gcc来控制gcc编译器选项。例如,假设您有一个int_sin.c文件。

$ gcc int_sin.c

前面的命令使用了-O1优化级别(默认级别),并将int_sin.c编译成一个可执行文件,默认名为a.out

$ gcc int_sin.c -o sinnorm

前面的命令使用-O1优化级别(默认级别)将int_sin.c编译成可执行文件;可执行文件的文件名被指定为sinnorm

$ gcc int_cos.c -fPIC -shared -o coslib.so

前面的命令使用-O1优化级别(默认级别)将int_cos.c编译成一个名为coslib.so的共享库文件。与之前编译成可执行程序的源代码文件不同,这个命令要求源代码文件int_cos.c不包含 main 函数。

$ gcc -O0 int_sin.c

前面的命令用默认文件名将int_sin.c编译成可执行文件。编译器不执行任何优化。

$ gcc -O3 int_sin.c

前面的命令使用最高的优化级别-O3int_sin.c文件编译成具有默认文件名的可执行文件。

$ gcc -msse int_sin.c

前面的命令使用 SSE 指令将int_sin.c编译成一个可执行文件。

$ gcc -mno-sse int_sin.c

前面的命令将int_sin.c编译成一个可执行文件,没有任何 SSE 指令。

$ gcc -mtune=atom int_sin.c

前面的命令将int_sin.c编译成可以使用英特尔凌动处理器指令的可执行文件。

从前面由gcc本地编译的例子中,您有了一些使用编译器switch选项进行gcc编译器优化的经验。对于gcc原生编译器,可以在switch选项中直接使用gcc命令来实现编译器优化。然而,从前面的例子中,你知道 NDK 不直接使用gcc命令。那么如何设置gcc编译器switch选项来实现 NDK 优化呢?

回想一下使用 NDK 的例子,您使用了ndk-build命令来编译 C/C++ 源代码;该命令首先需要读取 makefile 文件Android.mk。这个文件实际上包含了gcc命令选项。Android.mk使用LOCAL_CFLAGS控制并完成gcc命令选项。ndk-build命令将把LOCAL_CFLAGS运行时间值传递给gcc,作为其命令选项来运行gcc命令。LOCAL_CFLAGS将数值传递给gcc并将其作为命令选项来运行gcc命令。

例如,您将Android.mk修改如下:

1.  LOCAL_PATH := $(call my-dir)
2.  include $(CLEAR_VARS)
3.  LOCAL_MODULE       := jnitestmysharelib
4.  LOCAL_SRC_FILES    := jnitestccode.c
5.  LOCAL_CFLAGS       := -O3
6.  include $(BUILD_SHARED_LIBRARY)

第 5 行是新增加的。它设置了LOCAL_CFLAGS变量脚本。

当你执行ndk-build命令时,相当于增加了一个gcc -O3命令选项。它指示gcc在最高优化级别 O3 编译 C 源代码。同样,如果您将第 5 行编辑为:

LOCAL_CFLAGS       := -msse3

你指示gcc使用 SSE3 指令将 C 源代码编译成目标代码。

感兴趣的读者可以将LOCAL_CFLAGS设置为不同的值,比较目标库文件的大小和内容差异。注意,前面的例子jnitest C 代码非常简单,不涉及复杂的任务。因此,当从不同的LOCAL_CFLAGS值编译时,库文件的大小或内容不会有很大的不同。

那么,库文件的大小或内容会有显著的不同吗?其实答案是肯定的。在这方面,我们将在下面的章节中给出实际的例子。

概观

学习完本章后,您应该对 Android 原生开发套件有了全面的了解,并了解如何使用它来创建面向英特尔平台的 Android 应用。我们还介绍了英特尔 C++ 编译器及其选项。重要的是要记住,英特尔 C++ 编译器只是可用于英特尔 Android 应用的可能编译器之一。我们详细讨论了用于与您的 NDK 应用交互的 Java 本地接口,以及它是如何操作的。我们还介绍了各种代码示例,以便更好地解释英特尔 C++ 编译器的各种基本优化。

八、调试 Android

并非所有的问题都有技术上的答案,但当它们有了,那才是更持久的解决方案。

—安迪·格罗夫,半导体制造先驱

在以 Android 操作系统为目标的应用或 Android 系统软件堆栈中,识别运行时问题和可能有问题的代码的过程非常相似。无论底层平台架构是基于英特尔架构和最新一代凌动处理器,还是基于 ARM 架构,挑战和方法都是相同的。本章概述了英特尔架构上针对 Android 的软件的可用调试方法。在本章的前一部分,我们还将涉及针对 Android 和英特尔架构的应用和系统软件调试环境的设置和配置。在这样做的过程中,我们还将指出在为 ARM 架构开发时体验的不同之处。

先决条件

本章介绍了在运行 Android 操作系统的基于英特尔凌动处理器的设备上启用远程应用调试所必需的英特尔 USB 驱动程序。此外,我们还将了解面向 Android 仿真器的英特尔凌动 x86 系统映像。如果没有物理调试目标可用,那么在 Android SDK 的设备仿真层中仿真一个基于英特尔凌动处理器的虚拟设备是下一个最佳选择。这两个先决条件与 Android SDK 一起为基于英特尔凌动处理器的平板电脑或智能手机的交叉开发奠定了基础。

用于 Android 设备的英特尔 USB 驱动程序

本节介绍英特尔 Android USB 驱动程序包,它使您能够将主机开发机器连接到内置英特尔凌动处理器的 Android 设备。在第一个例子中,我们假设一个 Windows 开发主机。类似的原则也适用于 Linux 或 OS X 主机系统。

  1. http://www.intel.com/software/android下载安装包。

  2. 运行安装程序并接受 Windows 用户帐户控制(UAC)提示(如果适用)。

  3. You will see the screen in Figure 8-1. Click Next to continue. (If the installer detects an older version of the driver, accept to uninstall it.)

    9781430261308_Fig08-01.jpg

    图 8-1 。USB 设备驱动安装开始屏幕

  4. 阅读并同意英特尔 Android USB 驱动程序最终用户许可协议(EULA)。

  5. 系统将提示您选择组件。单击“下一步”按钮继续。

  6. 选择安装路径,然后单击安装。

  7. The installer will proceed to install the Android USB drivers. This may take a few minutes (see Figure 8-2).

    9781430261308_Fig08-02.jpg

    图 8-2 。USB 设备驱动程序安装进度屏幕

  8. 驱动程序安装完成后,单击弹出提示上的确定,然后单击完成关闭安装程序。

为 Android 仿真器安装英特尔凌动 x86 系统映像

对于使用 Android 虚拟设备管理器在开发主机上进行调试的替代方案,第一个先决条件是适当的系统映像的可用性。

使用 Android 模拟器需要安装英特尔凌动 x86 系统映像和 Android SDK。有关 Android SDK 的安装说明,请参考 Android 开发者网站(http://developer.android.com/sdk/installing.html),或者参考 第六章:为英特尔应用开发安装 Android SDK。Android SDK 管理器允许您下载并安装英特尔凌动 Android x86 仿真器映像插件,然后安装必要的英特尔凌动 x86 系统映像。

请遵循以下步骤:

  1. 启动 Android SDK 管理器程序。

  2. 在软件包image Android 4.x.x (API 1x)下,选中复选框以选择英特尔公司的英特尔凌动 x86 系统映像。

  3. Once selected, click the Install Package button, as shown in Figure 8-3.

    9781430261308_Fig08-03.jpg

    图 8-3 。x86 系统映像的 Android SDK 管理器选择

    image 注意根据您或 Android SDK 管理程序选择的其他包,您可能需要安装多个包。

  4. Review the Intel Corporation license agreement. If you accept the terms, select the Accept option and click the Install button, as shown in Figure 8-4.

    9781430261308_Fig08-04.jpg

    图 8-4 。Android SDK 管理器—接受许可条款

  5. 此时,Android SDK 管理器将下载并安装附加组件到您的 Android SDK 附加组件文件夹(<sdk>/add-ons/)。根据您的连接速度,下载和安装将需要几分钟时间。

  6. Select Manage AVDs from the Tools menu (see Figure 8-5).

    9781430261308_Fig08-05.jpg

    图 8-5 。Android SDK 管理器—管理 Android 虚拟设备

  7. The Android Virtual Device Manager window should appear. Click New (see Figure 8-6).

    9781430261308_Fig08-06.jpg

    图 8-6 。添加新的 Android 虚拟设备

  8. 在名称字段中输入虚拟设备的名称。名称中不允许有空格。

  9. Select Intel Atom x86 System Image (Intel Corporation) – API Level 10 from the Target field drop-down list (see Figure 8-7).

    9781430261308_Fig08-07.jpg

    图 8-7 。作为虚拟设备目标的英特尔凌动 x86 系统映像

  10. 选择配置设置后,单击创建 AVD 按钮。

  11. The new virtual device should appear on the Android Virtual Device Manager. Select the new device and click the Start button, as shown in Figure 8-8.

![9781430261308_Fig08-08.jpg](https://gitee.com/OpenDocCN/vkdoc-android-pt2-zh/raw/master/docs/andr-x86/img/9781430261308_Fig08-08.jpg)

图 8-8 。启动 Android 虚拟设备
  1. The Launch Options window should appear. Select the screen size and DPI for your system. Otherwise, the emulator might exceed the dimensions of your viewing screen. Click the Launch button (see Figure 8-9).
![9781430261308_Fig08-09.jpg](https://gitee.com/OpenDocCN/vkdoc-android-pt2-zh/raw/master/docs/andr-x86/img/9781430261308_Fig08-09.jpg)

图 8-9 。虚拟设备启动选项
  1. After a few moments, the emulator will launch and show you the screen in Figure 8-10.
![9781430261308_Fig08-10.jpg](https://gitee.com/OpenDocCN/vkdoc-android-pt2-zh/raw/master/docs/andr-x86/img/9781430261308_Fig08-10.jpg)

图 8-10 。基于英特尔架构的 Android 设备的 AVD 仿真

使用 Android 调试桥进行应用调试

Android Debug Bridge(ADB)是一个命令行工具,处理主机上的调试器(通常是 DDMS GDB(dal vik Debug Monitor Server)或 ADT)与目标上运行的 Android 映像之间的调试通信。目标映像可以在设备仿真或物理开发设备上运行,您可以通过 USB OTG(移动)或 USB 转以太网加密狗与之通信。简而言之,ADB 是使 Android 上的应用调试成为可能的粘合剂。

您要连接或仿真的设备可能涵盖多种外形规格。通常是智能手机或平板电脑。它也可以是医疗平板电脑或工业环境中的嵌入式设备、家庭能源管理、仓储或任何智能系统应用。

设置 Android 调试桥以允许基于英特尔凌动处理器的平台的远程调试与其他架构上的调试没有太大区别。

建立亚洲开发银行

首先你需要 Android SDK,包括安装在开发主机上的 ADB。这方面的说明可在http://developer.android.com/sdk/installing.html找到。

如果您的目标映像在物理设备上运行,您需要包括 USB OTG 或 USB 转以太网支持。对于 USB 到以太网的支持,需要更改和重建内核配置。如果需要,您的 OEM 将为您提供必要的信息。

远程应用调试的标准方法是使用大多数 Android 设备现有的 USB-OTG 接口。Android 开发者网站http://developer.android.com/guide/developing/device.html详细描述了设置。

关键步骤是:

  1. 在您的 Android 清单中将您的应用声明为“可调试的”。
  2. 在设备上打开 USB 调试。
  3. 在设备上,进入设置image应用image开发并启用 USB 调试(在 Android 4.x.x 设备上,该设置位于设置image开发者选项中)。
  4. 设置您的系统以检测您的设备。

如果你在 Windows 上开发,你需要为 ADB 安装一个 USB 驱动——访问http://developer.android.com/tools/extras/oem-usb.html获取驱动下载和先决条件。

如果您在 Ubuntu Linux 上开发,您需要添加一个udev rules文件,其中包含您想要用于开发的每种设备的 USB 配置。在rules文件中,每个设备制造商由一个唯一的供应商 ID 标识,由ATTR{idVendor}属性指定。有关供应商 id 的列表,请参见http://developer.android.com/tools/device.html#VendorIds

要在 Ubuntu Linux 上设置设备检测,以root的身份登录并创建这个文件:

/etc/udev/rules.d/51-android.rules

使用此格式将每个供应商添加到文件中:

SUBSYSTEM=="usb", ATTR{idVendor}=="????", MODE="0666", GROUP="plugdev"

MODE分配指定读/写权限,而GROUP定义哪个 UNIX 组拥有设备节点。

执行:

chmod a+r /etc/udev/rules.d/51-android.rules

当通过 USB 插入时,您可以通过从 SDK 平台-工具/目录执行 ADB 设备来验证您的设备是否已连接。如果已连接,您会看到设备名称以device的形式列出。

在 SDK 上引导 Android 操作系统后,将 USB OTG 电缆连接到 SDK 上的(USB mini b)端口,并将电缆的另一端(USB A)连接到您的开发主机。

如果一切正常,您应该能够运行以下命令来查看连接的设备:

$ adb devices
* daemon not running. starting it now *
* daemon started successfully *
List of devices attached
0123456789ABCDEF  device

image 注意要查看在 Linux dev.主机上哪个设备名称被分配给这个连接,您可以查看dmesg来找到usb-storage: device found at <num>的地址,然后运行ls -l /dev/bus/usb/*清单来找到那个号码。

Windows 上的 ADB

http://www.eclipse.org/downloads/下载并安装 Eclipse Classic。

http://developer.android.com/sdk/index.html ( android-sdk_r18-windows.zipinstaller_r18-windows.exe)下载 Windows 的 Android SDK 包。

安装 Android SDK 后,adb.exe将位于<install-dir>\android-sdk\platform-tools

亚行主机-客户端通信

到目前为止,我们专注于在开发主机上安装 ADB。实际上,它是一个客户机-服务器程序,包括三个组件:

  • 在您的开发机器上运行的客户端。您可以通过发出 ADB 命令从 shell 调用客户机。其他 Android 工具,如 ADT 插件和 DDMS,也可以创建 ADB 客户端。
  • 在开发机器上作为后台进程运行的服务器。服务器管理客户端和运行在仿真器或设备上的 ADB 守护程序之间的通信。
  • 在每个模拟器或设备实例上作为后台进程运行的守护程序。

当您启动 ADB 客户端时,客户端首先检查是否有 ADB 服务器进程正在运行。如果没有,它将启动服务器进程。当服务器启动时,它绑定到本地 TCP 端口 5037 并监听从 ADB 客户端发送的命令,所有 ADB 客户端都使用端口 5037 与 ADB 服务器通信。

然后,服务器建立到所有正在运行的仿真器/设备实例的连接。它通过扫描 5555 到 5585 范围内的奇数端口来定位仿真器/设备实例,这是仿真器/设备使用的范围。当服务器找到一个 ADB 守护进程时,它会建立一个到该端口的连接。请注意,每个仿真器/设备实例都需要一对顺序端口,一个偶数端口用于控制台连接,一个奇数端口用于 ADB 连接。例如:

 Emulator 1, console: 5554
 Emulator 1, adb: 5555
 Emulator 2, console: 5556
 Emulator 2, adb: 5557

如图所示,在端口 5555 上连接到 ADB 的模拟器实例与其控制台在端口 5554 上侦听的实例是相同的。

一旦服务器建立了到所有模拟器实例的连接,您就可以使用 ADB 命令来控制和访问这些实例。因为服务器管理到仿真器/设备实例的连接并处理来自多个 ADB 客户端的命令,所以您可以从任何客户端(或从脚本)控制任何仿真器/设备实例。

开始亚行

类型adb shell。您将看到一个 # 符号,表示连接成功。

$ adb shell
#

主要 ADB 设备命令

在表 8-1 中列出的命令有助于将被调试应用从命令行转移到目标设备或仿真上。这非常有用,尤其是在没有 ssh 终端连接的情况下。

表 8-1 。主要 ADB 设备命令

|

命令

|

描述

|
| --- | --- |
| adb push <local> <remote> | 将file/dir复制到设备 |
| adb pull <remote> [<local>] | 从设备复制file/dir |
| adb sync [ <directory> ] | 仅在更改时复制host->device(-l表示列表但不复制)(参见adb help all) |
| adb shell | 交互式运行远程 shell |
| adb shell <command> | 运行远程 shell 命令 |
| adb emu <command> | 运行模拟器控制台命令 |
| adb logcat [ <filter-spec> ] | 查看设备日志 |
| adb forward <local> <remote> | 正向套接字连接
正向规格为:

  • tcp:<port>
  • localabstract:<unix domain socket name>
  • localreserved:<unix domain socket name>
  • localfilesystem:<unix domain socket name>
  • dev:<character device name>

jdwp:<process pid> (remote only) |
| adb jdwp | 列出主持 JDWP 传输的进程的 PID |
| adb install [-l] [-r] [-s] <file> | 将此包文件推送到设备并安装
( -l表示向前锁定应用)
( -r表示重新安装应用,保留其数据)
( -s表示安装在 SD 卡上,而不是内部存储) |

有关 ADB 设置和使用的更多详细信息,请参见http://developer.android.com/guide/developing/tools/adb.html

使用 Eclipse 的 Android 调试工具插件

对于基于英特尔架构的设备,设置过程与http://developer.android.com/sdk/eclipse-adt.html#installing中描述的过程没有显著不同。Android 调试工具 (ADT )插件为基于英特尔架构的仿真器以及目标设备提供完整的 Eclipse IDE 集成应用调试。它提供了两种具有不同特性集的不同调试视角。您可以根据需要在两者之间切换,在调试应用时,它们都提供不同的优势。

Eclipse 中的调试透视图

Eclipse 中的调试透视图,如图 8-11 中的所示,允许您访问以下选项卡:

  • 调试。显示以前和当前调试的 Android 应用及其当前运行的线程。
  • 变量。设置断点时,在代码执行期间显示变量值。
  • 断点。显示应用代码中设置的断点列表。
  • LogCat 。允许您实时查看系统日志消息。LogCat 选项卡在 DDMS 视角中也是可用的。

9781430261308_Fig08-11.jpg

图 8-11 。Eclipse 中的调试透视图

您可以通过单击窗口image打开透视图image调试来访问调试透视图。有关更多信息,请参考 Eclipse 调试器的相应文档。

DDMS 的视角

Eclipse 中的 DDMS 透视图,如图 8-12 所示,允许您从 Eclipse IDE 中访问 DDMS 的所有特性。DDMS 的以下部分可供您选择:

  • 设备。显示了连接到 ADB 的设备和 avd 的列表。
  • 仿真器控制。允许您执行设备功能。
  • LogCat 。允许您实时查看系统日志消息。
  • 螺纹。显示虚拟机中当前运行的线程。
  • 。显示虚拟机的堆使用情况。
  • 分配跟踪器。显示对象的内存分配。
  • 文件浏览器。允许您浏览设备的文件系统。

9781430261308_Fig08-12.jpg

图 8-12 。《月蚀》中的 DDMS 视角

应用运行时调试环境

调试以基于英特尔架构的设备为目标的 Android 应用的不同之处在于设置调试目标设备。

使用 Android 虚拟设备管理器(Android SDK 的一部分)选择目标设备,进入 Eclipse IDE 下拉菜单中的窗口image AVD 管理器。您需要确保选择英特尔凌动(x86)作为操作系统映像和设备仿真的 EABI 目标(参见图 8-13 )。

9781430261308_Fig08-13.jpg

图 8-13 。在 Android 虚拟设备管理器中选择基于英特尔凌动处理器的设备

如果您按照本章开头概述的步骤设置 ADB 并建立到物理设备的调试桥,您将在 Eclipse IDE 中看到一个设备选择器条目,您可以从中选择应用部署和调试的目标。

否则,调试以英特尔架构为目标的 Android 应用与调试以 ARM 架构为目标的 Android 应用没有什么不同。

英特尔硬件加速执行管理器

英特尔硬件加速执行管理器(HAXM 结合英特尔和官方 Android SDK 管理器提供的 Android x86 仿真器映像,英特尔 HAXM 可在支持英特尔 VT 的系统上实现更快的 Android 仿真。有关安装和使用英特尔 HAXM 的更多信息,请参考 第十一章:使用英特尔硬件加速执行管理器在 x86 仿真 上加速 Android。

x86 Android 4.0.4(冰激凌三明治)仿真器系统映像使您能够在开发机器上运行 Android 仿真。结合 Android SDK,您可以在基于英特尔架构的虚拟 Android 设备上测试您的 Android 应用,从而充分利用底层的英特尔架构和英特尔虚拟化技术。

为了安装仿真器系统映像,您需要使用 Android SDK 管理器。

英特尔 HAXM 可以通过 Android SDK 管理器安装(参见图 8-14 )。英特尔 HAXM 要求安装 Android SDK(版本 17 或更高)。有关更多信息,请参考 Android 开发者网站(http://developer.android.com/sdk/)。

9781430261308_Fig08-14.jpg

图 8-14 。英特尔硬件加速执行管理器下载

英特尔 HAXM 可用于 Linux、Windows 和 OS X 主机操作系统。作为一个例子,我们概述了在 Ubuntu 64 位操作系统上的安装,因为这是 Google 为 Android 版本验证和支持的主要平台。

下一节包含如何安装 KVM、在 Ubuntu 主机平台上启用 KVM 以及使用英特尔硬件辅助虚拟化(hypervisor)启动英特尔 Android x86 仿真器的快速步骤。当 AVD 利用英特尔 HAXM 时,操作系统的运行速度将比没有虚拟机管理程序时明显更快、更流畅。

KVM 安装

  1. To see if your processor supports hardware virtualization, you can review the output from this command:

    $ egrep -c '(vmx|svm)' /proc/cpuinfo
    

    如果这个命令返回0,你的 CPU 不支持硬件虚拟化。

  2. Next, install CPU checker:

    $ sudo apt-get install cpu-checker

  3. Now you can check if your CPU supports KVM:

    $kvm-ok

    1. If you see:

      "INFO: Your CPU supports KVM extensions
      INFO: /dev/kvm exists
      KVM acceleration can be used"
      

      使用 KVM 扩展,您可以更快地运行虚拟机。

    2. If you see:

      "INFO: KVM is disabled by your BIOS
      HINT: Enter your BIOS setup and enable Virtualization Technology (VT),
      and then hard poweroff/poweron your system
      KVM acceleration can NOT be used"
      

      您需要进入 BIOS 设置并启用英特尔 VT。

使用 64 位内核

建议在主机操作系统上运行 64 位内核,但这不是必需的。要为虚拟机提供超过 2GB 的内存,您必须使用 64 位内核。在 32 位内核安装中,对于给定的虚拟机,您最多只能使用 2GB 的 RAM。此外,64 位系统可以承载 32 位和 64 位来宾。32 位系统只能托管 32 位来宾。

  1. To see if your processor is 64-bit, you can run this command:

    $ egrep -c ' lm ' /proc/cpuinfo
    

    如果打印出0,说明你的 CPU 不是 64 位。如果打印的是1或更高版本,则为。注意:lm代表长模式,相当于 64 位 CPU。

  2. To see whether your running kernel is 64-bit, just issue the following command:

    $ uname -m
    

    返回值x86_64表示正在运行的 64 位内核。如果您看到i386i486i586i686,那么您运行的是 32 位内核。

安装 KVM

要安装 KVM,请按照下列步骤操作:

  1. For Ubuntu 12.04 or later:

    $ sudo apt-get install qemu-kvm libvirt-bin ubuntu-vm-builder bridge-utils
    

    通过选择无配置,您可以忽略图 8-15 中显示的后缀配置请求。

    9781430261308_Fig08-15.jpg

    图 8-15 。KVM 安装后缀配置设置

  2. Next, add your <username> account to the group kvm and libvirtd:

    $ sudo adduser your_user_name kvm
    $ sudo adduser your_user_name libvirtd
    

    安装完成后,您需要再次登录,以便您的用户帐户成为kvmlibvirtd用户组的活动成员。该组的成员可以运行虚拟机。

    要验证安装,您可以使用以下命令测试安装是否成功:

    $ sudo virsh -c qemu:///system list
    

启动 Android 虚拟设备

Android for x86 英特尔仿真器(参见图 8-16 )可以使用以下命令启动:

$ <SDK directory>/tools/emulator-x86 -avd Your_AVD_Name -qemu -m 2047 -enable-kvm

9781430261308_Fig08-16.jpg

图 8-16 。在英特尔架构仿真层运行 Android 的 AVD

Your_AVD_Name作为你选择的名字;-qemu为 qemu 提供选项,-m为仿真的 Android(也就是 guest)指定内存量。如果您使用的内存值太小,则可能会因为频繁的交换活动而影响性能。

使用 Eclipse 中的 AVD 管理器来启动虚拟设备

Google 推荐使用以下步骤从 Eclipse IDE 中使用 AVD 开始调试应用:

  1. 在 Eclipse 中,单击您的 Android 项目文件夹,然后选择 Run image Run Configurations。

  2. 在运行配置对话框的左侧面板中,选择您的 Android 项目运行配置或创建一个新配置。

  3. 单击目标选项卡。

  4. 选择您之前创建的基于英特尔架构的 AVD。

  5. In the Additional Emulator Command Line Options field, enter:

    -qemu -m 2047 -enable-kvm

  6. 使用这个运行配置运行您的 Android 项目。

在 Oracle VirtualBox 中运行 Android】

在 Oracle VirtualBox 虚拟机内的桌面 PC 上运行完整的 Android 操作系统映像是 Windows 主机系统上 KVM 和 QEMU 的一种有价值的替代方案,尤其是对于那些希望在 Android 上开发和调试本机代码的开发人员而言。

在本节中,我们将分享以下一些详细信息:

  • 从 Google x86 VirtualBox 官方目标vbox-x86-eng(包含在 Android 4.0.1 源代码树中)构建面向 x86 的 Android 4.0.x VirtualBox 安装程序。
  • 使用英特尔提供的 Linux 2.6 内核为 VirtualBox 添加特定功能。
  • 如何使用installer.vdi将 Android ICS 4.0 安装到 VirtualBox 中?

除了由英特尔硬件加速执行管理器(英特尔 HAXM)技术支持的谷歌 AVD 仿真器之外,面向 x86 VirtualBox 的 Android 正在运行一个真正基于虚拟化英特尔架构的环境。因此,它为开发人员和合作伙伴提供了另一种快速、高性能的工具,用于快速应用开发和测试。在典型的英特尔酷睿 i5 主机系统上,在 VirtualBox 中启动 Android 4.0.x 大约需要 10 秒钟。速度、性能和用户体验是 VirtualBox 在 Android 开发人员中受欢迎的原因,尤其是在基于英特尔架构的平台上。基于英特尔架构的 Android 平板电脑和智能手机在市场上的可用性仍然有限,使用开发主机系统上的虚拟环境开始开发可能比依赖 USB 调试通信更方便。特别是对于基于英特尔架构的开发主机和 Android 目标设备,这成为一个有效且有趣的替代方案。

Google x86 VirtualBox 为 Android 4.x 构建目标

如果你之前一直在使用 Google 的 Android 4.0.x 源码库,你可能会注意到 Google 提供了一个 x86 版本的 VirtualBox target vbox_x86-eng。在开始构建之前使用lunch命令,Google 为 Android 4.0.x 提供的前三个目标是:

$ lunch
1\. full-eng
2\. full_x86-eng
3\. vbox_x86-eng

有了vbox_x86-eng (#3)目标,应用开发人员和系统开发人员都可以创建android_disk.vdiandroid installer.vdi包。然后,这些可以用于在 VirtualBox 中运行 Android 4.x,以便在 Windows、Linux 和 OS X 上进行应用开发和系统集成。

下载源代码树并安装存储库

要安装、初始化和配置存储库,请遵循以下步骤(您可以在http://source.android.com/source/downloading.html找到更多信息):

$ mkdir ∼/bin
$ PATH=∼/bin:$PATH
$ curlhttps://dl-ssl.google.com/dl/googlesource/git-repo/repo> ∼/bin/repo
$ chmod a+x ∼/bin/repo
$ mkdir ANDROID_TOP_PATH
$ cd ANDROID_TOP_PATH

要获得可用分支的列表(从您的 Android 存储库签出根目录),请使用以下命令:

$ git --git-dir .repo/manifests/.git/ branch –a

运行repo init以获得包含所有最新更新的可用存储库子分支的当前列表:

$ repo init -uhttps://android.googlesource.com/platform/manifest-b android-4.0.1_r1

要使用 Gerrit 代码审查工具,您需要一个连接到注册的 Google 帐户的电子邮件地址。请确保这是一个您可以接收邮件的实时地址。内核版本和内部版本号将被分配给你的内部版本,信息将显示在 Android/Settings/About Phone/页面上。

成功的初始化将以一条消息结束,该消息指出 Repo 已在您的工作目录中初始化。您的客户机目录现在应该包含一个.repo目录,其中存储了诸如清单之类的文件。

要将文件从存储库中拉到您的工作目录中,如默认清单中所指定的,运行

$ repo sync

默认情况下,对 Android 源代码的访问是匿名的。为了防止服务器过度使用,每个 IP 地址都与一个配额相关联。

构建支持鼠标的自定义内核

由于 Android 是为触摸屏设备设计的,默认情况下它不支持鼠标指针。此外,它可能不包括硬连线以太网支持的驱动程序,因为大多数 Android 设备只使用无线无线电进行网络通信。要添加这些功能,您需要用鼠标支持以及您需要的任何附加特性来重建内核。为此,请按照下列步骤操作:

  1. 通过 Android SDK 管理器下载 Android x86 模拟器映像插件。
  2. 创建一个新文件夹并将kernel_sdk_x86.tar.gz解压到其中,创建一个包含内核源代码树的文件夹。
  3. 切换到保存内核文件的目录。
  4. 现在您已经有了内核源代码,需要修改配置以匹配用作 VirtualBox 主机系统的硬件并重新构建。内核源代码提供的menuconfig图形用户界面将允许方便地做到这一点:
$ cp ANDROID_TOP_PATH/your_kernel_path/arch/x86/configs/vbox_defconfig  .config
$ make CC=gcc-4.4 CXX=g++-4.4 ARCH=x86 menuconfig

这将需要几秒钟的时间来编译和加载。一旦加载完毕,您就可以使用

  • 向上/向下箭头导航
  • 回车选择(展开)
  • 要包含的 y(或空格)

要启用鼠标支持,请导航至设备驱动程序image输入设备支持image鼠标。

image menuconfig可用于确保支持您的应用或系统集成所需的所有功能可用。对于应用开发人员来说,在默认的 Android 版本上测试和验证应用同样重要。只有这样,才能保证与多个不同设备制造商的 Android 设备的最大兼容性。

对内核配置做了必要的修改后,现在可以编译它了。不需要太长时间,所以我选了一个低–j值。重要的是要注意,如果你省略了CCCCX参数,编译将会提前终止(在这个设置上),没有一个明确的错误,因为它将使用 4.6 版本。

$ make CC=gcc-4.4 CXX=g++-4.4 ARCH=x86 –j8

–j参数提供了可用于编译的内核数量。此示例假设四核系统启用了英特尔超线程技术。

构建成功完成后,构建日志的最后一行将会显示

Kernel: arch/x86/boot/bzImage is ready

添加修补的内核

内核映像bzImage需要重命名为kernel-vbox并复制到/ANDROID_TOP_PATH/prebuilt/android-x86/kernel/kernel-vbox:

$ cp /ANDROID_TOP_PATH/kernel/arch/x86/boot/bzImage   /ANDROID_TOP_PATH/prebuilt/android-x86/kernel/kernel-vbox

使用 CCACHE 减少编译时间

通过使用编译器缓存,可以大大减少后续编译的编译时间。要设置 50GB 缓存,请执行以下操作:

  1. 安装 CCcache 程序并为您的ccache :

    $ sudo apt-get install ccache
    $ sudo mkdir /ANDROID_TOP_PATH/ccache
    $ sudo chown $LOGNAME  /ANDROID_TOP_PATH/ccache
    

    创建一个目录

  2. Set up your environment variables for ccache support by modifying ∼/.bashrc:

    $ sudo gedit ∼/.bashrc

  3. 添加以下内容:

    export CCACHE_DIR=/ANDROID_TOP_PATH/ccache
    export USE_CCACHE=1
    
  4. 设置ccache尺寸。

    $ ccache -F 100000
    $ ccache -M 50G
    

用新内核构建 Android 4.0.x

要设置环境,请执行以下操作:

$ /ANDROID_TOP_PATH/> source build/envsetup.sh

对于 ICS 4.0.1,您将看到:

including device/samsung/maguro/vendorsetup.sh
including device/samsung/tuna/vendorsetup.sh
including device/ti/panda/vendorsetup.sh
including sdk/bash_completion/adb.bash

为了确保在使用lunch命令时选择一个有效的目标,只需简单地执行以下操作即可:

$ lunch

并从列表中选择所需的目标:

1\. full-eng
2\. full_x86-eng
3\. vbox_x86-eng
4\. full_maguro-userdebug
5\. full_tuna-userdebug
6\. full_panda-eng
Which would you like? [full-eng]

image 注意确保选择3\. vbox_x86-eng

PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=4.0.1
TARGET_PRODUCT=vbox_x86
TARGET_BUILD_VARIANT=eng
TARGET_BUILD_TYPE=release
TARGET_BUILD_APPS=
TARGET_ARCH=x86
TARGET_ARCH_VARIANT=x86
HOST_ARCH=x86 HOST_OS=linux
HOST_BUILD_TYPE=release
BUILD_ID=ITL41D

运行make:

$ make -j8

构建 VirtualBox 磁盘和 Android 安装程序

最后,将Android_disk.vdiinstaller_vdi构建在一起:

$ make android_disk_vdi  installer_vdi  -j8

如果构建成功,您将在构建日志中看到以下输出:

Done with VirtualBox bootable disk image -
[ out/target/product/vbox_x86/android_disk.vdi ]-
Done with VirtualBox bootable installer image -
[ out/target/product/vbox_x86/installer.vdi ]-

使用android_disk.vdi,可以在 VirtualBox 中选择新建image创建新的虚拟机。你可以使用android_disk.vdi作为现有的磁盘镜像来作为虚拟机的基础,它现在会自动引导 Android 4.0.x。

为 VirtualBox 设置启动参数非常简单。进入关于此手机的设置image,查看图 8-17 中所示的制造信息。

9781430261308_Fig08-17.jpg

图 8-17 。Oracle VirtualBox 中的 Android 4.0.x 构建信息

使用 Android 安装盘创建一个大的虚拟分区

即使 VirtualBox 中的文件大小扩展到 550MB,它仍然太小,无法加载应用进行测试。将 Android 安装到一个更大的分区(最好大于 4GB)需要一个 Android 安装盘。

首先从 VirtualBox 菜单栏中选择 Machine image New,并使用 Create New Virtual Machine 向导在 IDE 控制器配置条目下创建一个更大的磁盘。

然后在虚拟机的设置image存储中,添加installer.vdi作为主 IDE 从机,如图图 8-18 所示。现在你可以在刚刚创建的 4GB 硬盘上安装 Android 了。

9781430261308_Fig08-18.jpg

图 8-18 。为虚拟机配置存储设置

  1. 启动模拟器。
  2. 使用 F12 进入 BIOS 引导菜单。引导至辅助驱动器。
  3. 使用grub选择安装选项:2。从主要从机启动(参见图 8-19)。

9781430261308_Fig08-19.jpg

图 8-19 。在 VirtualBox 中引导安装程序映像

当你看到Done processing installer config时,输入reboot

image 注意第一次在目标虚拟磁盘上安装时,安装可能会失败。将打印一条消息,告诉您再次运行安装程序。通过使用以下命令来完成此操作:

$ installer

重新启动后,您可以看到您的 Android 正在从您创建的更大的磁盘上运行,并且您可以在 VirtualBox IDE 控制器设置下安全地从存储中删除installer.vdi

串口

默认情况下,虚拟机中的串行端口支持处于启用状态。但是,COM1 串行端口在使用前需要初始化和配置。以下指令使 VirtualBox 在您的主目录中创建一个名为.vbox_pipe的命名管道。在命令行上,输入:

$ VBoxManage modifyvm Android --uart1 0x03f8 4
$ VBoxManage modifyvm Android --uartmode1 server /home/user/.vbox_pipe

或者,在 VirtualBox GUI 中,使用虚拟机设置菜单中的串行端口选项卡来启用 COM1 作为主机管道。选择创建管道,将其创建为/home/user/.vbox_pipe

若要连接到此命名管道,请使用:

$ socat unix-client:$HOME/.vbox_pipe stdout

image 注意 VirtualBox 可能不理解环境变量(比如$HOME),所以你必须指定一个完整的显式路径,比如/home/user/.vbox_pipe

以太网

镜像中的 DHCP 启用了以太网端口(eth0)。要通过 ADB 连接到它,您需要查找已经分配的 DHCP 地址。

如果您使用的是桥接以太网,您可以从 shell 提示符处获得该地址,或者从串行端口获得,或者使用以下命令从 Developer Tools image终端仿真器获得:

$ netcfg

如果您使用的是主机专用适配器vboxnet0,您应该使用地址 192.168.56.101。

最终注释

现在你有了一个运行 Android 4.0.x 的 VirtualBox 映像,它完全是根据谷歌的官方目标构建的(见图 8-20)。

9781430261308_Fig08-20.jpg

图 8-20 。Android 操作系统在 VirtualBox 虚拟机中完全启动

使用 GNU 项目调试器 GDB 进行调试

Android NDK 包括 GDB,GNU 调试器,允许你启动、暂停、检查和修改程序。在 Android 设备上,更普遍的是在嵌入式设备上,GDB 被配置为客户端/服务器模式。该程序在作为服务器和远程客户端的设备上运行。开发人员的工作站连接到它,并发送类似于本地应用的调试命令。GDB 本身就是一个命令行工具。在查看 Eclipse CDT 集成之前,您将首先查看它的基本使用模型。

用 GDB 调试时,gdbserver 在设备上运行处理调试通讯。然而,您可能仍然使用底层 USB 到以太网加密狗驱动程序和 ADB 与传输层通信,在传输层上gdbserver通过 TCP/IP 与运行在开发主机上的 GDB 通信。

有一个gdbclient应用设置调试通信环境,并在被调试设备上启动gdbserver

usage: gdbclient EXECUTABLE :PORT [PROG_PATH]
EXECUTABLE  executable name (default app_process)
PORT        commection port (default :1234)
PROG_PATH   executable full path on target (ex /system/bin/mediaserver)

如果PROG_PATH被设置,gdclient尝试启动gdbserver并将其连接到正在运行的PROG_PATH

要显式启动gdbserver,您可以使用以下命令:

# gdbserver :1234 --attach 269
Attached; pid = 269
Listening on port 1234

以下分步调试会话启动说明说明了即使使用 GDB(而非 ADT 或 DDMS)进行调试,ADB 仍然是调试通信的基础。假设正在使用端口 1234。

午餐流程如下:

gdbserver :1234 /system/bin/executable

要附加到现有流程,请执行以下操作:

gdbserver :1234 --attach pid

在您的工作站上,将端口 1234 转发到带有adb的设备:

adb forward tcp:1234 tcp:1234

启动一个特殊版本的gdb,它位于源代码树的“预构建”区域:

prebuilt/Linux/toolchain-eabi-4.x.x/bin/i686-android-linux-gdb(对于 Linux)或

prebuilt/darwin-x86/toolchain-eabi-4.x.x/bin/i686-android-linux-gdb(为达尔文)。

如果你找不到 GDB 的任何一个特殊版本,运行这个

 $find prebuilt –name i686-android-linux-gdb

在源代码树中找到并运行最新版本。请确保使用 symbols 目录中的可执行文件副本,而不是主 Android 目录,因为主目录中的副本已经去掉了符号信息。

在 GDB,告诉 GDB 在哪里可以找到将要加载的共享库:

set solib-absolute-prefix /absolute-source-path/out/target/product/product-name/symbols
set solib-search-path /absolute-source-path/out/target/product/product-name/symbols/system/lib

到您的源代码树的路径是绝对源代码路径??。确保你指定了正确的目录——如果你犯了错误,GDB 可能不会告诉你。通过发出gdb命令连接到设备:

(gdb) target remote :1234

:1234告诉 GDB 连接到本地主机端口 1234,该端口由 ADB 桥接到设备。

现在你可以开始用 GDB 调试运行在 Android 上的原生 C/C++ 代码,就像你习惯的那样。如果您还安装了 Eclipse(如果您使用 Android SDK 进行基于 Dalvik/Java 的应用开发,您可能会这样做),Eclipse 和 Eclipse 的 GDB 集成可以直接用于添加断点和检查程序。

事实上,使用 Eclipse,您可以在 Java 和 C/C++ 源文件中轻松插入断点。你只需点击文本编辑器的左边。得益于 ADT 插件,Java 断点可以开箱即用,该插件通过 Android 调试桥管理调试。CDT 不是这样的,它当然不支持 Android。因此,插入断点没有任何作用,除非您将 CDT 配置为使用 NDK 的 GDB,它本身需要绑定到原生 Android 应用以便调试它。首先,按照以下步骤在应用中启用调试模式:

  1. 有一件重要的事情要做,但是很容易忘记,那就是激活你的 Android 项目中的调试标志。这是在应用清单AndroidManifest.xml中完成的。不要忘记为本地代码使用合适的 SDK 版本:

    <?xml version="1.0" encoding="utf-8"?> <manifest ...>
    <uses-sdk android:minSdkVersion="10"/> <application ...
    android:debuggable="true"> ...
    
  2. 启用清单中的调试标志。这将自动激活本机代码中的调试模式。然而,APP_OPTIM标志也控制调试模式。如果它已经在Android.mk中被手动设置,那么检查它的值是否被设置为调试(而不是发布)或者简单地删除它:

    APP_OPTIM := debug
    
  3. Now configure the GDB client that will connect to the device. Recompile the project and plug your device in or launch the emulator. Run and leave your application. Ensure the application is loaded and its PID is available. You can check it by listing processes using the following command (use Cygwin in Windows):

    $ adb shell ps |grep gl2jni
    

    应该返回一行:

    app_75 13178 1378 201108 68672 ffffffff 80118883 S com.android.gl2jni
    
  4. Open a terminal window and go to your project directory. Run the ndk-gdb command (located in the Android NDK folder, for example android-ndk-r8\):

    $ ndk-gdb
    

    该命令不应返回消息,但会在obj\local\x86目录中创建三个文件:

    • gdb.setup。这是为 GDB 客户端生成的配置文件。
    • app_process。该文件直接从您的设备中检索。它是一个系统可执行文件,在系统启动时启动,并分叉以启动一个新的应用。GBD 需要这个参考文件来寻找它的标记。在某些方面,它是你的应用的二进制入口。
    • libc.so。这也是从您的设备中检索的。它是 GDB 用来跟踪运行时创建的所有本机线程的 Android 标准 C 库(通常称为 bionic)。
  5. 在您的项目目录中,复制obj\local\x86\gdb.setup并将其命名为gdb2.setup。打开它并删除下面一行,它请求 GDB 客户端连接到运行在设备上的 GDB 服务器(将由 Eclipse 执行):

    (gdb) target remote :1234
    
  6. 在 Eclipse 主菜单中,转到 Run | Debug Configurations,并在 C/C++ 应用中创建一个名为GL2JNIActivityDefault的新调试配置。此配置将在您的计算机上启动 GDB 客户端,并连接到设备上运行的 GDB 服务器。

  7. In the Main tab (see Figure 8-21), set the project to your own project directory. Set the C/C++ application to point to obj\local\ x86\app_process using the Browse button (you can use an absolute or relative path).

    9781430261308_Fig08-21.jpg

    图 8-21 。C/C++ 应用的调试配置

  8. Switch the launcher type to Standard Create Process Launcher (see Figure 8-22) using the link Select Other link at the bottom of the window.

    9781430261308_Fig08-22.jpg

    图 8-22 。选择首选发射器

  9. Go to the debugger file and set the debugger type to gdbserver. Set the GDB debugger to android-ndk-r8\toolchains\x86-4.4.3\prebuilt\windows\bin\i686-android-linux-gdb.exe. The GDB command file (see Figure 8-23) needs to point to the gdb2.setup file located in \obj\local\x86 (you can use an absolute or relative path).

    9781430261308_Fig08-23.jpg

    图 8-23 。调试器设置面板

  10. Go to the Connection tab (see Figure 8-24) and set Type to TCP. Keep the default values for hostname or IP address and port number (localhost, 5039).

![9781430261308_Fig08-24.jpg](https://gitee.com/OpenDocCN/vkdoc-android-pt2-zh/raw/master/docs/andr-x86/img/9781430261308_Fig08-24.jpg)

图 8-24 。调试器设置面板上的连接设置
  1. Now, let’s configure Eclipse to run a GDB server on the device. Make a copy of android-ndk-r8\ndk-gdb and open it with a text editor. Find the following line:
```java
$GDBCLIENT -x 'native_path $GDBSETUP'
```

将其注释掉,因为 GDB 客户端将由 Eclipse 本身运行:

`#$GDBCLIENT -x 'native_path $GDBSETUP'`
  1. In the Eclipse main menu, go to Run | External Tools | External Tools | Configurations (see Figure 8-25), and create a new configuration GL2JNIActivity_GDB. This configuration will launch GDB server on the device.
![9781430261308_Fig08-25.jpg](https://gitee.com/OpenDocCN/vkdoc-android-pt2-zh/raw/master/docs/andr-x86/img/9781430261308_Fig08-25.jpg)

图 8-25 。外部工具配置
  1. 在主选项卡上,设置指向android-ndk-r8中修改的ndk-gdb的位置。将工作目录设置为您的应用目录位置。或者,设置参数文本框:
    * Verbose:查看 Eclipse 控制台中发生的详细情况。
    * 强制:自动终止任何先前的会话。
    * Start:让 GDB 服务器启动应用,而不是在应用启动后连接它。如果您只调试本机代码而不调试 Java,那么这个选项很有意思。
  2. 现在,像往常一样启动应用。
  3. 一旦应用启动,您可以通过控制台直接启动ndk-gdb或者启动外部工具配置GL2JNIActivity_GDB,这将启动设备上的 GDB 服务器。GDB 服务器接收远程 GDB 客户端发送的调试命令,并在本地调试您的应用。
  4. Open jni\gl_code.cpp and set a breakpoint (see Figure 8-26) in setupgraphics by double-clicking the left margin of the text editor (or right-clicking and selecting Toggle Breakpoint).
![9781430261308_Fig08-26.jpg](https://gitee.com/OpenDocCN/vkdoc-android-pt2-zh/raw/master/docs/andr-x86/img/9781430261308_Fig08-26.jpg)

图 8-26 。设置断点
  1. 最后,启动GL2JNIActivity默认 C/C++ 应用配置来启动 GDB 客户端。它通过套接字连接将调试命令从 Eclipse CDT 中继到 GDB 服务器。从开发人员的角度来看,这几乎就像调试本地应用一样。

英特尔图形性能分析器(英特尔 GPA)

还有一些调试图形性能的特定工具。英特尔 GPA 系统分析器是英特尔图形性能分析器(GPA)的之一,支持基于英特尔的 Android 设备,旨在帮助应用和驱动工程师优化其 OpenGL ES 工作负载。

本节介绍了如何通过 USB 连接在 Android 设备上配置和使用英特尔 GPA。当连接到 Android 设备时,英特尔 GPA 系统分析器提供 OpenGL ES API、CPU 和 GPU 性能指标,并提供多种图形管道状态覆盖,以帮助您分析 OpenGL ES 应用性能。

要在基于 Android x86 的设备上使用英特尔 GPA 系统分析器,您需要从文档中检查目标机器和固件版本。

要开始收集指标,您需要在客户端系统上安装英特尔 GPA 系统分析器,并将其连接到目标设备:

  1. 在 Windows/Linux 客户机上安装英特尔 GPA 2013 。

  2. 启动英特尔 GPA 系统分析器。

  3. 确保使用 USB 电缆将 Android 设备连接到客户端系统。

  4. 等待 10 秒钟,让您的客户端系统检测目标设备。找到的设备出现在窗口中。目标设备列表每五到六秒刷新一次。

  5. Find the device you want to connect to and click Connect (see Figure 8-27). The Intel GPA System Analyzer will copy the required components to the target device and generate the list of installed applications. You can interrupt the connection process by clicking Stop.

    9781430261308_Fig08-27.jpg

    图 8-27 。选择连接的设备

  6. Select the desired application from the list of available ones. The Application List screen (see Figure 8-28) displays all user and system applications installed on the Android device.

    9781430261308_Fig08-28.jpg

    图 8-28 。应用列表

  7. 应用将会启动,您将会在英特尔 GPA 系统分析器窗口中看到其数据。

  8. 要切换到不同的应用,请单击“上一步”。请注意,正在运行的应用将被强制关闭。

  9. To switch to a different target device, click Back. The PowerVR graphics architecture consists of the following core modules that convert the submitted 3D application data into a rendered image—Tile Accelerator (TA), Image Synthesis Processor (ISP), and the Texture & Shading Processor (TSP). Intel GPA metrics in the “GPU” group correspond to one of these core modules, and the order of metrics in the Metrics List depends on the order of the core modules in the graphics pipeline (see Figure 8-29).

    9781430261308_Fig08-29.jpg

    图 8-29 。英特尔 GPA 系统分析器窗口

运行在英特尔凌动处理器上的 Android 操作系统的系统调试

到目前为止,本章一直专注于开发和调试应用,无论它们是单独使用 Android 的 Java 运行时,还是作为 x86 英特尔架构二进制文件和共享对象本机运行。

对于系统集成商和设备制造商来说,可能还需要处理设备驱动程序和系统软件堆栈层。如果需要实施额外的特定于平台的外围设备支持,或者如果要将第一个操作系统移植到基于英特尔凌动处理器的新设备上,这一点尤为重要。

在接下来的章节中,您将了解联合测试行动小组 IEEE 1149.1 (JTAG ) 基于标准的调试解决方案,以及 ARM 和英特尔架构之间可能影响系统级调试的架构差异。

JTAG 调试

对于真正的固件、操作系统级和设备驱动程序调试,世界上最常用的方法是使用 JTAG 调试接口。从历史上看,联合测试行动小组是由行业领导者在 20 世纪 80 年代成立的,旨在定义测试接入端口和印刷电路板的标准。IEEE 在 20 世纪 90 年代采用他们的标准作为 IEEE 1149.1 标准测试接入端口和边界扫描架构。为了简洁起见,测试标准通常被称为 JTAG。从最初用于电路板测试,它已经发展成为独立于操作系统和操作系统级平台调试的事实上的接口标准。

关于 JTAG 及其在现代系统软件堆栈调试中的使用的更多背景信息可在文章“JTAG 101;IEEE 1149.x 和软件调试”,作者是兰迪·约翰逊和斯图尔特·克里斯蒂(见http://www.intel.com/content/www/us/en/intelligent-systems/jtag-101-ieee-1149x-paper.html)。

从 OEM 及其合作伙伴应用和驱动程序开发人员的角度来看,了解在片上系统(SoC)集成智能系统或智能手机外形设备的不同部分上运行的驱动程序和软件堆栈组件之间的交互对于确定平台稳定性至关重要。从芯片验证者的角度来看,底层软件栈提供了一个测试环境,该环境模拟了平台在真实用例中将要面临的压力因素。简而言之,现代 SOC 需要理解完整的封装及其复杂的真实世界交互,而不仅仅是单个硬件组件的积极单元测试结果。这是基于 JTAG 的系统软件调试方法所能提供的洞察力水平。这可以通过将 JTAG 固有的深度硬件感知与导出目标上运行的 Android 操作系统的状态信息的能力相结合来实现。

尤其对于设备驱动程序调试,了解芯片组上外围设备的确切状态以及设备驱动程序与操作系统层和软件堆栈其余部分的交互非常重要。

如果你从系统调试的角度来看 Android 看设备驱动和操作系统内核——它实际上只是 Linux 的一个专门分支。因此可以像对待任何 2.6.3x 或更高版本的 Linux 一样对待它。

英特尔凌动处理器 Z2460 支持 IEEE-1149.1 和 IEEE-1149.7 (JTAG)边界扫描和 MPI 并行跟踪接口(PTI),以及通过英特尔 JTAG 兼容扩展调试端口(XDP)进行的基于分支跟踪存储(BTS)的指令跟踪。

各 JTAG 厂商提供支持 Android 的系统调试解决方案,包括:

  • 河风(http://www.windriver.com/products/JTAG-debugging/)
  • 劳特巴赫〔??〕
  • 英特尔 ( http://software.intel.com/en-us/articles/embedded-using-intel-tools

Android OS 调试,

让调试基于 Android 的平台变得复杂的是,Android 积极利用低功耗空闲状态和睡眠状态来优化功耗。因此,真正的挑战变成了通过低功率状态进行调试,或者通过一些低功率状态保持 JTAG 功能,或者当这不可能时,一旦 JTAG 的芯片组功率域被重新启用,就重新连接 JTAG。

这些类型的平台上的许多操作系统级问题往往集中在电源模式变化和睡眠/唤醒序列上。

无论是基于调试代理还是使用 JTAG 设备接口,系统调试器都是非常有用的工具,有助于满足操作系统开发的几个关键目标。

调试器可用于验证引导过程,并分析和纠正稳定性问题,如运行时错误、分段错误或引导期间未正确启动的服务。

通过提供页表、描述符表和指令跟踪的详细访问和表示,它还可以用于识别和纠正操作系统配置问题。指令跟踪和内存表访问的结合是一个非常强大的工具,可以识别堆栈溢出、内存泄漏甚至数据中止情况的根本原因。

图 8-30 显示了对英特尔 JTAG 调试器提供的页面转换属性和描述符表的详细访问。x86 在定义转换表的深度和寻址内存块的粒度方面具有高度的灵活性,这种内存布局的易访问性和可见性对于操作系统级别的系统开发变得更加重要。

9781430261308_Fig08-30.jpg

图 8-30 。内存配置的调试器视图示例

这突出了在英特尔架构和许多其他架构上开发和配置 Android 操作系统软件堆栈的两个关键区别。选择器基址和偏移寻址模型与本地描述符表(LDT)和全局描述符表(GDT)相结合,支持从物理内存到虚拟内存的深度、多层地址转换,并且具有可变的地址块粒度。对于具有受保护的隔离内存空间的分隔环境中的自定义内存配置,这是一个强大的功能。但是,如果使用不当,它也会增加内存访问时间。

英特尔架构与其他架构的另一个区别是处理系统中断。例如,在 ARM 上,从 0x0 到 0x20 的保留地址空间中有一组预定义的硬件中断。这些位置然后包含到中断处理程序的跳转指令。在英特尔架构上,采用了专用的硬件中断控制器。硬件中断不是通过内存空间直接访问的,而是通过访问 Intel 8529 中断控制器。这种方法的优点是中断处理程序已经允许直接处理附加设备的 I/O 中断。在不使用专用中断控制器的架构中,IRQ 中断通常需要用更复杂的中断处理程序来过载才能完成。

设备驱动调试

一个好的用于操作系统级调试的 JTAG 调试器解决方案应该进一步提供内核线程和活动内核模块以及内核导出的其他信息的可见性。为了允许调试动态加载的服务和设备驱动程序,可以使用导出驱动程序的初始化方法和销毁方法的存储位置的内核补丁或内核模块。

特别是对于系统配置和设备驱动程序调试,能够直接访问和检查设备配置寄存器的内容也很重要。位域编辑器的概念,如图 8-31 所示,对此非常有用。位域编辑器是 SoC 设备寄存器的按位可视化,允许在相关设备驱动程序与之交互时实时监控设备状态的变化。

9781430261308_Fig08-31.jpg

图 8-31 。设备寄存器位域编辑器视图

在 Android 压缩的zImage内核映像被解压到内存中之后,可以通过简单地释放调试器中的运行控制来分析代码,直到到达start_kernel。这意味着包含内核符号信息的vmlinux文件已经被加载。此时,可以使用软件断点。在引导过程的这一点之前,应该只使用基于断点寄存器的硬件断点,以避免调试器试图将断点指令写入未初始化的内存。一旦到达空闲循环mwait_idle,操作系统就成功启动。

此外,如果您的调试解决方案提供对基于分支跟踪存储(BTS)的指令跟踪的访问,此功能可以与 JTAG 调试器的所有常规运行控制功能结合使用,用于在异常时强制执行停止。然后,您可以反向分析执行流,以确定运行时问题的根本原因。

硬件断点

正如在 ARM 架构上一样,基于英特尔架构的处理器支持软件断点的断点指令以及数据和代码的硬件断点。在 ARM 架构上,通常会有一组断点和数据断点的专用寄存器(称为观察点)。常见的实现倾向于每种都提供两个。当这些寄存器包含一个值时,处理器检查通过程序计数器寄存器或存储器读/写对设置的存储器地址的访问。一旦发生访问,执行就会停止。这不同于软件断点,因为一旦遇到断点指令,它们的执行就被停止。由于断点指令替换了通常位于给定存储器地址的汇编指令,所以在通常位于断点位置的指令被执行之前,执行有效地停止。

英特尔架构上的硬件断点的实现与 ARM 上的非常相似,尽管它更灵活一些。

在所有英特尔凌动处理器内核上,都有四个存储地址的 DR 寄存器,这些寄存器会在内存提取之前(有时是之后)与内存总线上提取的地址进行比较。

您可以使用所有这四个寄存器来提供触发以下任何调试运行控制事件的地址:

  • 00.指令执行中断
  • 01.仅在数据写入时中断
  • 10.未定义的OR(如果架构允许)I/O 读取或写入中断
  • 11.数据读取或写入时中断,但取指令时不中断

因此,所有四个硬件断点都可以用作断点或观察点。观察点可以是只写的或读写的(或 I/O)。

交叉调试 :英特尔凌动处理器和 ARM 架构

许多以英特尔凌动处理器为目标的开发人员都有过主要针对具有固定指令长度的 RISC 架构进行开发的经验。MIPS 和 ARM 是具有固定长度的 ISAs 的主要例子。一般来说,英特尔凌动处理器和 ARM 架构处理器之间的交叉调试使用模式非常相似。许多概念性的调试方法和问题是相同的。

然而,在基于英特尔架构的开发主机上针对英特尔凌动处理器目标进行开发确实具有两大优势,尤其是当选择的嵌入式操作系统是 Linux 或 Windows 等常见标准操作系统的衍生产品时。第一个优势是丰富的性能、功耗分析和调试工具生态系统,可用于英特尔架构上更广泛的软件开发市场。第二个优点是调试应用的功能正确性和多线程行为可以在本地完成。这个优点将在本章后面讨论。

开发人员应该了解英特尔凌动处理器和 ARM 处理器之间的一些差异。接下来的两个小节总结了这些差异。

可变长度指令

IA-32 和 Intel 64 指令集具有可变的指令长度。调试器不能仅仅以固定的 32 位间隔检查代码,还必须根据这些指令的上下文解释和反汇编应用的机器指令。下一条指令的位置取决于前一条指令的位置、大小和正确解码。相比之下,在 ARM 架构上,调试器需要监控的只是从 ARM 模式切换到 Thumb 模式或增强 Thumb 模式的代码序列。一旦进入特定模式,所有指令和存储器地址的大小都是 32 位或 16 位。固件开发人员和设备驱动程序开发人员需要精确地将调用与特定的设备寄存器对齐,并且可能希望依赖于对调试器的内存窗口打印输出的理解,他们应该了解可变长度指令的潜在影响。

硬件中断

调试系统代码时可能相关的另一个架构差异是如何处理硬件中断。在 ARM 架构上,以下异常向量从地址 0x0 映射到地址 0x20:

  • 0.重置
  • 1.流产
  • 2.数据中止
  • 3.预取中止
  • 4.未定义指令
  • 5.中断(IRQ)
  • 6.快速中断(FIRQ)

这个内存区域受到保护,通常不能被重新映射。通常,0x0 到 0x20 的所有向量位置都包含到实际异常处理程序代码所在的内存地址的跳转。对于复位向量,这意味着在 0x0 将跳转到固件或平台引导代码的位置。这种方法使得硬件中断和 OS 信号处理程序的实现在 ARM 架构上不太灵活,但也更加标准化。只需在 0x0 至 0x20 地址范围内的向量位置设置一个硬件断点,就可以很容易地在调试器中捕获中断。

在英特尔架构上,采用了专用的硬件中断控制器。以下中断不能通过处理器内存地址空间直接访问,而是通过访问英特尔 8259 中断控制器来处理:

  • 0.系统记时器
  • 1.键盘
  • 2.级联第二中断控制器
  • 3.com 2—串行接口
  • 4.COM1—串行接口
  • 5.LPT—并行接口
  • 6.软盘控制器
  • 7.有空的
  • 8.CMOS 实时时钟
  • 9.声卡
  • 10.网络适配器
  • 11.有空的
  • 12.有空的
  • 13.数字处理器
  • 14.IDE—硬盘接口
  • 15.IDE—硬盘接口

正如您所看到的中断列表,控制器已经允许直接处理附加设备的硬件 I/O 中断,这些中断是通过 ARM 平台上的 IRQ 中断或快速中断来处理的。这一特性使得在英特尔架构上实现操作系统级的正确中断处理变得更加容易,尤其是对于设备 I/O。软件异常(如数据中止或分段故障)的映射在英特尔架构上也更加灵活,并且对应于通过中断描述符表(IDT)寻址的中断控制器端口。IDT 到硬件中断的映射可由软件栈定义。此外,从软件栈无关的调试实现中捕获这些异常并不容易。为了在英特尔架构上捕获触发硬件中断的软件事件,需要一些操作系统层的知识。有必要了解这些异常的操作系统信号如何映射到底层中断控制器。最常见的是,即使在系统级调试器中,来自操作系统的存储器映射信号表将捕获异常,而不是试图直接在硬件级捕获异常。

单步执行

ARM 架构没有明确的单步指令。在英特尔架构上,汇编级单步通常直接通过这样的指令在调试器中实现。在 ARM 上,单个指令步骤被实现为“运行直到中断”命令。调试器需要做一些代码检查,以确保所有可能的代码路径都被覆盖(特别是当它从分支指令或类似指令中脱离时)。从调试器实现的角度来看,这确实会产生轻微的开销,但不会过多,因为这种“运行到中断”的实现无论如何都是高级语言步进经常需要的。一般来说,软件开发人员应该意识到这种差异,因为这会导致略微不同的步进行为。

虚拟内存映射

虚拟内存映射的描述符表和页面转换实现惊人地相似,至少在概念上是如此。在英特尔架构上,全局描述符表(GDT)和局部描述符表(LDT )支持将内存页面的嵌套粗糙度调整映射到虚拟地址空间。图 8-32 使用调试器的页面转换功能来图形化表示英特尔架构上的线性到物理地址转换。

9781430261308_Fig08-32.jpg

图 8-32 。英特尔架构页面翻译

在 ARM 上,第一级和第二级页表定义了更直接的、最多一级或两级深度的虚拟内存页搜索。图 8-33 显示了一个线性地址到物理地址转换的例子。

9781430261308_Fig08-33.jpg

图 8-33 。ARM 上的页面翻译

英特尔架构为描述符表、页表、实模式下的 32 位地址空间访问和保护模式下的 64 位寻址提供了多种粗糙程度,具体取决于选择器base:offset型号。ARM 在其各种模式下不使用base:offset。在英特尔架构上,页表搜索可以更深入。在 ARM 上,定义的集合是两个页表。在英特尔架构上,描述符表实际上可以屏蔽嵌套表,因此页表运行的真正深度很容易达到 ARM 上深度的两倍或三倍。

英特尔架构上的页面转换机制在系统内存布局和机制方面提供了更大的灵活性,操作系统层使用这些机制来分配特定的内存块作为应用执行的受保护块。然而,它确实增加了开发人员对内存虚拟化的全面了解,从而避免内存泄漏和内存访问违规(分段错误)的挑战。在一个拥有大量内存的全功能操作系统上,这个问题不太值得关注。对内存处理具有更多可见性的实时操作系统可能更容易受到此问题的影响。

英特尔超线程技术的注意事项

从调试的角度来看,物理处理器内核和通过英特尔超线程技术实现的逻辑内核之间实际上没有什么区别。启用超线程是 BIOS 中平台初始化过程的一部分。因此,从应用的角度来看,真正的物理处理器内核和额外的逻辑处理器内核之间没有明显的区别。由于该技术支持多线程的并发执行,因此调试挑战类似于真正的多核调试。

SoC 与异构多核的交互

在 SOC 上交互的许多软件组件和硬件组件增加了调试期间发现根本原因问题所需的时间。不同软件组件之间的交互通常是时间敏感的。当试图调试组件间有许多交互的代码库时,单步调试一个特定的组件通常不是一个可行的选择。传统的 printf 调试在这种情况下也是无效的,因为调试更改会对计时行为产生负面影响,并导致更糟糕的问题(也称为“Heisenbugs”)。

斯文(系统可见事件关系)

SVEN 是一种软件技术(和 API ),它收集实时的、全系统可见的软件“事件跟踪”SVEN 目前内置于所有媒体/显示驱动程序中,是英特尔媒体处理器 CE3100 和英特尔凌动处理器 CE4100 平台的主要调试工具。SVEN 提供了调试、性能测量和回归测试功能。

最终,SVEN 只是一个带有高分辨率时间戳的软件事件列表。SVEN API 为开发人员提供了一种从任何操作系统上下文和固件传输事件的方法。SVEN 调试基础设施由一个小型快速的“事件传输”(SVEN-TX)库和一个详细的捕获和分析(SVEN-RX)功能组成。

SVEN-TX 库形式的这种所谓的系统可见事件关系提供了一种具有低的确定性开销的工具 API。它不会引起任何额外的时序相关效应。由于仪器观察,系统的行为没有变化。换句话说,不存在软件海森堡效应。要监控的事件可以由整个平台上的任何软件组件发出。这些可以是中断服务程序(ISR),驱动程序,应用,甚至固件。

一个名为 SVEN-RX 的实时监控界面对 SVEN-TX API 导出的数据进行实时和离线分析。SVEN-RX 可以监视正在执行的系统,并分析正在执行的应用的故障。此外,它还提供了细粒度性能调优的详细信息。

最后,SVEN Debug console 是一个命令行工具,它连接到 Nexus 并观察由 SVEN-TX 检测代码(驱动程序、用户应用和库)生成的所有事件。可脚本化的过滤器动态地接受或拒绝任何可描述的事件类别(例如,仅记录来自 MPEG 解码器的事件)。可编写脚本的“触发器”停止记录事件,以暂停导致故障的事件的本地捕获。反向工程功能将所有寄存器读/写从物理地址转移到单元和外部架构规范(EAS)寄存器。

SVEN 调试控制台可以将从 SoC 收集的记录事件保存到磁盘文件中,以便进行离线调试。

信号编码/解码调试

SVEN 调试控制台有一个内置的流媒体解码器(SMD)缓冲区流监视器,用于检查 SMD 端口/队列中驱动程序之间的数据流。它还对一段时间内的 SMD 循环缓冲区利用率进行采样。它的健康监控器能够触发执行停止和数据捕获,例如,如果它未能在指定的时间段内检测到视频翻转或音频解码。

斯文福利

SVEN 支持加速平台调试过程,为开发人员提供问题分类所需的所有证据。包含的自动化工具可以自动诊断大多数常见的系统故障。简而言之,它缩短了开发人员理解所有系统组件之间的数据交换和握手过程中出现的问题的时间,从而加快了基于英特尔凌动处理器的复杂 SoC 设计的开发周期。

概观

本章讲述了必要的驱动程序和调试工具的配置和安装细节。此外,我们强调了一些可能影响调试的底层架构差异,但通常只针对那些对非常接近系统层的开发感兴趣的开发人员。

正如您在可用调试解决方案和调试配置概述中看到的,有一整套调试环境可以满足 Java 应用开发人员、本机 C/C++ 代码开发人员以及系统软件堆栈开发人员的需求。

谷歌提供的标准 Android SDK 和 Android NDK 工具集支持在英特尔架构上进行调试。此外,英特尔和其他生态系统参与者还提供调试解决方案,这些解决方案扩展了这些可用的调试工具,并提供系统软件堆栈调试和图形性能调试的解决方案。

如果您熟悉运行在 ARM 架构上的 Android 的调试和开发,同样的调试方法也适用于英特尔架构。可用的调试工具和开发工具基础架构基于 Android SDK,并由英特尔以及生态系统合作伙伴(通常也熟悉 ARM)的解决方案进行了扩展。因此,与基于 ARM 的设备相比,在基于英特尔凌动处理器的 Android 设备上调试软件应该不会有什么意外。

九、x86 平台上 Android 应用的性能优化

就像生活中的许多其他事情一样,你很少只得到你优化过的东西。

-埃里克大帝

性能优化是每个应用开发人员一直想要追求的重要目标之一,无论该应用是用于一般的桌面 Windows 计算机还是 Android 设备。Android 是一个资源有限的系统,因此在空间和处理时间上需要非常严格的资源利用。因此,与桌面系统相比,Android 应用的性能优化更加重要。

不同的应用需要不同的优化重点。Android 系统的性能优化通常分为三类:

  • 优化应用的运行速度
  • 代码大小
  • 优化以降低功耗

一般来说,基于英特尔凌动处理器的 Android 系统的存储空间和成本并不是瓶颈,因此在本章中,我们将重点讨论如何通过性能优化来提高应用的运行速度。

我们将首先介绍 SOC 性能优化的基本原理。接下来,我们将介绍在英特尔架构上运行的 Android 应用的性能优化原则和方法。我们将讨论使用原生开发套件的 Android 应用开发,并分享使用特定工具(如英特尔图形性能分析器)的具体案例研究。

性能优化的基本概念

优化旨在减少根据规格准确完成特定任务所需的时间。这是通过基于硬件或软件优化的应用的结构调整或重构来实现的。

有几个与应用性能优化结果相关的基本原则需要遵循:

  • 等值原则:性能优化后,应用执行的结果没有变化。
  • 功效原理:性能优化后,目标代码运行速度更快。
  • 组合值原则:有时候,性能优化在某些方面获得了性能提升,但在另一些方面却降低了性能。在决定是否需要性能优化时,需要考虑综合的整体性能。

性能优化的一个重要考虑因素是用空间换取时间。例如,为了进行函数计算,可以预先计算函数值,并将其作为表格放入程序存储区(内存)。当程序运行时,为了减少执行时间,程序可以直接从表中获取值,而不是花费时间对函数进行重复计算来获取值。对于搜索操作,这可以使用散列方法在大空间上完成。

性能优化最常用的方法是减少指令和执行频率。比如从数据结构和算法的角度来看,冒泡排序中比较和交换的指令需要执行 O(n 2 次。然而,通过使用快速排序,指令时间减少到 O(n log n)次。在循环优化中,代码编译可以将无关的公共代码从循环中提取出来,并将公共代码的执行时间从 n 减少到 1,从而大幅降低执行频率。此外,C 和 C++ 支持的内嵌函数可用于更改函数调用、函数调用的指令以及返回指令的实现。

选择更快的指令

利用不同的指令可以实现相同的功能。不同的指令需要不同的机器时钟周期,因此执行时间也大不相同。这给了我们选择使用更快指令的机会。

降低计算强度是通过选择更快的指令集来实现性能优化的一个典型例子。例如,要将一个整数乘以 4,运算可以通过将运算符左移两位来完成。移位指令占用的时钟周期比乘法和除法指令少得多,运行速度也快得多。

这类优化的另一个例子是使用硬件提供的特殊指令来代替通用指令,以实现更快的指令执行。例如,英特尔凌动处理器提供流 SIMD 扩展(SSE) 指令集支持。对于向量运算,SSE 指令应始终用于完成运算,因为它们运行得更快,这得益于指令级并行处理。英特尔凌动处理器的普通加法指令宽度为 32 位,而 SSE 指令能够处理四倍于 32 位的数据。因此,使用 SSE 指令的优化代码大大缩短了耗时。

提高并行度

并行度可以在多个层面提高,包括指令、表达式、函数、线程。

包括英特尔凌动处理器在内的许多现代嵌入式处理器都支持指令流水线执行。这实现了一种称为指令级并行的优化方法。代码链可以分解为几个不依赖于链的代码单元,并且可以在流水线中并行执行。

此外,许多嵌入式系统处理器,如英特尔凌动处理器,在物理上支持线程的并发执行。使用适当数量的并发线程而不是单线程可以提高运行速度。为了利用线程并发优化的优势,程序员需要有意识地采用多线程技术;有时优化需要在编译器的支持下完成。

寄存器缓存的有效使用

进出高速缓存寄存器的读写比进出内存的读写要快得多。缓存优化的目标是尝试将正在使用和经常使用的数据和指令放入缓存,以提高缓存命中率并减少缓存冲突。缓存优化经常出现在嵌套循环的优化过程中。寄存器优化包括有效地使用寄存器,并尽可能多地将频繁使用的数据保存在寄存器中。

缓存基于局部性。也就是说,高速缓存假定要使用的数据位于已经在使用的最新数据中,或者在它们自己的寄存器附近。这被称为局域性原理局域性原理,它深深地影响着硬件、软件和系统的设计和性能。处理器所需的指令和数据总是首先被高速缓存访问读取。如果高速缓存有需要的数据,处理器总是直接访问高速缓存。在这种情况下,这种访问被称为高速缓存命中。如果高速缓存不包含所需的数据,这被称为失败命中或缓存未命中

如果发生这种情况,处理器需要将数据从内存复制到高速缓存中。如果高速缓存的相应位置已经被其他数据占用,缓存中不再需要的数据将需要被清除并写回内存。失败的命中将导致访问时间急剧增加;因此,提高缓存效率的目标是提高命中率和降低故障率。缓存和内存之间的数据交换是通过块单元执行的,块单元用于将所需的数据复制或回写块到内存中。

局部性描述了将被访问或引用的数据位置收集到可预测的群集中的方式,以便于处理器将来引用。有两种重要的局部性情况,简要解释如下:

  • 空间局部性 :当一个特定的数据对象被引用时,统计上很可能在不久的将来会引用附近数据。相邻数据的一部分被标记为单个块,以便处理器能够更快地访问该位置。
  • 时间局部性 :当一个特定的数据对象被引用时,在统计上也有可能在不久的将来再次引用相同的数据。被引用的数据被移动到高速缓存中,以便在新的位置可以更快地被引用。

性能优化的方法

性能优化有许多方法和技术。可以同时利用一个或多个综合优化原则,例如修改源代码以运行得更快。根据分类标准的类型,优化方法可以分为不同的类别。

依赖于机器的优化只能在特定的硬件或架构上进行。例如,将普通矢量指令计算切换到 SSE 指令的优化依赖于英特尔凌动处理器的许多底层细节,只能在支持 SSE 指令的英特尔处理器上使用。很难在具有不同架构的其他机器上使用这种优化,例如 ARM 或未指定的架构。一般来说,与机器无关的优化的复杂性高于与机器相关的优化,并且难以实现。

性能优化方法

在理想的情况下,编译器应该能够编译我们编写的任何代码,并将其优化为最有效的机器代码。但事实是,编译器实际上只能自动化所有可能的优化中的一部分,因为有些优化可能会被编译器的优化阻止程序默认阻止。一般来说,根据人工或自动化工具的作用大小,性能优化方法可以分为三大类:

  • 由编译器自动完成的性能优化
  • 借助开发工具完成的性能优化
  • 由程序员手动完成的性能优化

下面几节介绍了开发人员实现性能优化的几种途径和方法。

由编译器自动完成的性能优化

现代编译器自动完成最常见的代码优化。由编译器完成的这些自动优化也称为编译器优化或编译优化。编译器优化需要由适当的扩展选项或开关变量来触发。

Android 应用的 C/C++ 代码优化可以通过位于本机开发工具包中的 GNU 编译器集合工具来实现。我们将在下一章详细讨论这个话题。

由开发工具 辅助的性能优化

要实现一个大型程序的整体综合优化是非常困难的。幸运的是,对于基于英特尔架构的应用,有许多有用的工具可以帮助用户完成优化。例如,英特尔 Profiler、图形性能分析器(GPA) 、电源监控工具和其他工具可以帮助用户分析程序,并指导他们完成完整的优化。

VTune 和 GPA 是英特尔产品开发工具的一部分,只能用于英特尔处理器,如英特尔凌动处理器。英特尔档案器是一个 GNU 链工具,可用于所有类型的处理器。它可以用来创建一个分析过程,显示程序的哪些区域执行频繁并使用更多的计算资源,哪些区域实现不频繁。分析数据为开发人员完成优化提供了有价值的信息。

profile-guided optimization(PGO)的一个典型例子就是 switch 语句的优化(比如 C#的 switch-case 语句)。在本例中,根据收集的样本运行的概要,在获得每个 case 语句的实际出现频率后,case 语句在 switch 语句中按频率顺序排序。最频繁出现的语句被移到前面(这些语句的执行需要最少的比较次数),以便用最少的比较次数获得最佳结果。在 GNU 术语中,这个过程被称为概要引导优化(PGO)。

与 GNU 评测器一样,英特尔 VTune 能够定位程序中的热点。热点区域指的是执行时间长的程序代码段(这意味着计算能力更强)。借助 VTune,程序员可以找到耗时的代码段,然后采取措施优化这些代码。与 GNU 档案器相比,VTune 具有更高的分辨率(精细粒度)和更多定位热点的功能,包括显示程序的汇编代码、失败的缓存命中以及英特尔处理器的分支预测失误事件。

英特尔 GPA 最初是一种用于图形处理单元(GPU)分析的开发工具。它现在已经发展成为一个用于 CPU 速度分析、定制和设备功耗分析的综合工具。英特尔 GPA 可用于获取 CPU 负载、工作频率和功耗信息。它可以指导用户优化应用,尤其是多线程优化。英特尔 GPA 不仅是一个速度优化工具,也是一个非常方便的功耗优化工具。

有了优化工具,开发人员不再需要在试图寻找大型程序优化的起点时迷失方向和感到困惑。这些工具允许您轻松定位最需要优化的区域,也就是最有可能出现问题的代码段。快速找到热点可以让您用更少的时间和精力实现优化。当然,典型的性能优化是复杂的。工具只是起到指导和支持的作用——真正的优化还是需要编译器或者开发者手动完成。

高性能库的使用

高性能库 是一组软件库,通常由硬件原始设备制造商(OEM)开发,提供一些常用的操作和服务。这些库代码基于处理器特性的组合进行了精心优化,并且具有比普通代码更高的计算速度。简而言之,高性能数据库是通过各种方法优化的库,这些方法利用了处理器的全部潜力。例如,英特尔集成性能基元(IPP)库已经基于面向处理器的 SSE 指令、超线程/多线程并行流水线执行和瀑布流程进行了优化。与未优化的代码相比,英特尔 IPP 库可以提高英特尔处理器的处理能力并节省功耗。对于一些重要的代码和算法,使用高性能库实际上是一种简单、实用的优化方法,并提供了“站在巨人的肩膀上”的好处英特尔 IPP 是英特尔的高性能库之一。它是英特尔处理器的函数库,包括英特尔凌动处理器和英特尔芯片组。英特尔 IPP 功能强大;它可用于数学计算、信号处理、多媒体、图像和图形处理、向量计算和其他领域。英特尔 IPP 采用 C/C++ 编程接口。

手动完成性能优化

在优化的各个阶段,人的因素不应该被忽略。一些高级全局优化,如算法和数据结构的优化,不能由编译器自动完成。优化必须由人工完成。作为程序员,为了写出高效的代码,有必要学习各种算法和优化技术,养成良好的编程习惯和风格。即使编译器可以自动完成优化,仍然需要程序员的额外帮助来编写以下级别的高效代码:

  • 源代码(即高级语言)级优化 :这种优化是由程序员在源代码级完成的。程序员使用高级语言源代码的表达式来修改和转换程序。
  • 汇编语言级优化 :有时使用高级语言并不足以达到最佳效果,程序员可能需要修改代码,使其降到汇编语言级。在一些关键的计算领域,尽管汇编级优化的过程很繁琐,但结果的性能优势是值得的。
  • 编译指令级优化 :这是一种由执行者通过添加和修改编译器指令来完成的优化,例如典型的编译器指令修改“pragma”和/或增加 OpenMP 中的并行度。

程序交互优化是编程艺术的真实反映,其完成程度符合“人机合一”的理想这种统一是本章的重点。相对而言,在汇编语言级别或指令级别的编译阶段执行的优化要求程序员在处理器架构、硬件、系统等方面拥有全面的专业知识。因此,对于基于英特尔架构的 Android 系统,我们建议在源代码级别进行性能优化。在下面的例子中,我们重点介绍 Android 多线程设计的性能优化。

毫无疑问,优化可以通过多种方式完成,这些方式相互关联,在结构上不可分割,但每种方式都有其独特的功能。整个过程如图图 9-1 所示。

9781430261308_Fig09-01.jpg

图 9-1。推荐用户优化

如图图 9-1 所示,编译器优化、手动优化和高性能库函数是捆绑在一起的。它们是优化的最后步骤。为了修改源代码,需要使用手动优化和高性能库。在开始这些优化之前,使用优化工具分析程序已被证明对开发人员非常有益,并且是一个必不可少的步骤。

使用英特尔 VTune 进行性能调整

对于 Linux 平台,概要分析是最重要的软件性能分析类型。概要分析通常包括用户在各种工具的帮助下分析和识别需要优化的源代码。分析运行在英特尔处理器上的软件性能的最佳工具是英特尔 VTune 系列产品。

英特尔 VTune 性能放大器 (通常称为 VTune 分析器或 VTune)是一款性能分析软件,是英特尔处理器的有用优化工具。凭借分析程序性能的能力,VTune 可以帮助并指导程序员和编译器优化各种应用。VTune 提供用户友好的图形用户界面,不需要重新编译应用。性能分析可以直接在可执行应用上进行。VTune 适用于从嵌入式系统到超级计算机的各种应用,具有跨平台能力,可以在 Android、Tizen、Windows、Linux、Mac OS 等平台上运行。

VTune 基于热点区域分析来指导和支持应用的性能优化。热点是需要过长执行时间的代码段。除了热点分析之外,用户还需要考虑是什么导致了热点以及如何解决它。对于高级用户,VTune 可用于跟踪关键函数调用,监控特殊的 CPU 事件(如缓存未命中),以及执行一系列其它高级功能。

英特尔不断为 VTune 性能分析器添加功能,并在 2012 年将其重命名为工具。该公司的前身是带有英特尔线程档案器的英特尔 VTune 性能分析器,现更名为英特尔 VTune Amplifier XE。更新后的工具包括英特尔并行放大器的所有特性和功能,还为需要深入研究问题的程序员提供了一些高级特性。其主要功能将在以下章节中介绍。

如图 9-2 所示,VTune 性能放大器列出程序的整个运行时间,以及最耗时的前五个函数的信息(当程序的函数少于五个时,它只列出实际的函数号),例如前五个函数所用的运行时间占整个程序总运行时间的百分比。

9781430261308_Fig09-02.jpg

图 9-2。 VTune 运行时间与统计

VTune 提供运行时统计数据,具体到指向单个代码行的指针,可以帮助用户定位哪些代码段最耗时。如图图 9-3 所示,VTune 显示程序源代码语句的运行时间(滴答、时钟滴答)占总消耗时间和指令完成时间(失效指令)的百分比。

9781430261308_Fig09-03.jpg

图 9-3 。VTune 显示最耗时的源代码行

VTune 在调用图中以直观的方式显示程序函数调用关系,如图图 9-4 所示,其中函数调用以图形形式显示。在显示中高亮显示,从根到当前函数的函数调用关系称为关键路径 (也称为焦点函数 )。

9781430261308_Fig09-04.jpg

图 9-4 。函数调用关系的 VTune 调用图

VTune 可以直接显示在与目标文件对应的源代码中的汇编代码中。图 9-5 显示了程序功能对应的汇编代码。有了这个特性,用户可以进行编译分析,定位耗时的代码,并执行汇编级优化。

9781430261308_Fig09-05.jpg

图 9-5 。VTune 中的汇编代码与源代码

VTune 提供图形界面,显示时间及其它处理器事件的统计数据,包括缓存未命中、分支预测失误及其它信息。图 9-6 显示了对应于应用四个线程的处理器事件的统计数据。四个线程中的每一个都包括三个条形:顶部条形,表示执行指令时发生的时钟信号数的平均消耗(每条失效指令的时钟信号数);中间的条形显示消耗的最大时钟周期数(时钟信号);底部条表示已经完成的指令(失效的指令)。

9781430261308_Fig09-06.jpg

图 9-6 。其它处理器事件的 VTune 显示

VTune 可以显示应用的 CPU 使用情况,以及应用并发程度的分布。信息可以用来分析并行度。图 9-7 是使用英特尔凌动 N270 处理器的设备上一个应用的 1000HC 截图。

9781430261308_Fig09-07.jpg

图 9-7 。线程和 CPU 使用分布

英特尔凌动 N270 是一款单核处理器,但它支持英特尔超线程技术。应用使用两个线程来计算图 9-7 中所示力矩的平行度。CPU 利用率(CPU 时间)可以高达 200%。

通过比较前面介绍的 VTune 特性,我们可以按照调整的使用方式(优化分析)对这些特性进行分类。总之,VTune 函数调整可以分为三个级别,下面几节将对此进行介绍。

系统调整

系统调谐是 VTune Amplifier XE 的设计目的,但不是应用优化的主要目标。系统调优的目的是更改内存页面上的硬件配置,如网络带宽、每秒磁盘读/写操作数和命中故障。系统调优和优化主要是针对原设备厂商的产品。然而,作为一种高级工具,VTune 还可以用于实现某些辅助优化,例如锁定和等待分析。在这里,VTune 可以测量网络套接字对象完成磁盘写 I/O 的等待时间,这可以配置为减少线程到块的等待时间。另一个用途是运行热点分析并查找热点。如果结果适用,您可以通过手动添加线程化任务来并行化您的代码,或者您可以使用编译器通过英特尔线程构建模块(TBB)自动并行化代码。当然,您也可以通过检查函数调用关系和删除重复计算来调整或调优算法,以减少函数调用的数量。

在另一个场景中,程序已经并行化,您需要 VTune 运行并行分析 来确定程序的并行度,并运行热点分析来查找热点以及热点处的并行度。当热点处的并行度较低时(例如在线程不与其他线程并行的情况下,函数消耗大部分 CPU 时间),两个可能的原因是每个线程的任务分配不均匀,这可以通过调整算法来优化,以及线程阻塞 (等待时间),这是由共享资源或另一个线程持有的“锁”引起的。您可以使用 VTune 进行锁定,并等待分析找出阻塞的原因。在这种情况下,您需要确定是否需要在其他线程中占有共享资源,以及是否优化了时间。解决方案是尽可能地减少热区以减少数据依赖性,或者使用更“轻量级”的锁

基于处理器微体系结构的调整

当调整基于处理器的微体系结构时,开发人员通常需要对处理器有详细的了解。这通常会给应用开发人员带来困难,但当在热点分析中发现太多热点(或调用太多热点函数)时,特定于处理器的调整是必要的。在前面描述的两个不同级别的调整中,算法调整对应用优化最有用。

VTune 的两种模式

当 VTune 处于本地模式时,被测试程序的分析和 VTune 本身在同一台机器上工作。在远程模式下,应用的测试在一台机器上进行,VTune 安装在另一台机器上。对于台式机,VTune 一般用在本地模式,相对简单。Android 系统通常用于远程模式。在这里,VTune 安装在 Ubuntu/Win/MAC 开发系统上,大多数开发和 SDK 都位于该系统中,Android 设备通过 USB 网线或 Wi-Fi 网络连接到开发系统。

英特尔为非商业用途提供 VTune 的免费试用版,通常持续一年。还为学生和学术机构提供特殊折扣价格。读者可以去英特尔官方网站(http://www.intel.com)下载 Linux 版本的 VTune Amplifier XE。在下载之前,英特尔会让您填写电子邮件信息,序列号会发送给您。以下内容摘自英特尔 VTune Amplifier XE 2011 下载。

您已经注册了英特尔 VTune Amplifier XE Linux*版(以前称为 VTune 性能分析器 Linux *版)。您将收到一封电子邮件,其中包含下面列出的序列号以及供您将来参考的下载链接:

... ...

Serial Number:XXXX-XXXXXXXX

File to download: Install Package for IA-32 and Intel 64 (143 MB)

... ...

Intel VTune Amplifier XE for Linux*

Download:

Install Package for IA-32 and Intel 64

......

当读者前往下载链接时,序列号将显示为 XXXX-XXXXXXXX。将名为 vtune _ amplifier _ xe _ 2011 _ update 4 . tar . gz 的软件包(文件大小约为 150 MB)下载到本地驱动器并安装。

请注意,要在 Android 设备上安装和使用 VTune,您需要相应的驱动程序支持。需要使用 Android 课程代码编译和生成驱动程序。典型的用户很少能接触到或者熟悉 Android 设备的源代码。驱动准备一般是 Android 设备 OEM 生产的。大多数厂商都不愿意给商业级设备(比如手机)安装相关驱动,甚至连 root 权限都不开放。对于非 OEM 用户来说,在 Android 设备上本地使用 VTune 仍然是一个挑战,因为有些系统级配置只有 Android 系统工程师才能处理。然而,英特尔正在加紧开发和改进 VTune 在 Android 上的使用,估计在不久的将来,应用开发人员、学生和学术界可以访问强大的 VTune 工具,并在 Android 设备上本地使用它。

英特尔图形性能分析器

英特尔图形性能分析器(英特尔 GPA) 是一套优化和分析工具,仅用于支持基于英特尔酷睿和英特尔凌动处理器的硬件平台的英特尔处理器。英特尔 GPA 为 CPU/GPU 速度分析和定制功能提供了图形用户界面。它使开发人员能够发现性能瓶颈,并针对基于英特尔芯片组平台的设备优化应用。英特尔 GPA 由系统分析器、帧分析器和软件开发套件(SDK)组成。

介绍

英特尔 GPA 支持运行在英特尔凌动处理器上的所有 Android 设备。该套件提供以下功能:

  • 实时显示几十个关键指标,包括 CPU、GPU 和 OpenGL ES API
  • 几个图形管道测试,即时提供以隔离图形瓶颈
  • 与作为主机开发系统的 Microsoft Windows 或 Ubuntu OS 兼容

英特尔 GPA 目前仅支持使用英特尔凌动处理器的真实 Android 设备,不支持对 Android 虚拟设备(AVD)的分析。面向 Android 的英特尔 GPA 使用 Android 应用交叉开发中常用的典型硬件部署模型,其中主机系统(Windows 或 Ubuntu)和目标设备(Android 基于英特尔的设备)通过 USB 连接进行连接,以监控 Android 应用。Android GPA 在 Android Debug Bridge utility (adb,此处绑定到本地服务器)上运行,以实现对目标 Android 设备上的应用的监控。adb(服务器)运行在 Android 设备上,GPA 作为 adb 客户端应用运行在主机系统上,实现目标机监控。GPA 配置如图图 9-8 所示。

9781430261308_Fig09-08.jpg

图 9-8 。英特尔 GPA 配置,用于监控 Android 设备上的应用

鉴于英特尔 GPA 是基于 adb 来工作的,开发人员应该谨慎。由于 Eclipse 和 DDMS 也使用 adb,如果 GPA、DDMS 和 Eclipse 由于服务器冲突同时运行,英特尔 GPA 可能无法正常工作。使用英特尔 GPA 时,最好关闭其他 Android 软件开发工具,如 Eclipse 或 DDMS。图 9-9 显示了在 Android 设备上监控运行的应用过程中的英特尔 GPA 图形界面。

9781430261308_Fig09-09.jpg

图 9-9 。GPA 图形界面监控 Android 设备上运行的应用

如联想 K800 智能手机的截图图 9-9 所示,GPA 界面显示两个框架和一个工具栏窗格。指标工具栏显示被测量的指标,这些指标以树形结构组织,如下所示:

  • 在 CPU 指标下是聚合的 CPU 负载、CPU XX 负载、CPU XX 频率、目标应用 CPU 负载等。CPU XX 号由英特尔 GPA 监控的 CPU 数量决定。要获得 CPU 信息,如内核数量、型号和频率,我们可以在终端窗口使用 cat/proc/cpuinfo 命令。图中的联想 K800 智能手机采用了单核英特尔凌动 Z2460 处理器。该图显示了支持英特尔超线程技术(HTT)的两个逻辑处理器。因此,CPU 负载和 CPU 频率中显示的两个项目被索引为 00 和 01。在 CPU XX Load 中,XX 是 CPU 编号:它显示 CPU XX 的负载状态,而 CPU XX Frequency 显示 CPU XX 的频率状态。汇总的 CPU 负载是 CPU 的总负载。目标应用 CPU 负载是目标设备上应用的 CPU 负载。
  • 在设备 IO 指标下是磁盘读取、磁盘写入、网络接收和网络发送项目,它们分别列出了磁盘读取、磁盘写入以及通过网络发送和接收的数据包的状态和信息。
  • 内存指标下是应用驻留内存、可用内存等。
  • 在功率度量下是电流充电和电流放电,它们提供充电和放电的状态。

默认情况下,右窗格显示两个实时状态显示窗口。这些窗口显示指定指示器的类似示波器的状态。横轴表示经过的时间,纵轴表示相应指示器的值。用户可以将索引条目从左窗格拖放到两个窗口之一,该条目的实时指示器将显示在窗口中。在图 9-9 中,CPU 00 负载被拖放到顶部显示窗口,CPU 01 负载被拖放到底部显示窗口,垂直轴显示 CPU 利用率。最大利用率为 100%。

在实时状态显示窗口的上方是工具栏,提供截图和暂停显示等工具。用户可以使用这些工具来实现一些辅助功能。

安装

托管英特尔 GPA 可以在 Windows 或 Ubuntu 中完成。以下部分给出了适用于 Windows 平台的英特尔 GPA 安装流程示例。用户可以参考英特尔 GPA 发行说明或相关的英特尔 GPA 网站,了解在 Ubuntu 平台上的安装过程和使用方法。

英特尔 GPA 要求安装 4.0 版或更高版本的。NET Framework 安装为在 Windows 平台上运行。在 Windows 平台上安装英特尔 GPA 包括两个主要步骤:第一步是安装。Net 框架,第二步是真正的英特尔 GPA 安装。下面是在 Windows 主机平台上安装英特尔 GPA 的详细分步说明。

  1. Install .Net Framework.

    。Net Framework v4.0 版用于这些指令。确保您的计算机上安装了最新的 Windows service pack 和关键更新。如果平台是 64 位版本的 XP 或 Windows 2003,您可能需要安装 Windows 映像组件。要安装它,请访问http://www.microsoft.com/en-us/download/details.aspx?id=17851并下载。Net 框架 4.0。

    接下来双击 dotNetFx40_Full_setup.exe,出现图 9-10(a) 所示的弹出窗口界面,开始安装。按照逐步安装提示完成安装。

    9781430261308_Fig09-10a.jpg

    9781430261308_Fig09-10b.jpg

    图 9-10 。Net Framework 4.0 安装界面

  2. Install Intel GPA

    安装先决条件如下:确保您的计算机上安装了最新的 Windows service pack 和关键更新。用户可以直接去英特尔官方网站下载英特尔 GPA(这里测试用的是 GPA _ 12.5 _ release _ 187105 _ windows . exe 版本):http://intel.com/software/gpa或者http://software.intel.com/en-us/vcsource/tools/intel-gpa ,如图图 9-11 。双击 GPA _ 12.5 _ release _ 187105 _ windows . exe 运行程序。这是一个自解压文件。未压缩的文件保存在原始文件所在的文件夹中。双击解压缩文件夹中的 setup.exe 进行安装。

    9781430261308_Fig09-11.jpg

    图 9-11 。英特尔 GPA 软件下载网站

    双击 GPA _ 12.5 _ release _ 187105 _ windows . exe 运行程序,该程序为自解压文件;未压缩的文件保存在原始文件所在的文件夹中。

    接下来,双击解压后的文件夹中的 setup.exe,如上所示进行安装。

    如果。如果没有安装. Net Framework,将出现英特尔 GPA 必备设置 并提示用户必要的信息,如图图 9-12 所示。

    9781430261308_Fig09-12.jpg

    图 9-12 。英特尔 GPA 安装弹出窗口。找不到. Net Framework

    单击安装按钮。出现如图图 9-13 所示的对话框。

    9781430261308_Fig09-13.jpg

    图 9-13 。安装对话框

    一旦安装了先决条件,您将返回到安装英特尔 GPA 的第一步。英特尔 GPA 的安装进度条如图 9-14 所示。

    9781430261308_Fig09-14.jpg

    图 9-14 。英特尔 GPA 安装的进度条

    一旦英特尔 GPA 加载完毕,如图 9-15(a) 所示,点击下一步继续。英特尔 GPA 将测试兼容性和硬件支持,并提醒您下一步,如图图 9-15(b) 所示。单击确认继续。

    9781430261308_Fig09-15a.jpg

    9781430261308_Fig09-15b.jpg

    图 9-15 。英特尔 GPA 安装的消息框

    图 9-16 显示了目标文件夹选择框(在这个例子中,你会选择 D:\GPA\2012 R5)。

    9781430261308_Fig09-16.jpg

    图 9-16 。英特尔 GPA 目标文件夹选择

    按照逐步说明完成安装。

英特尔 GPA 在 Android 上的使用示例

以下示例展示了如何使用英特尔 GPA 监控 Android 设备上的应用。在本例中,目标机器是运行英特尔凌动处理器的联想 K800 智能手机。

在英特尔 GPA 能够监控 Android 设备上的应用之前,必须使用 Eclipse 来设置特定的应用参数。然后,应用可以由 Eclipse 生成和部署、运行和监控。

我们在此使用的英特尔 GPA 监控示例应用是 MoveCircle。操作界面如图 9-17(a) 所示。

该应用是一个简单的拖动游戏。用户界面是一个简单的圆形。当用户触摸圆圈内的任何一点并拖动它时,一个黑色的圆圈将跟随触摸点并四处移动。当用户停止触摸圆圈中的点时,圆圈将变得静止。此时,当用户将圆拖动到圆外(即,该圆内的初始触摸点)时,圆不会移动。如果用户按下手机的后退键,就会弹出退出对话框。选择退出将允许您退出应用,如图图 9-17(b) 所示。

9781430261308_Fig09-17.jpg

图 9-17 。MoveCircle 应用

应用的主要计算任务发生在拖动圆的过程中。您需要不断计算圆的新位置并刷新(重画)显示。

以下是英特尔 GPA 监控示例应用的步骤:

  1. 在 Eclipse 中构建和部署将由英特尔 GPA 监控的应用。

  2. 使用常规过程创建应用项目。将应用命名为 MoveCircle。

  3. Write the related code for the project. The document framework is shown in Figure 9-18.

    9781430261308_Fig09-18.jpg

    图 9-18 。应用 MoveCircle 的文档框架

  4. Edit the AndroidManifest.xml file and add the following code:

    1.      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    2.          package="com.example.movecircle"
    3.          android:versionCode="1"
    4.          android:versionName="1.0" >
    5.
    6.          <uses-sdk
    7.              android:minSdkVersion="8"
    8.              android:targetSdkVersion="15" />
    9.              <uses-permission android:name="android.permission.INTERNET"/>
    10.
    11.         <application
    12.             android:icon="@drawable/ic_launcher"
    13.             android:debuggable="true"
    14.             android:label="@string/app_name"
    15.             android:theme="@style/AppTheme" >
    16.             <activity
    17.                 android:name=".MainActivity"
    18.                 android:label="@string/title_activity_main" >
    19.                 <intent-filter>
    20.                     <action
    21.     android:name="android.intent.action.MAIN" />
    
    22.                     <category android:name="android.intent.category.LAUNCHER" />
    23.                 </intent-filter>
    24.             </activity>
    25.         </application>
    26.
    27.     </manifest>
    

    在第 9 行,我们添加了 uses-permission 元素的描述 ,这是应用的同一级别,我们授予应用的 Internet 写/读访问权限。在第 13 行,我们指定应用是可调试的。

  5. 生成应用包并将应用部署到真正的目标设备。在开始下一步之前,一定要关闭 Eclipse。

  6. Start Intel GPA on the host machine to monitor the application

    1. 将 Android 手机连接到 PC。确保屏幕没有被锁定,否则您可能会得到错误的错误不成功的电话连接。
    2. 确保关闭所有使用亚行服务器的工具,如 Eclipse 和 DDMS。否则,您可能会得到错误不成功的电话连接。(可跳过此步骤)确保 adb 服务器已启动并正在运行(参见图 9-19 )。

    9781430261308_Fig09-19.jpg

    图 9-19 。亚行服务器显示我们的医疗设备

  7. 选择 Windows 菜单“开始\程序\英特尔图形性能分析器 2012 RS \英特尔 GPA 系统分析器”启动英特尔 GPA。

  8. The Intel GPA initial window then pops up, suggesting the machine that will be monitored, as shown in Figure 9-20. Since the tuning target is a phone in this case, select the phone (in this case the Medfield04749AFB) by clicking the Connect button to the right of name of the phone.

    9781430261308_Fig09-20.jpg

    图 9-20 。用于连接受监控设备的英特尔 GPA 接口

  9. Once you are connected, Intel GPA does an initial analysis of the applications installed on the monitored smartphone, dividing the apps into two groups: analyzable applications and nonanalyzable applications, as shown in Figure 9-21.

    9781430261308_Fig09-21.jpg

    图 9-21 。英特尔 GPA 连接到受监控的手机设备后的初始界面(应用列表)

    窗口顶部是可分析的应用类别,可通过英特尔 GPA 进行调整或调试。不可分析的应用列在底部面板中。在可分析的应用列表中,您可以看到我们用作英特尔 GPA 监控练习示例的 MoveCircle 应用。英特尔 GPA 无法分析应用的原因通常是因为其参数的设置方式与我们在本节课前面所述的方式不同。

  10. Click the name of the application that you want Intel GPA to monitor in the Analyzable applications window (in this case, MoveCircle). An icon of a rolling circle showing ongoing progress appears on the left side of the app (MoveCircle). See Figure 9-22.

![9781430261308_Fig09-22.jpg](https://gitee.com/OpenDocCN/vkdoc-android-pt2-zh/raw/master/docs/andr-x86/img/9781430261308_Fig09-22.jpg)

图 9-22 。英特尔 GPA 中的应用初始化接口

此时,手机上会显示应用启动屏幕。屏幕会提示您正在等待调试器,如图图 9-23 所示。请注意,您不应该单击强制关闭按钮,而是应该等到消息框在界面中自动关闭。

![9781430261308_Fig09-23.jpg](https://gitee.com/OpenDocCN/vkdoc-android-pt2-zh/raw/master/docs/andr-x86/img/9781430261308_Fig09-23.jpg)

图 9-23 。当英特尔 GPA 开始监控应用时,目标手机上出现初始消息
  1. Next, Intel GPA monitoring interface appears, as shown in Figure 9-24.
![9781430261308_Fig09-24.jpg](https://gitee.com/OpenDocCN/vkdoc-android-pt2-zh/raw/master/docs/andr-x86/img/9781430261308_Fig09-24.jpg)

图 9-24 。应用启动时,英特尔 GPA 上显示的初始监控界面

与此同时,MoveCircle 应用开始在手机上运行,如图图 9-25 所示。

![9781430261308_Fig09-25.jpg](https://gitee.com/OpenDocCN/vkdoc-android-pt2-zh/raw/master/docs/andr-x86/img/9781430261308_Fig09-25.jpg)

图 9-25 。显示在目标手机设备上的 MoveCircle 应用

将 CPU 00 负载拖放到显示窗口顶部的实时状态显示面板上,将 CPU 01 负载拖放到显示窗口底部的实时状态显示面板上。单击并拖动移动圆几秒钟,然后停止交互几秒钟。相应的英特尔 GPA 监控屏幕如图 9-26 所示。

![9781430261308_Fig09-26.jpg](https://gitee.com/OpenDocCN/vkdoc-android-pt2-zh/raw/master/docs/andr-x86/img/9781430261308_Fig09-26.jpg)

图 9-26 。英特尔 GPA 监控 MoveCircle 应用并实时显示 CPU 负载

在图 9-26 中,我们可以看到一个规律:当我们拖动圆圈时,两个 CPU 负载都会上升到一定高度;当我们不与应用交互时,两个 CPU 的负载将立即下降到接近 0%。应用的主要计算任务集中在拖动和移动圆上,当不移动圆时,没有或只有很低的 CPU 负载。

要结束英特尔 GPA 分析,请退出应用,如图 9-17(b) 所示。英特尔 GPA 将返回到图 9-20 所示的启动界面。

前面的示例只演示了对 CPU 负载的监控。感兴趣的读者可以尝试其他应用示例和其他监控指标。以 MoveCircle 应用为例,我们为顶部显示窗口选择了磁盘读取指标,为底部显示窗口选择了磁盘写入指标。然后我们交换了应用,查看了一些照片文件。当我们回到 MoveCircle app 时,磁盘读取的即时动作显示了磁盘读取活动的存在,如图图 9-27 所示。

![9781430261308_Fig09-27.jpg](https://gitee.com/OpenDocCN/vkdoc-android-pt2-zh/raw/master/docs/andr-x86/img/9781430261308_Fig09-27.jpg)

图 9-27 。英特尔 GPA 监控窗口显示 MoveCircle 应用和其他应用的磁盘读取

Android 多线程设计

英特尔凌动处理器支持超线程和多核配置。多线程设计是增加并行度和提高性能的好方法。英特尔凌动 N 系列处理器支持多线程的并行执行。虽然大多数用于手机和其他移动设备的英特尔凌动 Z 系列处理器都是单核处理器,但它们支持英特尔超线程技术。因此,Z 系列处理器构成了两个或四个逻辑 CPU,并在物理上支持一定程度的并行执行。

请注意,这里使用的词是平行的,而不是并发的。对于某些任务,我们可以遵循并行计算中称为“分而治之”的经典方法论,将它们分成两个或更多个基本单元。我们将这些单元分配给不同的线程同时执行。通过这种方式,处理器的性能潜力得到了充分利用,从而加快了执行速度,使软件运行得更快、更高效。

基于 Java 多线程编程接口,Android 扩展为 Android 开发者提供了更强大的多线程编程接口。借助这个编程接口,开发人员可以轻松实现 Java 语言级别的多线程开发和设计,而无需使用繁琐的底层操作系统 interface。

Android 框架还是一个线程

Android 线程编程框架 基于 Java。Java 中实现多线程编程有两种方法。第一种是从 Thread 类继承并重写 run 方法。第二是实现 Runnable 接口和 run 方法。

Java 线程编程接口

实现多线程编程的第一种方法(从线程继承)的一般代码框架如下:首先,定义线程类(在本例中,称为MyThread的线程类)及其代码,如图图 9-28 所示。

9781430261308_Fig09-28.jpg

图 9-28 。示例自定义线程类

然后,创建一个我们自定义的线程类的实例并启动它,如图图 9-29 所示。

9781430261308_Fig09-29.jpg

图 9-29 。开始线程

等待线程结束,如果出现问题就进行处理,如图图 9-30 所示。

9781430261308_Fig09-30.jpg

图 9-30 。等待线程完成

用 Java 实现多线程编程的第二种方法是使用 Runnable 接口实现。以下是 Runnable 的通用代码框架。第一步是创建一个自定义的可运行接口,如图图 9-31 所示。

9781430261308_Fig09-31.jpg

图 9-31 。自定义可运行

接下来,您需要实际启动线程并给它一个 Runnable。如图图 9-32 所示。

9781430261308_Fig09-32.jpg

图 9-32 。开始线程

这两种方法效果相同,只是使用的场合不同。熟悉 Java 的开发者都知道,它没有多重继承,所以用接口实现代替。要单独实现一个线程,可以使用第一种方法,线程继承。

但是有些类本身是从另一个类继承而来的。在这种情况下,如果希望线程运行,就必须使用第二种方法(Runnable 接口方法)来实现线程实现。在这个方法中,您可以声明该类实现 Runnable 接口,然后将作为线程运行的代码放入 run 函数中。这样,它就不会影响它以前的继承层次,也可以作为一个线程来运行。

关于 Java 的线程框架,请注意以下几点:

  1. 在 Java 运行时中,系统为线程执行实现了一个线程调度器,用于确定线程在 CPU 上运行的时间。
  2. 在 Java 技术中,线程通常是抢占式的,不需要时间片分配过程(给每个线程分配相等的 CPU 时间)。在抢占式调度模型中,所有线程都处于准备运行状态(等待状态),但实际上只有一个线程在运行。该线程继续运行,直到它终止或者一个更高优先级的线程变得可运行。在这种情况下,低优先级线程终止,并将运行权交给高优先级线程。
  3. Java 线程调度器支持具有不同优先级的线程的这种抢占式方案,但是调度器本身不支持具有相同优先级的线程的时间片轮换。
  4. 如果运行 Java 运行时的操作系统支持时间片的旋转,那么 Java 线程调度器支持相同优先级线程的时间片旋转。
  5. 不应该过分依赖系统的线程调度程序。例如:低优先级线程也必须获得运行的机会。

关于 Android 应用的 Java 多线程编程方法的更多详细信息,开发者可以参考相关的 Java 编程书籍。

线程编程扩展和支持

Android 运行时,Dalvik 虚拟机支持多个并发 CPU。也就是说,如果机器有不止一个逻辑处理器,Dalvik 虚拟机将遵循某些策略,自动分配不同的线程在不同的 CPU 上运行。这样,Android 可以在物理上并行运行不同的线程。除了 Java 提供的线程编程接口,Android 还提供了重要的扩展和支持。第一个是 Looper-Message 机制。

Android 的界面,包括各种各样的活动,都是在应用的主线程(也称为 UI 线程、接口线程或默认线程)中运行的。默认情况下,应用只有一个线程,即主线程。因此,应用被认为是单线程的。一些耗时的任务(计算),如果默认运行在主线程上,会导致主界面的交互长时间没有响应。为了防止主界面交互长时间处于停滞状态,应该将那些耗时的任务分配给独立线程来执行。

在后台运行的独立线程(也称为辅助线程或后台线程)经常需要与主线程的接口进行通信,例如通过更新显示接口。如果后台线程调用一个接口对象的函数来更新接口,Android 会给出执行错误消息 CalledFromWrongThreadException。

比如在一个应用中(这里是 GuiExam),如果一个 worker 线程直接调用界面中 TextView 对象的 setText 函数来更新显示,系统会立即遇到错误,终止正在运行的应用,如图图 9-33 所示。

9781430261308_Fig09-33.jpg

图 9-33 。工作线程直接调用 UI 对象的函数时运行错误消息

为了使工作线程和主线程接口能够很好地通信,您需要理解循环消息机制。为了解决这样的问题,Android 有一个叫做消息队列的机制,其中线程可以由消息队列、处理处理器和 Looper 组件组合起来交换信息。

消息

Java 类消息定义了线程之间交换的信息。当幕后的线程需要更新接口时,它会向 UI 线程(主线程)发送包含数据的消息。

处理者

Handler 类是 Message 类的主处理器,负责发送消息以及消息内容的执行和处理。后台线程利用传入的处理对象,调用 sendMessage 函数发送消息。要使用 Handler 类,您需要一个方法来实现 handleMessage 类,该类负责处理消息操作内容,如更新接口。handleMessage 方法通常需要子类化。

Handler 类本身并不用于打开一个新线程。处理程序更像是主线程的秘书,一个触发器,负责管理来自子线程的更新数据,然后更新主线程中的接口。后台线程处理 sendMessage()方法发送消息,处理程序将回调(自动调用)handleMessage 方法中的处理来处理消息。

消息队列

消息队列用于根据先入先出执行规则存储处理程序发送的消息。对于每个消息队列,都有一个相应的处理程序。该处理程序使用两种方法向消息队列发送消息:sendMessage 或 post。根据先入先出规则,这两种类型的消息将被插入到消息队列的末尾。这两种方法发送的消息以稍微不同的方式执行:sendMessage 发送的消息是一个消息队列对象,将由处理程序的 handleMessage 函数处理,而通过 post 方法发送的消息是一个 runnable 对象,将自动实现。

Android 没有全局消息队列,自动为主线程(UI 线程之一)建立消息队列,但是子线程中还没有建立消息队列,所以必须调用 Looper.getMainLooper()来获取主线程的 Looper。主线程不会循环到 NULL,但是调用 Looper.myLooper()来获取 Looper 的当前线程的循环,就有可能为 NULL。

尺蠖

Looper 类是每个线程的消息队列的管家。循环是处理程序和消息队列之间的桥梁。程序组件首先通过处理程序将消息传递给循环程序,然后循环程序将消息放入队列。

对于应用默认 UI 的主线程,系统已经建立了消息队列和 looper,不需要在源代码中写消息队列和 Looper 操作代码。尽管如此,两者对于默认的主线程都是“透明”的。然而,该处理程序对于默认的主线程是不透明的。为了向主线程发送消息并处理它们,用户必须建立自己的处理程序对象。

异步任务

除了使用 Looper-Message 机制来实现工作线程和 GUI 主线程之间的通信,还可以使用一种叫做异步任务 (AsyncTask) 机制的技术来实现通信。使用 AsyncTask 框架的一般过程描述如下:

  1. Implement the AsyncTask according one or several of the following methods:

    • onPreExecute():在执行之前开始准备工作。
    • doInBackground(参数...):开始后台执行,在这个里面调用 publishProgress 方法更新实时任务进度。
    • onProgressUpdate(进度...):publishProgress 方法被调用后,UI 线程会调用该方法来显示任务界面的进度;例如,通过显示进度条。
    • onPostExecute(Result):操作执行完成后,将结果发送给 UI 线程。

    在这四个函数中,没有一个可以手动调用。除了 doInBackground(参数...)方法,其他三个都是 UI 线程调用的,导致以下需求:

    • 必须在 UI 线程中创建 AsyncTask 实例。
    • 必须在 UI 线程中调用 AsyncTask.execute 函数。

请记住,该任务只能执行一次。多次调用将导致不一致和不确定的结果。

线程示例

运行界面应用 GuiExam 如图 9-34 中所示。这里我们用一个例子来说明 Android 线程化编程的使用。

9781430261308_Fig09-34.jpg

图 9-34 。多线程代码框架的演示用户界面

如图图 9-34 所示,演示 app 有三个主要的活动按钮:开始线程运行、停止线程运行、退出 App;前两个按钮用于控制辅助线程的操作。点击开始线程运行按钮,线程开始运行,如图图 9-34(b) 所示。点击停止线程运行,线程运行结束,如图图 9-34(c) 所示。工作线程每隔一段时间(在本例中为 0.5 s)刷新输出文本显示 TextView,在屏幕上显示“Done Step。x 从 0 递增到 x。单击“退出”关闭活动并退出应用演示。”

演示应用的结构和程序如下:

  1. Edit the main activity file (in this example: activity_main.xml), delete the originalTextView window component, and then add three buttons and two TextView window components. The properties of Button’s ID are respectively: @+id/startTaskThread,@+id/stopTaskThread,@+id/exitApp. The TextView property is respectively startTaskThread, exitApp, taskThreadOutputInfo, and stopTaskThread. There is one ID of TextView for which the property is set as @+id/taskThreadOuputInfo to display the text output of the worker thread. The TextView Outline for the Demo App is shown in Figure 9-35.

    9781430261308_Fig09-35.jpg

    图 9-35 。演示应用 activity_main.xml 中的多线程代码框架

  2. Edit the source code MainActivity.java of activity_main class. The content is listed below:

    1.      package com.example.guiexam;
    2.      import android.os.Bundle;
    3.      import android.app.Activity;
    4.      import android.view.Menu;
    5.      import android.widget.Button;
    6.      import android.view.View;
    7.      import android.view.View.OnClickListener;
    8.      import android.os.Process;
    9.      import android.widget.TextView;
    10.     import android.os.Handler;
    11.     import android.os.Message;
    
    12.     public class MainActivity extends Activity {
    13.            private Button btn_StartTaskThread;
    14.            private Button btn_StopTaskThread;
    15.            private Button btn_ExitApp;
    16.            private TextView threadOutputInfo;
    17.            private MyTaskThread myThread = null;
    18.            private Handler mHandler;;
    
    19.         @Override
    20.         public void onCreate(Bundle savedInstanceState) {
    21.            super.onCreate(savedInstanceState);
    22.            setContentView(R.layout.activity_main);
    23.            threadOutputInfo = (TextView)findViewById(R.id.taskThreadOuputInfo);
    24.            threadOutputInfo.setText("Thread Not Run");
    
    25.            mHandler = new Handler() {
    26.                public void handleMessage(Message msg) {
    27.                     switch (msg.what)
    28.                     {
    29.                     case MyTaskThread.MSG_REFRESHINFO:
    30.                             threadOutputInfo.setText((String)(msg.obj));
    31.                             break;
    32.                     default:
    33.                             break;
    34.                     }
    35.                }
    36.            };
    
    37.            btn_ExitApp = (Button) findViewById(R.id.exitApp);
    // Code for <Exit App>Button
    38.            btn_ExitApp.setOnClickListener(new /*View.*/OnClickListener(){
    39.                public void onClick(View v) {
    40.                                     finish();
    41.                     Process.killProcess(Process.myPid());
    42.                }
    43.            });
    
    44.            btn_StartTaskThread = (Button) findViewById(R.id.startTaskThread);
    45.    // Code for<Start Thread Run>
    46.            btn_StartTaskThread.setOnClickListener(new /*View.*/OnClickListener(){
    47.                public void onClick(View v) {
    48.                     myThread = new MyTaskThread(mHandler);  // Create a thread
    49.                     myThread.start();       // Start Thread
    50.                     setButtonAvailable();
    51.                 }
    52.            });
    
    53.            btn_StopTaskThread = (Button) findViewById(R.id.stopTaskThread);
    54.    //code for <Stop Thread Run>
    55.            btn_StopTaskThread.setOnClickListener(new /*View.*/OnClickListener(){
    56.                public void onClick(View v) {
    57.                     if (myThread!=null && myThread.isAlive())
    58.                             myThread.stopRun();
    59.                     try {
    60.                             if (myThread!=null){
    61.                                     myThread.join();
    62.    // Wait for Thread Run to end
    63.                                     myThread =null;
    64.                             }
    65.                     } catch (InterruptedException e) {
    66.                              // Empty statement block, ignored forcibly abort exception
    67.                     }
    68.                     setButtonAvailable();
    69.                 }
    70.            });
    71.            setButtonAvailable();
    72.        }
    
    73.        @Override
    74.        public boolean onCreateOptionsMenu(Menu menu) {
    75.            getMenuInflater().inflate(R.menu.activity_main, menu);
    76.            return true;
    77.        }
    
    78.        private void setButtonAvailable()     // New function is used to set the button optional
    79.        {
    80.             btn_StartTaskThread.setEnabled(myThread==null);
    81.             btn_ExitApp.setEnabled(myThread==null);
    82.             btn_StopTaskThread.setEnabled(myThread!=null);
    83.        }
    84.    }
    

    在刚刚给出的代码的第 17 行和第 18 行,我们分别将已定义线程类的变量 myThread 定义为 MyTaskThread,将默认的主线程处理程序对象定义为 mHandler。从第 25 行到第 36 行,我们定义了处理程序类。消息类的 What 属性字段指示消息的类型。自定义处理程序类根据消息类型对不同的处理程序使用 switch-case 语句,其中 MSG_REFRESHINFO 是自定义线程类 MyTaskThread 的消息类型,这意味着工作线程需要更新的界面显示消息。第 29 到 31 行的目的是处理消息。代码非常简单;它根据带有参数对象的消息更新 TextView 的小部件显示。

    第 47 到 49 行包含单击 start running threads 按钮时给出的响应代码。它首先创建自定义的 thread 对象,然后调用 Thread.start 函数,使自定义的线程类 MyTaskThread 运行,它将 run 函数中的执行代码作为单线程运行。第 49 行调用自定义的 setButtonAvailable 函数来设置每个按钮的选项(也就是说,灰色表示不可选,白色表示可选)。

    第 55 到 65 行是负责停止线程运行按钮的代码。第 55 行首先确定线程是否已经存在或正在运行,然后通过调用自定义线程类 MyTaskThread 中定义的 stop-the-thread 原型函数,然后调用 Thread.join()函数,停止第 56 行中的线程运行。然后,它等待线程运行结束。最后,它设置界面按钮的可选状态。

    第 75 到 80 行包含一个定制的函数,用于确定每个按钮的可选状态:白色表示可选;灰色表示不可选。

  3. 在应用中创建一个新类 MyTaskThread。该类从 Thread 继承,用于实现工作线程。这个类的源代码文件 MyTaskThread.java 如下:如下

    1.      package com.example.guiexam;
    2.      import android.os.Handler;
    3.      import android.os.Message;
    4.
    5.      public class MyTaskThread extends Thread {
    6.              private static final int stepTime = 500;
    7.      // Execution timeof each step(unite:ms)
    8.              private volatile boolean isEnded;
    9.      //mark if the thread is running. Used to stop thread run
    10.             private Handler mainHandler;
    11.     //Handler used to send message
    12.             public static final int MSG_REFRESHINFO = 1;
    // Update message on interface
    13.
    14.             public MyTaskThread(Handler mh)
    // Define a constructor
    15.             {
    16.                     super();
    // Call the parent class builder to create objects
    17.                     isEnded = false;
    18.                     mainHandler = mh;
    19.             }
    20.
    21.         @Override
    22.             public void run()
    // Write run code in thread body run method
    23.             {
    24.             Message msg ;
    25.                     for (int i = 0; !isEnded; i++)
    26.                     {
    27.                             try {
    28.                                     Thread.sleep(stepTime);
    // designate time for every  step of the thread to sleep
    29.                             String s = "Complete" + i +"step";
    30.                             msg = new Message();
    31.                             msg.what = MSG_REFRESHINFO;
    // Define message type
    32.                             msg.obj = s;
    // attach data to message
    33.                             mainHandler.sendMessage(msg);
    // send message
    34.                             } catch (InterruptedException e) {
    35.                                     e.printStackTrace();
    36.                             }
    37.                     }
    38.             }
    39.
    40.         public void stopRun()
    // Stop control function for stop thread run
    41.         {
    42.             isEnded = true;
    43.         }
    42.     }
    

这是自定义线程类 MyTaskThread 的实现代码,它是这个应用的关键。这个应用使用第一种方法,线程继承,来实现线程化。在第 5 行,让定制的类从 Thread 继承,然后从第 14 行到第 39 行,让线程运行重写 run 函数上的代码。为了应付线程的工作,在第 6 行到第 9 行我们定义了相关的变量。常量 stepTime 表示线程延迟时间的每一步的长度,以毫秒为单位。标记 isEnded 控制是否继续 run 函数中循环体的每一步以继续。请注意,变量前面是 volatile 修饰符:Volatile variables。每次线程访问变量时,都会在变量被修改后读取内存中的最终值。写请求也必须写入内存。这可以避免缓存或寄存器中的副本与内存变量中的值不匹配,从而导致错误。mainHandler 是保存主线程处理程序的变量。MSG_REFRESHINFO 是处理自定义消息的常量类型。

第 10 行到第 15 行是构造函数。在这个函数体中,我们初始化线程运行控制变量 isEnded 的值,然后将 mainHandler 保存为作为参数传递的主线程处理程序对象。

第 16 行到第 33 行是重写 run 函数的核心线程代码。代码由一个循环组成,决定是否继续使用控制变量 isEnded。在这里,我们称一个循环为一个步骤。工作的每一步也很简单:当线程类静态函数 sleep 在指定时间后在第 28 行被调用时,一条消息被生成并在第 24 行到第 27 行被组装。最后,在第 28 行,消息被发送到指定的(消息循环)处理程序。

第 34 行到第 37 行是一个定制的控制函数,用来停止线程的运行。代码非常简单;它改变运行回路控制变量值。

线程同步

多线程进程不可避免地涉及一个问题:如何处理线程对共享数据的访问,这涉及到线程同步。线程数据共享也被称为临界段 。对共享数据的访问也被称为对资源访问的竞争。一般来说,在操作系统教科书中,线程同步不仅包括这种被动选择访问共享数据的同步,还包括线程之间主动选择同步,以便协作完成任务。线程同步特别关注对共享数据的访问。在本节中,我们将讨论共享数据访问的同步问题。

在多线程编程中,如果对共享数据的访问不使用某些同步机制,就无法保证数据的一致性和完整性。有两种方法可以实现 Java 线程同步:一种叫做内部锁数据对象,另一种叫做同步。这两种方法都是用 synchronized 关键字实现的。由 synchronized 块修改的语句可以保证线程间操作的排他性,在操作系统中被认为是唯一的或原子的 。在 Java 中它被称为同步。同步块也被称为同步锁

在锁定数据对象的第一种方法中,在任何时候,只有一个线程可以访问被锁定的对象。代码框架如图图 9-36 所示。

9781430261308_Fig09-36.jpg

图 9-36 。方法一

在上面的代码中, var 必须是每个线程都可以访问的变量,这样就变成了同步变量。实际上,同步变量和共享变量可以是相同或不同的变量。图中代码中的 Object 类可以替换为 Object 的子类,因为除了 Java 中的简单类,任何类都可以是 Object 子代类。对象可以被任何类替换。

注意,一个原语类型(如 int 和 float,但不是 String 类)不能是同步变量,如图图 9-37 所示。

9781430261308_Fig09-37.jpg

图 9-37 。无效的同步块

当使用第二种方法,即同步方法时,任何时候只有一个线程访问一个代码段,如图图 9-38 所示。

9781430261308_Fig09-38.jpg

图 9-38 。同步方法

除了上图所示的通用类(函数)的同步,还有对类的静态函数的同步,如图图 9-39 所示。

9781430261308_Fig09-39.jpg

图 9-39 。同步静态方法

在同步方法中,被锁定的是调用同步方法的对象。当 MyClass: obj1 的一个对象在不同的线程中实现同步方法时,会形成互斥来达到同步的结果。但是由 MyClass 类生成的另一个对象 obj2 可以用 synchronized 关键字调用这个方法。因此,上图中的代码可以用与图 9-40 中的代码等效的术语来编写。

9781430261308_Fig09-40.jpg

图 9-40 。同步一般方法

静态同步方法是图 9-41 中所示的。

9781430261308_Fig09-41.jpg

图 9-41 。锁定一个类

在第二个方法,静态方法中,类被当作一个锁。它生成与同步静态函数相同的结果。获得锁定的时机也很特殊;当调用该对象所属的类,而不再是该类生成的特定对象时,会获得锁。

下面的概括了 Java 通过同步函数实现锁的规则:

  • 规则 1 :当两个并行线程访问同一个对象的 synchronized(this)同步代码段时,任何时候只能运行一个线程。另一个线程必须等待,直到当前线程运行完该代码段,才能运行同一代码段。
  • 规则二:当一个线程访问一个对象的同步(this)同步代码段时,另一个线程仍然可以访问一个对象的非同步(this)同步代码段。
  • 规则 3 :当一个线程访问一个对象的 synchronized(this)同步代码段时,所有其他线程对该对象所有其他 synchronized(this)同步代码段的访问都会被阻塞。
  • 规则 4 :当一个线程访问一个对象的 synchronized(this)同步代码段时,获取这个对象的对象锁。因此,其他线程对一个对象的所有同步(这个)同步代码段的所有访问都将被暂时锁定。
  • 规则 5 :前述规则适用于所有其他对象锁。

尽管同步可以保证所执行的对象或语句块的粒度,但是这种粒度的互斥性降低了线程的并发性,使得原本可以并行运行的代码不得不串行执行。因此,我们需要谨慎,将 synchronized 函数的使用限制在需要同步锁的情况下。另一方面,我们需要使锁粒度尽可能小,以便既保证程序的正确性,又提高运行效率。这是通过尽可能提高并发度来实现的。

线程通信

在多线程设计中,随着线程之间的数据交换,设置信号协作来完成任务是一个常见的问题。广义的线程问题是问题的主要部分,类似于生产者-消费者问题的典型例子。这些线程必须协作才能完成任务。

通常建议使用信号量来实现线程同步原语。Java 并不直接提供信号量原语或编程接口,而是用一个类函数来实现信号量的功能,比如 wait、notify、notifyAll 等等。

wait、notify 和 notifyAll 类属于 Object 类的函数,而不是 Thread 类的一部分。在 Java 中,每个对象都有一个等待队列(等待集)。当一个对象刚刚被创建时,它的等待队列是空的。

等待函数可以让当前线程中的对象一直等待,直到另一个线程调用这个对象的 notify 或 notifyAll 方法。换句话说,当一个调用在对象的队列中等待时,有一个线程进入等待状态。只有当 notify 方法被调用时,我们才能将线程从队列中放出来,使这个线程变得可运行。notifyAll 方法等待队列中的所有线程变为可运行。Notify 和 notifyAll 在功能上差别不大。

wait、notify 和 notifyAll 函数需要与 synchronized 结合使用来建立同步模型,这样可以保证前面函数的粒度。例如,在调用 wait 之前,我们需要获取对象的同步锁,以便调用这个函数。否则编译器可以调用 wait 函数,但它将收到一个 IllegalMonitorStateException 运行时异常。

下面是 wait、notify 和 notifyAll 的代码框架的几个例子。

图 9-42 显示了等待资源的代码。

9781430261308_Fig09-42.jpg

图 9-42 。锁定一个对象

图 9-43 显示了使用 notify 和提供资源的代码(一个例子是完全使用资源并返回给系统)。

9781430261308_Fig09-43.jpg

图 9-43 。使用通知

上图是同步对象 obj 的独立用例。我们也可以在类中编写同步代码。这段代码的框架可以写成如图图 9-44 所示。

9781430261308_Fig09-44.jpg

图 9-44 。同步的类示例

等待资源的线程调用 myclass.func1 函数,提供资源的线程调用 myclass.func2 函数。

英特尔凌动处理器多线程优化的原则

多线程软件设计允许不同线程中的程序代码同时运行。然而,盲目或过度使用多线程编程可能不会带来性能提升,甚至可能会降低软件性能。所以我们需要看看 Android x86 上多线程优化的原理。

首先,线程的启动或调度需要一定的开销,并占用一定的处理器时间。对于不支持超线程和多核处理的处理器,系统不能在物理上让线程同时运行。如果我们使用虚拟化技术将一个物理处理器拆分为多个逻辑处理器,让每个线程在一个逻辑内核上运行,以支持多线程程序,则会产生巨大的开销。这种多线程策略不仅难以提高性能,甚至可能导致多线程的执行速度比单线程程序慢。因此,要使用多线程设计实现多线程性能加速(比单线程执行速度更快的先决条件),处理器必须支持超线程、包含多核或拥有多个处理器。

其次,对于支持超线程或多核的处理器来说,线程越多软件运行越快并不总是正确的。有一个性价比需要考虑。性能调优中多线程设计的物理基础可以解释为允许多个线程在物理层上同时并行运行。因此,处理器支持的最大并发线程数是多线程优化的最佳线程数。

英特尔超线程技术可以支持两个线程并行运行,并且多核支持多个线程并行运行。例如,对于支持英特尔超线程技术的双核英特尔处理器,支持并行运行的最大线程数为:

2 核×2(HTT)= 4 个线程

因此,这台机器支持多线程优化,线程(并发运行的线程)的最大数量等于四。

例如,如果目标机器是联想 K800 手机,它使用单核英特尔凌动 Z2460 处理器,按照上面的公式,我们可以得出结论,运行两个线程可以使机器达到最佳性能。对于使用单核英特尔凌动 Z2480 处理器的摩托罗拉 MT788 目标机,最佳线程数是两个。如果目标机器是采用双核处理器的联想 K900、采用 HTT 的英特尔凌动 Z2580 处理器,那么达到的最佳线程数将是 4。

一般来说,当我们在 Android 平台上考虑多线程优化时,有必要仔细查看处理器信息,看看它是否支持超线程或多核技术。

案例研究:英特尔 GPA 辅助的 Android 应用多线程优化

在上一节中,我们解释了几种优化技术和原则。在这一节中,我们用一个综合的例子来解释优化的知识。在这种情况下,多线程优化与英特尔 GPA 辅助优化相结合,优化应用,使其运行更快。

我们用作示例的应用执行圆周率(π)的计算。我们现在来介绍一下这个 app 的背景。我们知道数学公式如下:

image

我们知道积分公式可以用不定式来表示:

image

其实∈x不可能无限小,所以我们只能让∈x尽可能小。因此,公式的结果更接近 π 。如果我们用来表示∈x,那么

image

必须是最大值才能得到圆周率的精确近似值。考虑一下

image

是凸起的函数。这里,我们取一个中值来计算总和;也就是说,我们使用

image

替换

image

计算总数。用此公式计算的结果并不总是小于π的实际值。所以最终我们得到了这个应用所基于的最终公式:

image

基于这个公式,我们可以推导出等价的计算源代码。

原始申请 和英特尔 GPA 分析

我们可以从上一节刚刚得出的公式中推导出案例研究应用的源代码。源代码不会被优化。我们将它称为原始应用,并将其命名为 SerialPi 。

该应用的设计与前面线程示例部分给出的示例相同。计算π的任务放在一个工作线程中(这里称为任务线程)。在主应用屏幕上设置一个按钮来控制线程的运行,并使用一个文本视图来显示任务线程的结果。显示 app 单次运行的界面如图图 9-45 所示。

9781430261308_Fig09-45.jpg

图 9-45 。SerialPi 的 App 运行界面

应用启动后出现的界面如图图 9-45(a) 所示。当点击开始计算按钮时,界面上的所有按钮都变灰,直到π线程的计算完成。然后界面显示π的计算结果以及线程的总运行时间。可以点击【退出应用】按钮,如图图 9-45(b) 所示,退出应用。从显示的界面我们知道这个 app 计算π大概需要 22 秒。当重复运行这个应用时,每次的计算时间都差不多(22 秒)。

应用步骤和关键代码的结构如下:

  1. 新版本 SerialPi。建议的项目属性设置为使用默认值。请注意,[Build SDK]被设置为支持 x86 API。

  2. Edit activity_main.xml; place two Buttons and two TextViews in the layout, the ID attribute of a TextView is set as @+id/taskOuputInfo, which is used to display the results of the task thread, as shown in Figure 9-46.

    9781430261308_Fig09-46.jpg

    图 9-46 。SerialPi 应用的主要概述

  3. Create a new thread class MyTaskThread in the new project and use it to calculate the value of π. Edit the source code file MyTaskThread.java as follows:

    1.      package com.example.serialpi;
    2.      import android.os.Handler;
    3.      import android.os.Message;

    4.      public class MyTaskThread extends Thread {
    5.              private Handler mainHandler;
    6.              public static final int MSG_FINISHED = 1;
    7.      // Defined the message type  for the end of the calculation

    8.              private static final long num_steps = 200000000;
    9.      // num_steps variables in Formula, the total number of steps
    private static final double step = 1.0 / num_steps;
    10.     // Step variable  in formula, step length
    11.             public static double pi = 0.0;

    12.     // the calculation of results of π

    14.             static String msTimeToDatetime(long msnum){
    15.     // The function converts the number of milliseconds into hours: minutes: seconds. Milliseconds "format
    16.             long hh,mm,ss,ms, tt= msnum;``16.             long hh,mm,ss,ms, tt= msnum;``16.             long hh,mm,ss,ms, tt= msnum;

    19.             mm = tt % 60; tt = tt / 60;

    哈哈哈" + ms +"Miliseconds";``24.``25.         @Override``25.         @Override``26.             public void run()``26.             public void run()``26.             public void run()``26.             public void run()``26.             public void run()``26.             public void run()``26.             public void run()``26.             public void run()``26.             public void run()``26.             public void run()

    类似于前面线程示例部分中给出的框架和代码示例,线程继承法则用于实现线程初始化。请密切注意以灰色突出显示的代码段。它们是与π的计算最直接相关的。在第 7 行和第 8 行,我们以静态变量的形式在计算π的公式中定义了同名的变量。第 9 行定义了保存π计算结果的变量。注意,这个变量是公共的,所以主线程可以访问它。

    第 22 到 28 行是根据公式计算π的代码。其中 x 变量是函数的自变量 x

    image

    总和是∑的累积变量。累计∑后,最后在第 28 行,让π = × ∑计算最终结果。参见前面线程实例章节中的两段较长的代码框架;理解代码背后的数学原理应该不难。

    注意,在线程的 run 函数中,一旦计算完成,消息将在第 29 行被发送到主线程(接口)。

  4. Edit the source code of the main activity class file: MainActivity.java. The code is allowed to control the run of the thread and displays the calculated results as follows:

    1.      package com.example.serialpi;
    2.      import android.os.Bundle;
    3.      import android.app.Activity;
    4.      import android.view.Menu;
    5.      import android.widget.Button;
    6.      import android.view.View;
    7.      import android.view.View.OnClickListener;
    8.      import android.os.Process;
    9.      import android.widget.TextView;
    10.     import android.os.Handler;
    11.     import android.os.Message;

    12.     public class MainActivity extends Activity {
    13.             private MyTaskThread myThread = null;
    14.         private TextView tv_TaskOutputInfo; // Display (Calculated) Task thread output

    15.         private Handler mHandler;;
    16.             private long end_time;
    17.             private long time;

    20.         public void onCreate(Bundle savedInstanceState) {
    21.             super.onCreate(savedInstanceState);
    22.             setContentView(R.layout.activity_main);
    23.             tv_TaskOutputInfo = (TextView)findViewById(R.id.taskOuputInfo);

    25.             btn_ExitApp.setOnClickListener(new /*View.*/OnClickListener(){
    26.                 public void onClick(View v) { Pi= "+ MyTaskThread.pi+ "  Time consumed:"
    46.                                                              +
    47.     MyTaskThread.msTimeToDatetime(time);
    48.                             tv_TaskOutputInfo.setText(s);
    49.                             btn_ExitApp.setEnabled(true);
    48.                             tv_TaskOutputInfo.setText(s);``32.                 public void onClick(View v) {``32.                 public void onClick(View v) {``31.             btn_StartTaskThread.setOnClickListener(new /*View.*/OnClickListener(){``31.             btn_StartTaskThread.setOnClickListener(new /*View.*/OnClickListener(){

    这里列出的代码类似于线程示例部分中示例 MainActivity 类的代码框架。以灰色突出显示的代码行是为了估计任务代码的运行时间而添加的。第 16 行到第 18 行首先定义了三个变量: start_time 为任务的开始时间, end_time 为任务的结束时间, time 为任务运行时间的长度。这三个变量是以下公式的一部分:

    time = end_time - start_time

    在第 65 行,当我们启动任务线程时,机器的当前时间被记录在 start_time 变量中。在第 43 行到第 44 行,当接收到任务线程已经结束运行的消息时,机器的时间被记录在 end_time 中。currentTimeMillis 函数是 java.lang 包中的 Java 系统类提供的静态函数。该函数以毫秒为单位返回当前时间。

  5. 参考Android 上英特尔 GPA 的示例使用部分中给出的示例,修改项目 AndroidManifest.xml 文件,使其符合英特尔 GPA 监控的要求。

在编码完成和应用编译生成之后,我们可以将应用部署到实际的目标设备上。在这种情况下,我们使用联想 K800 手机作为测试目标。

现在我们使用英特尔 GPA 来分析我们的 SerialPi 应用。具体步骤可以在Android 上英特尔 GPA 的示例使用部分找到。第一步是监视和分析两个 CPU 负载的情况。单击英特尔 GPA 开始按钮将开始记录 CPU 负载。分析结果如图图 9-47 (a)、(b)、(c)所示。

9781430261308_Fig09-47a.jpg

9781430261308_Fig09-47b.jpg

9781430261308_Fig09-47c.jpg

图 9-47 。SerialPi 的英特尔 GPA 分析屏幕

图 9-47 (a) 显示了点击开始按钮的分析,任务线程在开始时运行。图 9-47(b) 显示任务线程正在运行。图 9-47(c) 显示了运行结束时的任务线程。从这三个画面可以看出,在 app 开始运行前或者运行结束后,CPU 上的负载处于较低水平。一旦计算任务线程开始运行,CPU 上的负载就会急剧上升到满负载水平的 100%。我们还可以看到,在任务线程运行期间,两个 CPU 中只有一个处于满负荷状态,而另一个处于低负荷水平。通过分析该图,您会注意到 100%满载并不总是发生在特定的 CPU 上。相反,100%满负载在两个 CPU 之间交替轮换,这反映了 Java 运行时对任务调度的支持;处理器系统本身对应用是透明的。尽管两个 CPU 的负载率会发生变化,但负载率处于“互补”状态:一个 CPU 上的负载上升意味着另一个 CPU 上的负载下降。也就是说,总负载(任何时候两个 CPU 的负载之和)不会超过单个 CPU 的 100%满负载,这在运行多个应用时非常有用。

优化应用和英特尔 GPA 分析

前面的例子是直接从计算π值的公式中导出的代码。这个简单的数学例子,还有优化的空间吗?答案肯定是肯定的!优化需要我们研究应用的算法,并应用我们刚刚学到的原则,充分利用英特尔凌动处理器的硬件特性来释放全部性能潜力。

您如何挖掘英特尔凌动处理器的性能潜力?如前所述,采用英特尔超线程技术的多核英特尔凌动处理器支持在多个物理内核上并行运行多线程。例如,联想 K800 手机使用英特尔凌动 Z2460 处理器,并支持并行运行的两个线程。这是我们按照前面提到的分而治之的策略进行算法优化的切入点。通过仔细分析 MyTaskThread 类的 Run 函数的代码,我们设法使分配给多个线程的计算任务运行起来(在本例中是两个);并行运行的线程可以使应用运行得更快。为了计算π的积分面积的累积值,在第 24 行中,我们一步一步地计算积分面积,并加上累积和。现在我们采用不同的方法,将积分区域分成许多块,让每个线程负责计算一个这样的块。最后,我们将每个线程计算的块的累积面积相加,得到π值。这样,我们用一种“分而治之”的策略来完成任务分配,得到最终的结果。我们称这种算法优化方法为 ThreadPi。当 ThreadPi 在计算积分面积的累加值(也就是π值)时,我们让每个线程的计算步长累加步长来增加线程总数。这允许他们每个人负责他们自己的区块区域的累积和。

app ThreadPi 运行时的 UI 如图图 9-48 所示。

9781430261308_Fig09-48.jpg

图 9-48 。单独运行的 ThreadPi 用户界面

这个优化的应用(ThreadPi)的接口与原始应用(SerialPi)中的接口相同。在图 9-48(b) 中,我们可以看到这个应用用了十三秒来完成π值的计算。它的时间减少到原来的一半(22 秒)。唯一不同的是,优化的应用使用两个线程来计算π。

此应用基于修改原始应用代码;所做的主要更改如下:

  1. The thread class of the computing tasks’ MyTaskThread source code file was modified to MyTaskThread.java as follows:

    1.      package com.example.threadpi;
    2.      import android.os.Handler;
    3.      import android.os.Message;

    4.      public class MyTaskThread extends Thread {
    5.              private Handler mainHandler;
    6.              public static final int MSG_FINISHED = 1;
    7.              private static final long num_steps = 200000000;
    8.      // num_steps variable in formula, total steps
    9.              private static final double step = 1.0 / num_steps;
    10.     // step variable in formula, step length

    12.             public static final int num_threads = 2; // Thread count
    13.             private int myNum;                              // Thread #
    private static Object sharedVariable = new Object();
    14.     // synchronization lock variable for Pi variable
    15.             private static int finishedThreadNum = 0;

    17.
    18.             static String msTimeToDatetime(long msnum){
    19.     // The function to convert the number of milliseconds into hours: minutes: seconds. Millis
    20.             long hh,mm,ss,ms, tt= msnum;
    21.             ms = tt % 1000; tt = tt / 1000;
    22.             ss = tt % 60; tt = tt / 60;
    mm = tt % 60; tt = tt / 60;
    23.             hh = tt % 60;
    24.             String s = "" + hh +"hour "+mm+"minute "+ss + "" + ms +"milliseconds";
    25.             return s;

    27.             public void setStepStartNum(int n)
    28.     // set thread # for thread, in response to startting position of i
    29.             {
    30.                     myNum = n;

    32.
    33.             @Override
    34.             public void run()

    36.                     double x, partialSum = 0.0;
    37.             long i;
    38.                     for (i = myNum; i < num_steps; i += num_threads) {
    39.                             x = (i + 0.5) * step;
    40.                             partialSum += 4.0 / (1.0 + x * x);
    41.                     }
    42.                     synchronized (sharedVariable) {
    43.                             pi += partialSum * step;

    45.                             if (finishedThreadNum >= num_threads) {
    //waiting all threads finishing run and send message

    46.                                     Message msg = new Message();
    47.                                     msg.what = MSG_FINISHED; //Define message type

    49.                             }

    51.             }

    public MyTaskThread(Handler mh) // constructor
    {
    super();
    mainHandler = mh;
    }
    }

    前面代码中以灰色突出显示的代码段代表了应用(ThreadPi)和原始应用(SerialPi)之间的主要区别。在第 10 到 13 行,我们定义了多线程计算任务所需的变量。当计算任务开始时,变量 num_threads 计算线程的数量。在这种情况下,联想 K800 有一个带有两个逻辑 CPU 的英特尔凌动处理器,因此该值被设置为 2。myNum 变量计算线程数量,该数量在 0 到 num_threads-1 的范围内选择。变量 sharedVariable 是由应用于变量 pi 的同步锁引入的。由于 pi 是一个简单的变量,所以不能直接锁定。finishedThreadNum 变量表示用于完成计算的线程数。当 finishedThreadNum 的值等于 num_threads 的值时,我们认为所有的计算线程都在运行的末尾。

    在第 23 到 26 行,我们专门为 MyTaskThread 添加了一个函数。它标记了线程的索引号。

    第 30 到 44 行是计算线程的原型代码。第 30 到 35 行是计算π的直接代码。对比原应用的对应代码,我们可以看到原应用的 sum 变量被 partialSum 代替了,这反映了这个线程的面积只是总面积的一部分。最重要的区别在第 32 行:步长变量 I 不是 1,而是 num_threads,这意味着每次运行应用时,线程计数器都会向前移动若干步。变量 I 的初始位置不是 0,而是从线程号导出的。这有点像田径比赛,每个运动员(线程)从他们的泳道的起点开始,而不是在同一个起点。遵循线程计算就像运动员在自己的跑道上跑步一样。

    当一个线程计算累计和并需要将该数据添加到总累计和(即 pi 变量)时,这是一个由多线程共享的变量,因此需要添加一个同步锁。这一步对应于第 36 行到第 44 行。在第 36 行,我们添加了一个同步锁,而在第 37 行,线程自己的计算结果被添加到 pi 的公共结果中。在第 38 行,我们在计算结束时给线程数加 1。在第 39 行,通过比较完成计算的线程数与线程总数,我们确定是否所有计算线程都已运行完成。只有在所有线程结束时,消息才会发送到主线程。

  2. The source code file of the main activity class MainActivity MainActivity.java was modified, as in the following:

    1.      package com.example.threadpi;
    2.      import android.os.Bundle;
    3.      import android.app.Activity;
    4.      import android.view.Menu;
    5.      import android.widget.Button;
    6.      import android.view.View;
    7.      import android.view.View.OnClickListener;
    8.      import android.os.Process;
    9.      import android.widget.TextView;
    10.     import android.os.Handler;
    11.     import android.os.Message;

    13.             private MyTaskThread thrd[] = null;

    14.         private TextView tv_TaskOutputInfo;
    15.         private Handler mHandler;;
    16.             private long end_time;
    17.             private long time;

    21.             super.onCreate(savedInstanceState); Pi="+ MyTaskThread.pi+ "  Time spent:"
    50.                                                     + MyTaskThread.msTimeToDatetime(time);
    51.                             tv_TaskOutputInfo.setText(s);
    52.                             btn_ExitApp.setEnabled(true);

    54.                      default:
    55.                             break;``56.                      }

    69.                 for( int i=0; i < MyTaskThread.num_threads; i++){
    70.                     thrd[i] = new MyTaskThread(mHandler);            // Create a thread
    71.                     thrd[i].setStepStartNum(i);
    72.                     thrd[i].start();

    73.         }
    74.

    76.                     for (int i = 0; i < MyTaskThread.num_threads && thrd != null; i++) {
    77.                             try {
    78.                                      thrd[i].join(); // Wait for thread running to end
    79.                             } catch (InterruptedException e) {
    80.                             }

    82.                     finish();
    83.             Process.killProcess(Process.myPid());
    }

源代码中以灰色突出显示的代码段代表了优化应用和原始应用之间的主要区别。在第 13 行,原始应用的单线程对象变量变成了一个线程数组。在从第 67 行到第 71 行的关于启动计算任务的部分中,启动原始应用中的单个线程被更改为启动数组中的所有线程,并在启动应用时设置线程的索引号。至于线程号的含义,在 MyTaskThread 代码描述中已经介绍过了。其他变化包括等待单个线程的结束,以便等待线程数组的结束(第 74 到 79 行)。

完成这些优化后,我们需要编译、生成应用,并将其部署到目标设备上,就像我们处理原始应用一样。我们可以独立运行应用,并测量这个优化应用的运行时间。计算时间减少到原来的一半。

图 9-49 显示了使用英特尔 GPA 分析这一优化应用(ThreadPi)的结果。分析过程与我们用于原始应用(SerialPi)的过程相同。

9781430261308_Fig09-49a.jpg

9781430261308_Fig09-49b.jpg

9781430261308_Fig09-49c.jpg

图 9-49 。ThreadPi 的英特尔 GPA 分析截图

如图所示,当单击 Start 按钮时,计算(任务)线程开始运行。两个 CPU 负载都从低负载上升到 100%满负载。当计算完成时,两个 CPU 负载降回到低负载条件。与原始应用(SerialPi)不同,在计算任务运行期间,两个 CPU 都是 100%满载的。不再有任何负载旋转。这表明优化的应用(ThreadPi)有两个并行的 CPU 在计算任务上满负荷工作,这使得应用的运行速度提高了一个数量级。

概述

在本章中,我们深入探讨了 Android x86 平台上的性能优化。从性能优化方法的高级概述开始,我们讨论了需要记住的要点:减少指令、降低执行频率、选择最高效的指令、正确使用并行性以及优化可用的缓存特性。我们还讨论了为我们做的优化以及对性能的常见误解。从这里,我们了解了英特尔 VTune,一款面向 Android 和 x86 的性能评测工具,以及深入的安装和设置指南。接下来,我们讨论了 Android 特有的多线程和设计,并从深入的、以代码为中心的角度研究了它是如何实现的。最后,我们将所有的点连接起来,使用英特尔 GPA 来观察我们的 Android 应用,然后对正确的并行实现应该是什么样子进行了一个小案例研究。

十、x86 NDK 和 C/C++ 优化

技术进步只是为我们提供了更有效的倒退手段。

Aldous Leonard 赫胥黎

在前一章中,我们介绍了性能优化的基本原则、优化方法和 Android 应用开发的相关工具。由于 Java 是 Android 开发人员的主要应用开发语言,前一章介绍的优化工具主要是针对 Java 的。我们知道 Java 应用是在虚拟机中运行的,速度天生就比 C/C++ 应用慢,C/c++ 应用是直接编译运行在硬件指令上的。此外,由于 C/C++ 的底层和基础性质,C/C++ 应用的开发人员已经创建了更多的优化工具。

矢量化

英特尔编译器支持高级代码生成,包括自动矢量化。对于英特尔 C/C++ 编译器,矢量化是指通过同时对几个元素执行单指令、多数据(SIMD)指令的生成来展开循环。开发者可以手动展开循环,并插入对应于 SIMD 指令的适当的函数调用。这种方法不能向前扩展,并且会导致高开发成本。当具有高级指令支持的新微处理器发布时,这项工作必须重做。例如,早期的英特尔凌动微处理器无法从处理双精度浮点的循环向量化中受益,而单精度则由 SIMD 指令有效处理。

自动向量化 简化了编程任务,因为程序员不必学习每个特定微处理器的指令集。例如,英特尔编译器始终支持最新一代的英特尔微处理器。

-vec选项为支持 IA32 架构的微处理器(包括英特尔和非英特尔)开启默认优化级别的矢量化功能。为了提高矢量化的质量,您需要指定代码将在其上执行的目标微处理器。为了在基于英特尔架构的 Android 智能手机上获得最佳性能,最好使用–xSSSE3_ATOM选项。英特尔 C++ 编译器以-O2及更高的优化级别支持矢量化。

许多循环是自动矢量化的,大多数时候编译器会自己生成最佳代码。然而,有时可能需要程序员的指导。高效矢量化的最大问题是让编译器尽可能精确地估计数据依赖关系。

为了充分利用英特尔编译器矢量化功能,以下技术非常有用:

  • 生成并理解矢量化报告
  • 通过指针歧义消除提高性能
  • 使用过程间优化提高性能
  • 使用编译器编译指示

矢量化报告

本节从内存复制的实现开始。循环采用 Android 源代码中常用的结构:

清单 10-1 。内存复制实现

// It is assumed that the memory pointed to by dst
// does not intersect with the memory pointed to by src

void copy_int(int* dst, int* src, int num)
{
int left = num;
if ( left <= 0 ) return;
do {
    left--;
    *dst++ = *src++;
} while ( left > 0 );
}

对于矢量化实验,您将重用hello-jni项目。为此,将函数添加到名为jni/copy_cpp.cpp的新文件中。将该文件添加到jni/Android.mk的源文件列表中,如下所示:

清单 10-2 。矢量化失败

LOCAL_SRC_FILES := hello-jni.c copy_int.cpp

要启用详细的矢量化报告,请将–vec-report3选项添加到jni/Application.mk中的APP_CFLAGS变量:

APP_CFLAGS := -O3 -xSSSE3_ATOM  -vec-report3

如果您重新构建libhello-jni.so,您会注意到生成了几条注释:

jni/copy_int.cpp(6): (col. 5) remark: loop was not vectorized: existence of vector dependence.
jni/copy_int.cpp(9): (col. 10) remark: vector dependence: assumed ANTI dependence between src line 9 and dst line 9.
jni/copy_int.cpp(9): (col. 10) remark: vector dependence: assumed FLOW dependence between dst line 9 and src line 9.
...

不幸的是,自动矢量化失败了,因为编译器可用的信息太少。如果矢量化成功,赋值将被替换如下:

*dst++ = *src++;
//The previous statement would be replaced with
*dst = *src;
*(dst + 1) = *(src + 1);
*(dst + 2) = *(src + 2);
*(dst + 3) = *(src + 3);
dst += 4; src += 4;

前四个任务将由 SIMD 指令并行执行。但是如果在赋值的左边访问的存储器也在赋值的右边访问,那么赋值的并行执行是无效的。例如,考虑当dst+1等于src+2时的情况。在这种情况下,dst+2的最终值将是不正确的。

备注指出编译器保守假设哪些类型的依赖关系会阻止向量化:

  • 依赖性是来自同一存储器位置的较早存储和较晚加载之间的依赖性。
  • 依赖性是对同一存储器位置的较早加载和较晚存储之间的依赖性。
  • 输出依赖于两次存储到同一个内存位置。

从代码注释中,可以安全地假设作者要求由dstsrc指向的内存不重叠。为了向编译器传递信息,只需向dstsrc参数添加限制限定符:

void copy_int(int * __restrict__ dst, int * __restrict__ src, int num)

restrict 限定符被添加到 1999 年发布的 C 标准中。要启用对 C99 的支持,您需要将–std=c99添加到选项中。或者,您可以使用–restrict选项为 C++ 和其他 C 语言启用它。在前面的代码中,__restrict__关键字已经被插入,并且总是被认为是restrict关键字的同义词。

如果您再次重建库,您会注意到循环被矢量化了:

jni/copy_int.cpp(6): (col. 5) remark: LOOP WAS VECTORIZED.

在本例中,由于编译器保守分析,矢量化失败。还有其他循环未矢量化的情况,包括:

  • 指令集不允许有效的向量化。以下说明指出了这种类型的问题:
    • "使用了非单位步幅"
    • "混合数据类型"
    • "运算符不适合矢量化"
    • "第 XX 行包含不可计算的语句"
    • “条件可以保护异常”
  • 编译器试探法会阻止矢量化。矢量化是可能的,但实际上可能会导致速度变慢。如果是这种情况,诊断将包含:
    • “矢量化是可能的,但似乎效率不高”
    • "低行程计数"
    • “非内部循环”
  • 矢量器的缺点:
    • "条件太复杂"
    • "下标太复杂"
    • "不支持的循环结构"

矢量器产生的信息量由–vec-reportN控制。您可以在编译器文档中找到更多详细信息。

杂注

如您所见,您可以使用restrict指针限定符来避免对数据依赖性的保守假设。但是有时候插入restrict关键词很棘手。如果在循环中访问许多数组,注释所有指针也可能太费力。为了在这些情况下简化矢量化,您可以使用英特尔特有的编译指令simd。假设迭代之间没有依赖关系,您可以使用它来矢量化内部循环。

Pragma simd仅适用于在本机整数和浮点类型上操作的for循环:

  • for循环应该是可计数的,在循环开始之前知道迭代的次数。
  • 循环应该在最里面。
  • 循环中的所有内存引用都不应该出错(这对屏蔽的间接引用很重要)。

要用 pragma 对循环进行矢量化,需要将代码重写为一个for循环,如清单 10-3 所示。

清单 10-3 。可以向量化的内存复制实现

void copy_int(int* dst, int* src, int num)
{
#pragma simd
for ( int i = 0; i < num; i++ ) {
*dst++ = *src++;
}
}

重新构建示例,注意循环是矢量化的。简单的循环重组pragma simd的和在 Android OS 源代码中插入#pragma simd允许您在不修改基准本身的情况下将 Softweg 基准的性能提高 1.4 倍。

自动矢量化和限制

前面几节中的例子是基于这样的假设,即在开始优化工作之前,您已经很好地理解了代码。如果您不熟悉代码,可以通过扩展分析范围来帮助编译器分析它。在复制的例子中,编译器应该做出保守的假设,因为它对copy_int例程的参数一无所知。如果调用点可用于分析,编译器可以尝试证明参数对于矢量化是安全的。

为了扩展分析的范围,您需要启用过程间优化 。在单个文件编译期间,默认情况下会启用其中的一些优化。过程间优化将在单独的章节中介绍。

矢量化不能用来加速 Linux 内核代码,因为在内核模式下使用–mno-sse选项禁用了 SIMD 指令。这是内核开发人员有意为之的。

过程间优化

如果编译器可以跨函数边界进行优化,它可以执行额外的优化。例如,如果编译器知道某个函数调用参数是常量,那么它可以创建一个专门为该常量参数定制的特殊版本的函数。这个特殊版本以后可以利用参数值的知识进行优化。

要在单个文件中启用优化,请指定–ip选项。当指定此选项时,编译器会生成一个可由系统链接器处理的最终目标文件。生成目标文件的缺点是几乎完全丢失信息;编译器甚至不会尝试从目标文件中提取信息。

由于信息丢失,单个文件范围可能不足以进行分析。在这种情况下,需要添加–ipo选项。使用此选项时,编译器会将文件编译成中间表示形式,稍后由特殊的英特尔工具 xiar 和 xild 进行处理。

您使用 xiar 工具而不是 GNU archiver ar 来创建静态库,并且使用 xild 而不是 GNU linker ld。只有在直接调用链接器和归档器时才需要它。更好的方法是使用编译器驱动程序iccicpc 进行最终链接。扩展范围的缺点是失去了单独编译的优势——每次修改源代码都需要重新链接,而重新链接会导致完全重新编译。

从全局分析中受益的高级优化技术有很多。 第九章:x86 平台上 Android 应用的性能优化介绍了其中一些技术,其他的将在本章后面讨论。请注意,有些优化是英特尔特有的,并通过–x*选项启用。

不幸的是,就共享库而言,Android 中的事情稍微复杂一些。默认情况下,所有全局符号都是可抢占的。可抢占性很容易用例子来解释。考虑以下库链接到同一个可执行文件的情况:

清单 10-4 。libone.so ,一个链接库示例

int id(void) {
    return 1;
}

清单 10-4 是链接的第一个库,第二个在清单 10-5 中有描述。

清单 10-5 。libtwo.so ,第二个链接库示例

int id( void ) {
    return 2;
}
int foo( void ) {
    return id();
}

假设这些库是通过执行icc –fpic –shared –o <libname>.so <libname>.c创建的。只给出严格要求的选项–fpic–shared

如果系统动态链接器在库libtwo.so之前加载库libone.so,那么函数foo()对函数id()的调用在libone.so库中解析。

当编译器优化函数foo()时,它不能使用来自libtwo.so库的关于id()的知识。例如,它不能内联id()函数。如果编译器内联了id()函数,就会破坏涉及libone.solibtwo.so的场景。

因此,当您编写共享库时,您应该仔细指定哪些函数可以被抢占。默认情况下,所有全局函数和变量在共享库之外都是可见的,并且可以被抢占。当您实现几个本机方法时,默认设置并不方便。在这种情况下,您只需要导出由 Dalvik Java 虚拟机直接调用的符号。

符号的可见性属性指定一个符号在模块外是否可见,以及它是否可以被抢占:

  • “默认”可见性使全局符号在共享库之外可见,并且能够被抢占。
  • “受保护的”可见性使符号在共享库之外可见,但该符号不能被抢占。
  • “隐藏”可见性使全局符号仅在共享库中可见,并禁止抢占。

回到hello-jni应用,有必要指定默认可见性是隐藏的,并且为 JVM 导出的函数具有受保护的可见性。

要将默认可见性设置为隐藏,将-fvisibility=hidden添加到jni/Application.mk中的APP_CFLAGS变量:

APP_CFLAGS := -O3 -xSSSE3_ATOM  -vec-report3 -fvisibility=hidden -ipo

要覆盖Java_com_example_hellojni_HelloJni_stringFromJNI的可见性,将属性添加到函数定义中:

Jstring __attribute__((visibility("protected")))
  Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)

设置此标志后,默认可见性被隐藏。这是英特尔 NDK 应用的过程间优化程度。

利用 IPP 进行优化

从第七章你知道,Android 应用可以绕过 NDK 开发工具,使用第三方开发的现有.so共享库。在本章中,我们以英特尔 IPP 函数库为例。使用该库的典型应用包括多媒体和流应用,但是任何时间性能都是问题的应用都将从该工具中受益。

英特尔 IPP (集成性能基元)是英特尔提供的高性能库之一。它是英特尔处理器和芯片组的强大函数库,涵盖数学、信号处理、多媒体、图像和图形处理、矢量计算和其他领域。英特尔 IPP 的一个显著特点是其代码已经基于任何英特尔处理器的特性使用多种方法进行了广泛的优化。可以说是一个与英特尔处理器关联的高度优化的高性能服务库。英特尔 IPP 具有跨平台特性;它提供了一组跨平台和操作系统的通用 API,可用于 Windows、Linux 和其他操作系统,并支持嵌入式、桌面、服务器和其他处理器规模的系统。

事实上,IPP 是一组函数库,每个函数库在相应的函数库中都有不同的功能区域,并且英特尔 IPP 中的函数库在不同处理器架构支持的功能数量上略有不同。例如,英特尔 IPP 5。x 图像处理功能在英特尔架构中可支持 2570 个功能,而在 IXP 处理器架构中仅支持 1574 个功能。

包括英特尔 IPP 在内的各种高性能图书馆提供的服务是多方面和多层次的。应用可以直接或间接使用 IPP。IPP 为应用、其它组件和库提供支持。

使用 IPP 的应用可以分为两个层次——直接使用英特尔 IPP 函数接口,或者使用样本代码间接使用英特尔 IPP。此外,使用 OpenCV 库(一个跨平台开源计算机视觉库)相当于间接使用英特尔 IPP 库。英特尔 IPP 和英特尔 MKL 函数库最终都可以在各种架构的高性能英特尔处理器上运行。

考虑到 IPP 的强大功能,并根据英特尔处理器优化特性的特点,您可以使用英特尔 IPP 库来替换一些经常运行且耗费大量时间的关键源代码。您可以获得比一般代码更高的性能加速。这简直就是一个“站在巨人肩膀上”的实用优化方法。用户无需在关键区域手动编写代码即可实现优化。

英特尔最近发布了名为 Beacon Mountain 的英特尔 Android 开发环境代码。它为 Android 应用开发人员提供了 IPP 和英特尔线程构建模块(TBB)。普通用户可以轻松使用英特尔 IPP、英特尔 TBB、英特尔 GPA 和其他工具进行 Android 应用开发。英特尔 IPP 的示例可在http://software.intel.com/en-us/articles/intel-integrated-performance-primitives-intel-ipp-intel-ipp-sample-code找到。

NDK 集成优化示例

我们已经介绍了基于 NDK 优化的知识和基本理论的优化。本节使用一个案例研究来演示通过将 NDK 与 C/C++ 相集成的综合优化技术。

本案分为两步。第一步演示了一种在从 C/C++ 代码编译的本地函数上使用的技术,以加速传统的基于 Java 的程序中的计算任务。第二步演示了使用 NDK 编译器优化来实现 C/C++ 优化任务本身。我们在这一章的以下两个部分中介绍每个步骤,这两个部分是紧密联系的。

C/C++:原始应用加速

在前一章中,我们介绍了使用 Java 代码示例(SerialPi)来计算π。在这一节中,我们将把计算任务从 Java 改为 C 代码,使用 NDK 把它变成一个本地库。然后,我们将它与原始的 Java 代码任务进行比较,您将获得一些使用 C/C++ 本地库函数实现传统的基于 Java 的任务加速的第一手经验。

用于本案例研究的应用名为 NDKExp ,我们使用联想 K800 作为目标手机,它运行图 10-1 所示的界面。

9781430261308_Fig10-01.jpg

图 10-1 。NDKExp 运行界面的原始版本

图 10-1 (a)显示了应用的主界面,包括三个按钮——启动 Java 任务、启动 C 任务、退出 App。点击启动 Java 任务按钮会启动一个传统的 Java 任务(如 Java 写的 SerialPi 源代码所示,它计算π)。当任务完成后,计算的结果将显示在按钮下方,同时显示花费的时间,如图图 10-1 (b)所示。点击启动 C 任务按钮将启动一个用 C 编写的计算任务,使用相同的数学公式计算π。当任务完成后,计算的结果将显示在按钮下方,同时显示花费的时间,如图图 10-1 (c)所示。

如图图 10-1 所示,同样的任务,用传统 Java 编写的应用需要 12.565 秒才能完成;用 C 语言编写并由 NDK 开发工具编译的应用需要 6.378 秒才能完成。这个例子可以让您直观地体验到使用 NDK 来实现性能优化的强大功能。

该示例实现如下。

步骤 1 :创建一个新的 Android 应用项目

  1. 在 Eclipse 中生成项目,将项目命名为NDKExp,并选择 Build SDK 选项以支持 x86 版本的 API(在本例中为 Android 4.0.3)。其他人使用默认值。完成所有这些步骤后,生成项目。

  2. Modify the main layout file. Put three text views and three buttons in the layout, set their Text and ID attributes, and adjust their size and position, as shown in Figure 10-2.

    9781430261308_Fig10-02.jpg

    图 10-2 。原始版本 NDKExp 的布局

  3. Modify the main layout of the class source code file MainActivity.java. It reads as follows:

    1.  package com.example.ndkexp;
    2.  import android.os.Bundle;
    3.  import android.app.Activity;
    4.  import android.view.Menu;
    5.  import android.widget.Button;
    6.  import android.view.View;
    7.  import android.view.View.OnClickListener;
    8.  import android.os.Process;
    9.  import android.widget.TextView;
    10\. import android.os.Handler;
    11\. import android.os.Message;
    12.
    13\. public class MainActivity extends Activity {
    14.    private JavaTaskThread javaTaskThread = null;
    15.    private CCodeTaskThread cCodeTaskThread = null;
    16.      private TextView tv_JavaTaskOuputInfo;
    17.      private TextView tv_CCodeTaskOuputInfo;
    18.      private Handler mHandler;;
    19.    private long end_time;
    20.    private long time;
    21.    private long start_time;
    22.      @Override
    23.      public void onCreate(Bundle savedInstanceState) {
    24.          super.onCreate(savedInstanceState);
    25.          setContentView(R.layout.activity_main);
    26.          tv_JavaTaskOuputInfo = (TextView)findViewById    (R.id.javaTaskOuputInfo);
    27.        tv_JavaTaskOuputInfo.setText("Java the task is not    started ");
    28.          tv_CCodeTaskOuputInfo = (TextView)findViewById    (R.id.cCodeTaskOuputInfo);
    29.          tv_CCodeTaskOuputInfo.setText("C  code task is not    start ");
    30.          final Button btn_ExitApp = (Button) findViewById    (R.id.exitApp);
    31.          btn_ExitApp.setOnClickListener(new /*View.*/    OnClickListener(){
    32.              public void onClick(View v) {
    33.                exitApp();
    34.              }
    35.          });
    36.          final Button btn_StartJavaTask = (Button)     findViewById(R.id.startJavaTask);
    37.          final Button btn_StartCCodeTask = (Button)     findViewById(R.id.startCCodeTask);
    38.           btn_StartJavaTask.setOnClickListener(new /*View.*/    OnClickListener(){
    39.              public void onClick(View v) {
    40.                btn_StartJavaTask.setEnabled(false);
    41.                btn_StartCCodeTask.setEnabled(false);
    42.                btn_ExitApp.setEnabled(false);
    43.                startJavaTask();
    44.                }
    45.          });
    46.          btn_StartCCodeTask.setOnClickListener(new /*View.*/    OnClickListener(){
    47.              public void onClick(View v) {
    48.                btn_StartJavaTask.setEnabled(false);
    49.                btn_StartCCodeTask.setEnabled(false);
    50.                btn_ExitApp.setEnabled(false);
    51.                startCCodeTask();
    52.                }
    53.          });
    54.          mHandler = new Handler() {
    55.              public void handleMessage(Message msg) {
    56.              String s;
    57.               switch (msg.what)
    58.                {
    59.                case JavaTaskThread.MSG_FINISHED:
    60.                    end_time = System.currentTimeMillis();
    61.                    time = end_time - start_time;
    62.                    s = " The return value of the Java task "+     (Double)(msg.obj) +"  Time consumed:"
    63.               + JavaTaskThread.msTimeToDatetime(time);
    64.                    tv_JavaTaskOuputInfo.setText(s);
    65.                    btn_StartCCodeTask.setEnabled(true);
    66.                  btn_ExitApp.setEnabled(true);
    67.                break;
    68.                case CCodeTaskThread.MSG_FINISHED:
    69.                    end_time = System.currentTimeMillis();
    70.                    time = end_time - start_time;
    71.                    s = " The return value of the C code     task"+ (Double)(msg.obj) +"  time consumed:"
    72.               + JavaTaskThread.msTimeToDatetime(time);
    73.                    tv_CCodeTaskOuputInfo.setText(s);
    74.                     btn_StartJavaTask.setEnabled(true);
    75.                  btn_ExitApp.setEnabled(true);
    76.                break;
    77.                default:
    78.                  break;
    79.                }
    80.              }
    81.          };
    82.      }
    83.
    84.      @Override
    85.      public boolean onCreateOptionsMenu(Menu menu) {
    86.          getMenuInflater().inflate(R.menu.activity_main, menu);
    87.          return true;
    88.      }
    89.
    90.      private void startJavaTask() {
    91.        if (javaTaskThread == null)
    92.          javaTaskThread = new JavaTaskThread(mHandler);
    93.        if (! javaTaskThread.isAlive())
    94.        {
    95.               start_time = System.currentTimeMillis();
    96.               javaTaskThread.start();
    97.               tv_JavaTaskOuputInfo.setText("The Java task is     running...");
    98.        }
    99.      }
    100.
    101.      private void startCCodeTask() {
    102.        if (cCodeTaskThread == null)
    103.          cCodeTaskThread = new CCodeTaskThread(mHandler);
    104.        if (! cCodeTaskThread.isAlive())
    105.        {
    106.               start_time = System.currentTimeMillis();
    107.               cCodeTaskThread.start();
    108.               tv_CCodeTaskOuputInfo.setText("C codes task is     running...");
    109.        }
    110.      }
    111.      private void exitApp() {
    112.        try {
    113.          if (javaTaskThread !=null)
    114.          {
    115.            javaTaskThread.join();
    116.            javaTaskThread = null;
    117.          }
    118.        } catch (InterruptedException e) {
    119.        }
    120.        try {
    121.          if (cCodeTaskThread  !=null)
    122.          {
    123.            cCodeTaskThread.join();
    124.            cCodeTaskThread = null;
    125.          }
    126.        } catch (InterruptedException e) {
    127.        }
    128.      finish();
    129.        Process.killProcess(Process.myPid());
    130.      }
    131.
    132.    static {
    133.      System.loadLibrary("ndkexp_extern_lib");
    134.    }
    135.  }
    

    前面的代码与 SerialPi 的示例代码基本相同。第 123 到 134 行的代码是唯一的新部分。这段代码要求在应用运行之前加载libndkexp_extern_lib.so共享库文件。应用需要使用这个库中的本地函数。

  4. 项目中的新线程任务类JavaTaskThread用于计算π。代码类似于 SerialPi 示例中的MyTaskThread类代码,此处省略。

  5. The thread task class CCodeTaskThread in the new project calls the local function to calculate π; its source code files CCodeTaskThread.java read as follows:

    1.  package com.example.ndkexp;
    2.  import android.os.Handler;
    3.  import android.os.Message;
    
    4.  public class CCodeTaskThread extends Thread {
    5.    private Handler mainHandler;
    6.    public static final int MSG_FINISHED = 2;
            // The message after the end of the task
    7.    private native double cCodeTask();
           // Calling external C functions to accomplish computing     tasks
    8.    static String msTimeToDatetime(long msnum){
    9.    long hh,mm,ss,ms, tt= msnum;
    10.      ms = tt % 1000; tt = tt / 1000;
    11.      ss = tt % 60; tt = tt / 60;
    12.      mm = tt % 60; tt = tt / 60;
    13.      hh = tt % 60;
    14.      String s = "" + hh +" Hour "+mm+" Minute "+ss + " Second     " + ms +" Millisecond ";
    15.      return s;
    16.      }
    17.      @Override
    18.      public void run()
    19.      {
    20.      double pi = cCodeTask();
            // Calling external C function to complete the calculation
    21.      Message msg = new Message();
    22.      msg.what = MSG_FINISHED;
    23.      Double dPi = Double.valueOf(pi);
    24.        msg.obj = dPi;
    25.        mainHandler.sendMessage(msg);
    26.      }
    
    27.      public CCodeTaskThread(Handler mh)
    28.      {
    29.         super();
    30.         mainHandler = mh;
    31.      }
    32.  }
    

    前面的代码类似于 SerialPi 示例的MyTaskThread类的代码框架。主要区别在第 20 行。原来计算π的 Java 代码被替换为调用一个本地函数cCodeTask。要声明cCodeTask函数是一个局部函数,您必须在第 7 行添加该函数的局部声明。

  6. 在 Eclipse 中构建项目。同样,这里我们只是构建,而不是运行。

  7. 在项目根目录下创建jni子目录。

第二步:编写 cCodeTask 函数的 C 实现代码

根据第七章:创建和移植基于 NDK 的 Android 应用****的 NDK 示例部分描述的方法,你需要将文件编译成一个.so库文件。主要步骤如下:

  1. Create a C interface file. Since the case is a CCodeTaskThread class using a local function, you need to generate the class header file according to the class file of this class. At the command line, go to the project directory and then run the following command:

    E:\temp\Android Dev\workspace\NdkExp> javah -classpath "D:\Android\android-sdk\platforms\android-15\android.jar";bin/classes  com.example.ndkexp.CCodeTaskThread
    

    该命令将在项目目录中生成一个名为com_example_ndkexp_CCodeTaskThread.h的文件。文件的主要内容如下:

       ...
    23.  JNIEXPORT jdouble JNICALL Java_com_example_ndkexp_   CCodeTaskThread_cCodeTask
    24.  (JNIEnv *, jobject);
       ...
    

    在第 23–24 行,定义了本地函数cCodeTask的原型。

  2. Based on the previous header files, you create corresponding C code files in the jni directory of the project. In this case, we named it mycomputetask.c, which reads as follows:

    1.  #include <jni.h>
    2.  jdouble Java_com_example_ndkexp_CCodeTaskThread_cCodeTask
    (JNIEnv* env,  jobject thiz )
    3.  {
    4.    const long num_steps = 100000000;  // The total step length
    5.    const double step = 1.0 / num_steps;
    6.      double x, sum = 0.0;
    7.      long i;
    8.    double pi = 0;
    9.
    10.    for (i=0; i< num_steps; i++){
    11.          x = (i+0.5)*step;
    12.          sum = sum + 4.0/(1.0 + x*x);
    13.      }
    14.      pi = step * sum;
    15.
    16.    return (pi);
    17.  }
    

    第 4 行到第 16 行是函数的主体——计算π的代码,这是对应于 SerialPi 示例中的MyTaskThread类的代码。注意,在第 4 行,变量num_steps(总步长)的值必须与JavaTaskThread类表示的相同步长的值相同。否则,比较没有意义。

    每个jni文件的第一行必须包含标题。第 2 行是cCodeTask函数原型,基于上一步获得的稍微修改过的头文件。

    第 16 行显示了返回结果。使用 Java 的double类型,对应于 C 的jdouble类型,C 可以有一个直接返回给它的double类型的pi变量。这是我们在本章介绍中讨论过的内容。

  3. In the project jni directory, you must create the Android.mk and Application.mk files. The content of Android.mk reads as follows:

    1.  LOCAL_PATH := $(call my-dir)
    2.  include $(CLEAR_VARS)
    3.  LOCAL_MODULE     := ndkexp_extern_lib
    4.  LOCAL_SRC_FILES  := mycomputetask.c
    5.  include $(BUILD_SHARED_LIBRARY)
    

    第 4 行指定了案例文件中的 C 代码。第 3 行表示生成的库的文件名,其名称必须与项目文件MainActivity.java的第 133 行System.loadLibrary函数的参数一致。

  4. 按照 第七章 关于 NDK 实例一节中描述的方法,将 C 代码编译到项目lib目录下的.so库文件中。

  5. 部署:运行项目。

应用运行界面如图 10-3 所示。

9781430261308_Fig10-03.jpg

图 10-3 。扩展版 NDKExp 运行界面

编译器优化扩展应用

在前面的示例中,您见证了 NDK 在应用加速方面的能力。然而,这个应用只实现了一个局部函数,不能为您提供比较编译器优化效果的信息。为此,您需要重新构建应用,并使用它来试验编译器优化的效果。

运行该界面的应用如图 10-3 所示。

该应用有四个按钮。当您单击 Start Java Task 按钮时,响应代码不会改变。

当你点击 Start C Task 或 Start Other C Task 按钮时,应用将启动一个本地函数来运行。

两个函数的代码(函数体)是一样的。它计算π的值,但名称不同。第一个调用cCodeTask函数,第二个调用anotherCCodeTask函数。这两个函数位于mycomputetask.canothertask.c文件中,编译后分别对应库文件libndkexp_extern_lib.solibndkexp_another_lib.so。在这种情况下,使用-O0选项编译libndkexp_extern_lib.so,使用-O3选项编译libndkexp_another_lib.so,因此一个是非优化编译,另一个是优化编译。

因此,点击启动 C 任务将运行 C 函数的未优化版本,如图图 10-3 (b)所示,点击启动其他 C 任务将运行 C 函数的优化版本,如图图 10-3 (c)所示。任务执行后,系统显示时间消耗的计算结果。

从图中可以看出,无论是否使用编译器优化,本地函数的运行时间总是比 Java 函数的运行时间(12.522 秒)短。相对而言,-O3优化函数的执行时间(5.632 秒)比未优化(-O0编译器选项)函数的执行时间(7.321 秒)要短。

从这个比较中,您可以看到使用编译器优化实际上减少了应用的执行时间。不仅如此,它甚至比原来的应用运行时间(6.378 秒)还要短。这是因为没有编译器选项的原始应用默认为优化的-O1级别,而-O3优化级别甚至比原始应用更高,因此它的运行时间最短也就不足为奇了。

下面的应用是原始应用 NDKExp 的修改和扩展版本。步骤如下。

步骤 1:修改应用的 Android 部分

  1. Modify the main layout file. Add a text view and a button in a layout. Set their Text and ID properties, and adjust their size and position, as shown in Figure 10-4.

    9781430261308_Fig10-04.jpg

    图 10-4 。扩展版本 NDKExp 布局

  2. Modify the class source code file MainActivity.java of the main layout. The main changes are as follows:

       ...
    13.  public class MainActivity extends Activity {
    14.    private JavaTaskThread javaTaskThread = null;
    15.    private CCodeTaskThread cCodeTaskThread = null;
    16.    private AnotherCCodeTaskThread anotherCCodeTaskThread = null;
    17.    private TextView tv_JavaTaskOuputInfo;
    18.    private TextView tv_CCodeTaskOuputInfo;
    19.    private TextView tv_AnotherCCodeTaskOuputInfo;
       ...
    182.   static {
    183.      System.loadLibrary("ndkexp_extern_lib");
    184.      System.loadLibrary("ndkexp_another_lib");
    185.   }
    186\. }
    

    在第 16 行和第 19 行,为新的 Start Other C Task 按钮添加必需的变量。

    关键的变化在第 184 行。除了加载原始共享库文件之外,这些文件还会被添加到另一个库文件中。

  3. In the project, add a thread task class  with the name AnotherCCodeTaskThread that calls a local function to calculate π. Its source code file AnotherCCodeTaskThread.java reads as follows:

    1.  package com.example.ndkexp;
    2.  import android.os.Handler;
    3.  import android.os.Message;
    
    4.  public class AnotherCCodeTaskThread extends Thread {
    5.    private Handler mainHandler;
    6.    public static final int MSG_FINISHED = 3;
          // The message after the end of the task
    7.    private native double anotherCCodeTask();
         // Calling external C functions to complete computing tasks
    
    8.    static String msTimeToDatetime(long msnum){
    9.      long hh,mm,ss,ms, tt= msnum;
    10.     ms = tt % 1000; tt = tt / 1000;
    11.     ss = tt % 60; tt = tt / 60;
    12.     mm = tt % 60; tt = tt / 60;
    13.     hh = tt % 60;
    14.     String s = "" + hh +"Hour "+mm+"Minute "+ss + "Second " +     ms +"Millisecond";
    15.     return s;
    16.     }
    
    17.    @Override
    18.    public void run()
    19.    {
    20.    double pi = anotherCCodeTask();
           // Calling external C function to complete the calculation
    21.        Message msg = new Message();
    22.        msg.what = MSG_FINISHED;
    23.        Double dPi = Double.valueOf(pi);
    24.        msg.obj = dPi;
    25.        mainHandler.sendMessage(msg);
    26.    }
    
    27.    public CCodeTaskThread(Handler mh)
    28.    {
    29.    super();
    30.    mainHandler = mh;
    31.    }
    32.  }
    

    前面的代码几乎是抄录了CCodeTaskThread类的代码。它只通过调用另一个名为anotherCCodeTask的外部 C 函数来完成第 20 行的计算任务,做了一点处理。在第 7 行中,它为本地函数提供了适当的指令,并在第 6 行中更改了消息类型的值。这样,它通过前面的 C 函数将自己与完整的消息区分开来。第 4 行显示了从thread类继承而来的task类。

  4. 在 Eclipse 中构建项目。类似地,在这里,你只有一个构建,而不是运行。

第二步:修改 mycomputetask.c 的 Makefile 文件,重新构建库文件

  1. Modify the Android.mk file under the jni directory of the project, which reads as follows:

    1.  LOCAL_PATH := $(call my-dir)
    2.  include $(CLEAR_VARS)
    3.  LOCAL_MODULE    := ndkexp_extern_lib
    4.  LOCAL_SRC_FILES := mycomputetask.c
    5.  LOCAL_CFLAGS    := -O0
    6.  include $(BUILD_SHARED_LIBRARY)
    

    与原来的应用不同,在第 5 行中,您添加了传递给gcc的命令LOCAL_CFLAGS的参数。值-O0表示没有优化。

  2. 将 C 代码文件编译成项目的lib目录下的.so库文件。

  3. 将项目的lib目录中的.so库文件(在本例中,该文件是libndkexp_extern_lib.so)保存到磁盘中的某个其他目录中。以下操作将删除这个.so库文件。

步骤 2:为 anotherCCodeTask 函数编写 C 实现代码

从上一节复制cCodeTask功能的处理步骤。然后将文件编译成.so库文件。主要步骤如下:

  1. Create a C interface file. At the command line, go to the project directory, and then run the following command:

    E:\temp\Android Dev\workspace\NdkExp> javah -classpath "D:\Android\android-sdk\platforms\android-15\android.jar";bin/classes  com.example.ndkexp.AnotherCCodeTaskThread
    

    该命令将生成一个名为project com_example_ndkexp_AnotherCCodeTaskThread.h的目录文件。该文件的主要内容有:

       ...
    23.  JNIEXPORT jdouble JNICALL Java_com_example_ndkexp_AnotherCCodeTaskThread_anotherCCodeTask
    24.  (JNIEnv *, jobject);
       ...
    

    第 23–24 行定义了本地函数anotherCCodeTask原型。

  2. According to the previously mentioned header files in the project jni directory, establish corresponding C code files, in this case named anothertask.c, the content of which is based on the mycomputetask.c modification. The modification as follows:

    1.  #include <jni.h>
    2.  jdouble Java_com_example_ndkexp_AnotherCCodeTaskThread_anotherCCodeTask (JNIEnv* env,  jobject thiz )
    3.  {
       ...
    17\. }
    

    mycomputetask.c的第二行被替换为anotherCCodeTask函数的原型。这是从关于.h文件的函数原型的描述中复制的同一个函数原型,它是在前面的步骤中创建的,只做了微小的修改。最终的形式可以在代码行 2 中看到。

  3. Modify the Android.mk file under the jni directory in the project, as follows:

    1.  LOCAL_PATH := $(call my-dir)
    2.  include $(CLEAR_VARS)
    3.  LOCAL_MODULE       := ndkexp_another_lib
    4.  LOCAL_SRC_FILES    := anothertask.c
    5.  LOCAL_CFLAGS       := -O3
    6.  include $(BUILD_SHARED_LIBRARY)
    

    在第 4 行,该值被替换为新的 C 代码文件anothertask.c。在第 3 行,该值被替换为新的库文件名,这与System.loadLibrary函数的参数一致(在项目中的MainActivity.java文件的第 184 行)。在第 5 行,用于传递的gcc命令的LOCAL_CFLAGS参数的值被替换为-O3,这表示最高级别的优化。

  4. 将 C 代码文件编译成项目的lib目录下的.so库文件。然后你可以看到项目中lib目录下的libndkexp_extern_lib.so文件消失了,取而代之的是一个新生成的libndkexp_another_lib.so文件。保存库文件是非常重要的。

  5. Put the previously saved libndkexp_extern_lib.so library file back into the libs directory in the project.

    现在目录中有两个文件。您可以使用dir命令来验证:

    E:\temp\Android Dev\workspace\NdkExp>dir libs\x86
    2013-02-28  00:31             5,208 libndkexp_another_lib.so
    2013-02-28  00:23             5,208 libndkexp_extern_lib.so
    
  6. 您重新部署并运行项目。

运行界面的应用如本章前面的图 10-3 所示。

编译器优化扩展的多种情况比较

通过本章中的案例研究,您将获得关于编译器优化效果的第一手经验。任务执行时间从优化前的 7.321 秒缩短到优化后的 5.632 秒。我们只比较了gcc -O3-O0命令的区别。您可以通过在编译mycomputetask.canothertask.c两个文件时修改Android.mk文件来扩展这种配置,然后继续比较使用不同编译器命令选项时优化效果的差异。要修改Android.mk文件,只需要修改LOCAL_CFLAGS项的值即可。您可以选择gcc命令的多个选项进行比较。这里有几个例子来说明这个过程。

示例:使用 SSE 指令比较优化结果

可以让启动 C 任务按钮对应mycomputetask.c编译的Android.mk文件:

LOCAL_CFLAGS     := -mno-sse

并使启动其他 C 任务按钮对应于anothertask.c编译的Android.mk文件:

LOCAL_CFLAGS     := -msse3

前者告诉编译器不要编译 SSE 指令;后者允许编译器编程为 SSE3 指令。选择 SSE3 指令的原因是 SSE3 是英特尔凌动处理器支持的最高级别的指令。

运行应用的结果如图 10-5 所示。

9781430261308_Fig10-05.jpg

图 10-5 。应用 NDKExp 的编译器 SSE 指令优化比较

从图 10-5 可以看出,同样的任务使用 SSE 指令执行时间比不使用 SSE 指令要短。执行时间从原来的 6.759 秒缩短到 5.703 秒。

需要注意的是,在这个例子中,我们完成了对Android.mk文件的修改,并重新运行ndk-build来生成.so库文件。我们立即部署并运行了 NDKExp 项目,但发现我们无法达到预期的效果。原因是因为只有.so库文件被更新。Eclipse 的项目经理没有意识到项目需要重新构建。结果,apk没有得到更新,目标机器码上的 NDKExp 应用也不会更新原代码。考虑到这种情况,您可以使用以下方法来避免此问题:

  1. 从手机上卸载应用。
  2. 从宿主项目目录的bin子目录中删除classes.dexjarlist.cacheNdkExp.apk文档。
  3. 在 Eclipse 中删除项目。
  4. 在 Eclipse 中,重新导入项目。
  5. 最后,重新部署并运行项目,这样您就可以获得想要的效果。

此示例仅比较 SSE 指令的效果。有兴趣的读者可以尝试其他的gcc编译器选项,比较它们的运行结果。

此外,在前面的例子中,我们只关心 NDK 效应,所以 C 函数仍然使用单线程代码。有兴趣的读者可以将本章学到的 NDK 优化知识与上一章的多线程优化结合起来,将 C 函数改为多线程,与编译器优化一起实现。在各种应用中编写这样一套优化技术肯定会让应用运行得更快。

概观

与第九章类似,本章主要关注基于英特尔 x86 架构的 Android NDK 的代码和技术方面。我们创建了一个简单的 Android NDK 应用,展示了所有这些部分是如何连接并在 x86 模拟器上运行的。本章还提供了 Android NDK 编译器可以为其开发者提供的优化的高级视图。然后我们看了英特尔的集成性能原语库(IPP),这是一个提供给 x86 开发人员的高性能库。最后,我们用一些如何使用所有讨论的工具和技巧的例子来结束这一章。

十一、在 Windows、Mac OS 和 Linux 上使用英特尔硬件加速执行管理器来加速 x86 仿真上的 Android

我不害怕电脑。我担心他们的缺乏。

艾萨克·阿西莫夫

一旦安装了 Android SDK,运行了 Android 模拟器,并且您的开发环境设置符合您的喜好,前面还有一个挫折:Android 模拟器可能非常慢。尤其是在测试和调试较大的应用时,模拟器的速度是开发中一个明显的瓶颈。最佳解决方案是采用英特尔虚拟化技术(英特尔 VT)的英特尔硬件加速执行管理器(英特尔 HAXM) 。如果您的开发系统使用受支持的英特尔处理器之一,这种硬件辅助虚拟化引擎或虚拟机管理程序将支持闪电般的 Android 仿真。

介绍

该软件:

  • 使用英特尔 VT,在特定的英特尔处理器上提供。
  • 提供英特尔 x86 Android 虚拟设备的硬件加速仿真。
  • 与 Android SDK 集成。
  • 英特尔 HAXM 要求安装 Android SDK(版本 17 或更高)。为了获得最佳性能,建议使用 SDK 2.0 版或更高版本。
  • 最新的 Windows 或 Mac OS X (32/64 位)。

重要的是,英特尔 HAXM 不能在没有英特尔处理器的系统上使用,也不能在没有所需硬件特性的英特尔处理器上使用。要确定您的英特尔处理器的性能,请访问http://ark.intel.com/。此外,英特尔 HAXM 只能为仿真器 x86 加速 Android x86 系统映像。HAXM 已通过英特尔在http://www.intel.com/software/android提供的 x86 系统映像的验证。

下载英特尔 HAXM

英特尔 HAXM 可以通过 Android SDK 管理器(推荐)安装,也可以通过从英特尔网站下载安装程序来手动安装。

image 注意英特尔 HAXM 不会自动检查更新。要获得最新版本,请使用 Android SDK 管理器(推荐)或从英特尔软件网络 Android 开发人员网站下载英特尔 HAXM 软件包。

通过 Android SDK 管理器下载

  1. 启动 Android SDK 管理器。

  2. Under Extras, check the box next to Intel x86 Emulator Accelerator (HAXM), as seen in Figure 11-1.

    9781430261308_Fig11-01.jpg

    图 11-1 。下载英特尔 x86 模拟器加速器(HAXM)

  3. 单击安装软件包按钮。

  4. 查看英特尔公司许可协议。如果您接受这些条款,请选择接受并单击安装。

  5. SDK 管理器会将安装程序下载到主 SDK 目录下的 Tools 目录中。

  6. 提取工具目录中的安装程序,并按照您的平台的安装说明进行操作。

手动下载

  1. 转到http://www.intel.com/software/android
  2. 为您的平台选择英特尔 HAXM 安装程序包。
  3. 提取安装程序,并按照您的平台的安装说明进行操作。

在 Windows 上安装英特尔 HAXM

image 警告如果您的系统不符合系统要求,包括对英特尔处理器特性的支持,如英特尔虚拟化技术(英特尔 VT),英特尔 HAXM 安装将会失败。

  1. http://www.intel.com/software/android或使用 SDK 管理器下载安装包。

  2. 运行安装程序(如果适用,接受 UAC 提示)。

  3. If an older version Intel HAXM is installed, you will see something like Figure 11-2.

    9781430261308_Fig11-02.jpg

    图 11-2 。通知对话框

  4. 单击是升级英特尔 HAXM,或单击否退出安装并保留当前安装的英特尔 HAXM 版本。

  5. You will see a screen like Figure 11-3.

    9781430261308_Fig11-03.jpg

    图 11-3 。HAXM 安装屏幕

    image 您可以通过点击英特尔 HAXM 文档随时访问文档。

  6. 单击下一步。

  7. 阅读英特尔 HAXM 最终用户许可协议(EULA ),如果您同意,接受 EULA 并继续安装英特尔 HAXM。

  8. You will be prompted to adjust the amount of RAM allocated to Intel HAXM, as shown in Figure 11-4.

    9781430261308_Fig11-04.jpg

    图 11-4 。HAXM RAM 调整屏幕

    image 注意安装程序也可以作为英特尔 HAXM 的配置工具。要更改内存设置,请再次运行安装程序。

  9. Figure 11-5 confirms your Intel HAXM memory allocation settings.

    9781430261308_Fig11-05.jpg

    图 11-5 。英特尔 HAXM 准备安装

  10. 英特尔 HAXM 安装完成后,单击完成退出安装程序。

英特尔 HAXM 现已安装完毕,可以使用了。要验证英特尔 HAXM 是否正在运行,请打开命令提示符窗口并执行以下命令:

sc query
intelhaxm

如果英特尔 HAXM 正在工作,该命令将显示一条状态消息,指示状态为4 RUNNING

要停止英特尔 HAXM,请使用以下命令:

sc stop
intelhaxm

要启动英特尔 HAXM,请使用以下命令:

sc start
intelhaxm

调整英特尔 HAXM 内存分配

要更改分配给英特尔 HAXM 的内存量,请再次运行安装程序。

image 注意对英特尔 HAXM 内存设置的更改将在英特尔 HAXM 重启后生效。当前运行的模拟器将继续使用以前的内存设置。

英特尔虚拟化技术(英特尔 VT-x)能力

安装英特尔 HAXM 时,您可能会遇到有关英特尔 VT-x 支持的错误。以下情况可能会触发此错误消息:

  • 您的计算机处理器不支持英特尔 VT-x。
  • 英特尔 VT-x 未启用。

不支持英特尔 VT-x

英特尔 HAXM 需要具有英特尔 VT-x 功能的英特尔处理器,并且不能在缺乏该硬件特性的系统上使用。要确定您的英特尔处理器的性能,请访问http://ark.intel.com/

英特尔 VT-x 未启用

在某些情况下,英特尔 VT-x 可能在系统 BIOS 中被禁用,必须在 BIOS 设置工具中启用。要访问 BIOS 设置工具,您需要在计算机启动过程中按某个键。该键取决于使用的 BIOS,但通常是 F2、Delete 或 Esc 键。在 BIOS 设置工具中,英特尔虚拟化可通过术语“虚拟化”、“虚拟化技术”或“虚拟化-d”来识别。请确保启用所有虚拟化功能。有关进入 BIOS 设置和启用英特尔 VT 的具体信息,请联系您的硬件制造商。

小窍门

下面的列表包含了一些使用英特尔 HAXM 驱动程序从 Android 模拟器获得最佳体验的建议:

  • 在 AVD 管理器中为您的映像启用 GPU 加速。HAXM 驱动程序通过处理器中的英特尔虚拟化技术本机执行大多数 CPU 指令,GPU 加速将 OpenGL 调用卸载到主机 GPU。截至 SDK release 19,GPU 加速被谷歌认为是“实验性的”。
  • 从命令行启动模拟器以获得更详细的输出。
  • 使用以下命令启动模拟器:

emulator-x86 –avd <avd name> -partition-size 1024 –gpu on –verbose

  • 1024 的分区大小允许安装 1GB 的应用。这与 AVD 管理器中的 SDCard 大小选项不同,后者指定在仿真器内部分配多少存储空间来存储媒体文件。将 GPU 设置为on将提供更好的图形性能。
  • 确保在控制面板image系统image高级系统设置image环境变量中设置了 GPU 仿真库的Path环境变量。您也可以在每次启动新的命令提示符时手动设置它。如果您使用多个 SDK 安装,建议手动设置它。以下<sdk install location>通常是指:

"c:\Users\<your username>\android-sdk" set PATH=%PATH%;<sdk install location>\tools\lib

  • 安装英特尔 HAXM 时,将驱动程序设置为使用系统中一半的可用内存。例如,如果您的系统安装了 6GB 的内存,则使用 3GB 用于英特尔 HAXM 驱动程序。与系统内存相比,这为 HAXM 驱动程序提供了良好的内存平衡。
  • 创建映像时,不要将设备 RAM 大小选项设置为大于分配给英特尔 HAXM 驱动程序的 RAM 数量。在前面的示例中,设备 RAM 大小不应大于 3GB,因为只有 3GB 分配给了英特尔 HAXM。
  • 32 位系统可选择的英特尔 HAXM 驱动程序的最大内存为 1.6 GB。对于 64 位系统,最大值为 8GB。

有时,当第一次启动一个映像时,它会出现在启动屏幕上。引导过程已完成,但主屏幕没有出现。点击模拟器上的主页按钮,显示主页屏幕。

Mac OS 系统

  1. http://www.intel.com/software/android或使用 SDK 管理器下载安装包。

  2. 打开 DMG 文件,然后运行里面的安装程序。

  3. 如果安装了旧版本的英特尔 HAXM,您会看到一个通知对话框。单击“确定”关闭对话框。然后,您可以退出安装程序以保留当前版本的英特尔 HAXM,或者继续安装并升级您的英特尔 HAXM 版本。

  4. You will see a welcome screen, like Figure 11-6.

    9781430261308_Fig11-06.jpg

    图 11-6 。Mac OS 上的英特尔 HAXM 欢迎屏幕

  5. 单击继续。

  6. 阅读英特尔 HAXM 最终用户许可协议(EULA ),如果您同意,接受 EULA 并继续安装英特尔 HAXM。

  7. You will be prompted to adjust the amount of RAM that will be allocated to Intel HAXM, as shown in Figure 11-7.

    9781430261308_Fig11-07.jpg

    图 11-7 。Mac OS 上的英特尔 HAXM RAM 调整屏幕

  8. Figure 11-8 confirms your Intel HAXM memory allocation settings.

    9781430261308_Fig11-08.jpg

    图 11-8 。Mac OS 上的英特尔 HAXM 完成屏幕

  9. 选择将安装英特尔 HAXM 的驱动器,然后单击继续。

  10. 安装英特尔 HAXM 后,单击关闭退出安装程序。

  11. 英特尔 AXM 现已安装完毕,可以使用了。

要验证英特尔 HAXM 是否正在运行,请打开终端窗口并执行以下命令:

kextstat | grep
intel

如果英特尔 HAXM 运行正常,该命令将显示一条状态消息,指示名为com.intel.kext.intelhaxm的内核扩展已加载。

要停止英特尔 HAXM,请使用以下命令:

sudo kextunload -b
com.intel.kext.intelhaxm

要启动英特尔 HAXM,请使用以下命令:

sudo kextload -b
com.intel.kext.intelhaxm

调整英特尔 HAXM 内存分配

要更改分配给英特尔 HAXM 的内存量,请再次运行安装程序。

image 注意对英特尔 HAXM 内存设置的更改将在英特尔 HAXM 重启后生效。当前运行的模拟器将继续使用以前的内存设置。

移除英特尔 HAXM

要卸载英特尔 HAXM,请打开终端窗口并执行以下命令:

sudo
/System/Library/Extensions/intelhaxm.kext/Contents/Resources/uninstall.sh

系统将提示您输入当前用户密码。按照卸载程序的提示删除英特尔 HAXM。

image 重要提示移除英特尔 HAXM 将禁用所有英特尔 x86 Android 仿真器的加速。现有的 Android 虚拟设备将继续运行,但不再加速。再次安装英特尔 HAXM 将重新启用 Android 模拟器加速。

故障排除

英特尔 HAXM 需要英特尔提供的 Android x86 系统映像。您可以通过 Android SDK 管理器或从英特尔开发人员专区网站手动下载这些图像。

英特尔执行禁用(XD)位功能错误

安装英特尔 HAXM 时,您可能会遇到有关英特尔 XD 支持的错误。

以下情况可能会触发此错误消息:

  • 您的计算机处理器不支持英特尔 XD。
  • 英特尔 XD 未启用。

不支持英特尔 XD

英特尔 HAXM 需要具有执行禁用(XD)位功能的英特尔处理器,不能在缺少此硬件功能的系统上使用。要确定您的英特尔处理器的性能,请访问http://ark.intel.com/

英特尔 XD 未启用

image 注意如果处理器支持,苹果电脑会永久启用英特尔 XD。

如果您收到一条错误消息,指出英特尔 XD 未启用,则您的计算机不符合使用英特尔 HAXM 的最低系统要求。要确定您的英特尔处理器的性能,请访问http://ark.intel.com/

英特尔虚拟化技术(VT-x)能力

安装英特尔 HAXM 时,您可能会遇到有关英特尔 VT-x 支持的错误。

以下情况可能会触发此错误消息:

  • 您的计算机处理器不支持英特尔 VT-x。
  • 英特尔 VT-x 未启用。

不支持英特尔 VT-x

英特尔 HAXM 需要具有英特尔 VT-x 功能的英特尔处理器,并且不能在缺乏该硬件特性的系统上使用。要确定您的英特尔处理器的性能,请访问http://ark.intel.com/

英特尔 VT-x 未启用

image 注意如果处理器支持,苹果电脑会永久启用英特尔 VT-x。

如果您收到一条错误消息,指出英特尔 VT 未启用,则您的计算机不符合使用英特尔 HAXM 的最低系统要求。要确定您的英特尔处理器的性能,请访问http://ark.intel.com/

小窍门

以下列表包含使用英特尔 HAXM 驱动程序从 Android 模拟器获得最佳体验的建议:

  • 在 AVD 管理器中为您的映像启用 GPU 加速。英特尔 HAXM 驱动程序通过处理器中的英特尔虚拟化技术本机执行大多数 CPU 指令,GPU 加速将 OpenGL 调用卸载到主机 GPU。
  • 在终端中使用以下命令启动模拟器:

./emulator-x86 –avd <avd name> -partition-size 1024 –gpu on

  • 1024 的分区大小允许安装 1GB 的应用。这与 AVD 管理器中的 SDCard 大小选项不同,后者指定在仿真器内部分配多少存储空间来存储媒体文件。将 GPU 设置为on将提供更好的图形性能。
  • 确保 GL 库的环境变量设置正确。在终端中使用以下命令设置LD_LIBRARY_PATH变量。修改命令以指向您的 SDK 安装。

export LD_LIBRARY_PATH=<sdk install location>/tools/lib

  • 要在新终端启动时自动运行该命令,您可以将该命令添加到您的∼/.bash_profile脚本中。
  • 安装英特尔 HAXM 时,将驱动程序设置为使用系统中一半的可用内存。例如,如果您的系统安装了 6GB 的内存,则使用 3GB 用于英特尔 HAXM 驱动程序。与系统内存相比,这使得英特尔 HAXM 驱动程序的内存达到了良好的平衡。
  • 创建映像时,不要将设备 RAM 大小选项设置为大于分配给英特尔 HAXM 驱动程序的 RAM 数量。在前面的示例中,设备 RAM 大小不应大于 3GB,因为只有 3GB 分配给了英特尔 HAXM。
  • 在 32 位系统上,英特尔 HAXM 驱动程序的最大内存为 1.6GB。对于 64 位系统,最大内存为 8GB。
  • 有时,当第一次启动一个映像时,它会出现在启动屏幕上。启动过程已完成,但主屏幕没有出现。点击模拟器上的主页按钮,显示主页屏幕。

Linux〔??〕

由于 Google 主要支持 Linux 平台上的 Android 构建,并且许多 Android 开发人员正在 Linux 系统托管的 Eclipse 上使用 AVD,因此 Android 开发人员利用面向 Linux 的英特尔硬件辅助 KVM 虚拟化非常重要,就像面向 Windows 和 IOS 的英特尔 HAXM 一样。要在 Ubuntu 主机平台上启用 KVM,并开始使用支持英特尔硬件辅助虚拟化(hypervisor)的英特尔 Android x86 仿真器,请执行以下步骤。

KVM 安装

第一步是按照 Ubuntu 社区页面的说明(https://help.ubuntu.com/community/KVM/Installation)安装所需的 KVM。要检查您系统的处理器是否支持硬件虚拟化,请使用以下命令:

$ egrep -c '(vmx|svm)' /proc/cpuinfo

如果输出是0,说明你的 CPU 不支持硬件虚拟化。

下一步是安装 CPU 检查器:

$ sudo apt-get install cpu-checker

现在,您可以通过发出以下命令来检查您的 CPU 是否支持 KVM:

$kvm-ok

如果您看到此消息:

"INFO: Your CPU supports KVM extensions
INFO: /dev/kvm exists
KVM acceleration can be used"

这意味着您可以使用 KVM 扩展更快地运行虚拟机。

但是,如果你看到这个:

"INFO: KVM is disabled by your BIOS
HINT: Enter your BIOS setup and enable Virtualization Technology (VT),
and then hard poweroff/poweron your system
KVM acceleration can NOT be used"

您需要进入 BIOS 设置并启用英特尔 VT。

安装 KVM

对于 Ubuntu Lucid (10.04)或更高版本,请使用以下命令:

$ sudo apt-get install qemu-kvm libvirt-bin ubuntu-vm-builder bridge-utils

接下来,将您的<username>帐户添加到kvmlibvirtd组:

$ sudo adduser your_user_name kvm
$ sudo adduser your_user_name libvirtd

安装完成后,您需要再次登录,以便您的用户帐户成为kvmlibvirtd用户组的有效成员。这些组的成员可以运行虚拟机。您可以使用以下命令验证安装是否成功:

$ sudo virsh -c qemu:///system list

如果安装成功,您的屏幕将显示以下内容:

Id Name                              State

从终端直接从 Android SDK 启动 AVD

现在使用以下命令启动 Android for x86 英特尔仿真器,如图 11-9 所示:

$ <SDK directory>/tools/emulator-x86 -avd Your_AVD_Name -qemu -m 2047 -enable-kvm

9781430261308_Fig11-09.jpg

图 11-9 。Linux 上的英特尔 HAXM

只有 64 位的 Ubuntu 才允许你运行 2GB 或更大的内存。我的 64 位 Ubuntu 有 6GB 内存,所以我用了三分之一用于 Android AVD。我的名为Intel_Atom_gingerbread_2.3\. '-qemu'的 AVD 提供了到qemu的选项,-m指定了仿真 Android(也就是 guest)的内存量。如果使用的值太小,可能会因为频繁的交换活动而影响性能。添加-show-kernel查看来自内核的消息。

在 Eclipse 中通过 AVD 管理器启动 AVD

以下是谷歌推荐的程序。如果您从 Eclipse 运行模拟器,请使用基于 x86 的 AVD 运行您的 Android 应用,并包含 KVM 选项:

  1. 在 Eclipse 中,单击您的 Android 项目文件夹,然后选择 Run image Run Configurations。

  2. 在运行配置对话框的左侧面板中,选择您的 Android 项目来运行配置或创建新的配置。

  3. 单击目标选项卡。

  4. 选择您之前创建的基于 x86 的 AVD。

  5. 在附加仿真器命令行选项字段中,输入:

    -qemu -m 2047 -enable-kvm
    
  6. 使用这个运行配置运行您的 Android 项目。

概观

本章介绍了采用英特尔虚拟化技术(英特尔 VT)的英特尔硬件加速执行管理器(英特尔 HAXM)的安装。作为 Android x86 开发人员,这些工具为您提供了最快、最高效的全方位体验。本章包括特定于每个主要操作系统的部分——Windows、Mac OS 和 Linux。这些部分不仅强调了安装过程,还强调了排除一些常见问题的提示和技巧。

十二、通过平台调整执行性能测试和分析应用

我没有失败。我刚刚发现了一万种行不通的方法。

—托马斯·爱迪生

在计算中,硬件加速涉及使用计算机硬件来执行功能,其速度比运行在通用 CPU 上的软件更快。通常,处理器是顺序的,指令是一个接一个执行的。各种各样的技术被用来提高处理性能,硬件加速,正如在 第十一章 中所讨论的,就是其中之一。硬件和软件优化之间的主要区别可以说是抽象层次。由于硬件优化的性质,它们可能比软件优化提供更大的速度提升。硬件加速器是为计算密集型软件代码设计的。

越来越多的开发者使用 FFmpeg 开发 Android 视频应用和 OpenCV 开发图像处理软件,这些都有 NDK 的改编。多媒体应用通常有高性能要求,本章介绍一些 x86 Android 上常见的优化技术。

从您的第一台 x86 全格式视频播放器开始

Android 上内置的编解码程序非常有限,所以开发者使用 FFmpeg 免费开源媒体框架支持全格式解码。FFmpeg 项目包括音频/视频编解码器库和一个用于转换多媒体文件代码的命令行程序,并根据您选择的组件,使用 LGPL 或 GPL 许可证支持跨平台的音频和视频流。它提供录制、转换和流式音频和视频功能。FFmpeg 是用于多媒体 Android 开发的最流行的开源框架;这是研究英特尔架构性能软件调整的良好起点。有关 FFmpeg 项目的更多信息,请访问http://www.ffmpeg.org/

我们将从制作一款全新的全格式 x86 播放器开始。本项目推荐使用开源的tewilove_faplayer 。它基于 VLC 播放器,但是tewilove_faplayer包含了所有需要的组件,而 VLC 播放器必须首先做一个引导来下载所有的组件。VLC 玩家的编译脚本也比tewilove_faplayer复杂。项目 URL 为https://github.com/shaobin0604/faplayer,在这里可以阅读项目笔记,下载 ZIP 文件。tewilove_faplayer可以在 ARM 平台上轻松使用;对于 x86,有一些必要的修改:

  1. 修改vlc_fixups.h。删除__cplusplus,否则会导致编译问题。

  2. 修改\jni\vlc\config.h。添加此处定义的宏:

    #define CAN_COMPILE_MMX  1
    #define CAN_COMPILE_MMXEXT 1
    #define CAN_COMPILE_SSE 1
    #define CAN_COMPILE_SSE2 1
    #define asm __asm__
    #define MODULE_NAME_IS_i422_yuy2_sse2
    #define MODULE_NAME_IS_i420_yuy2_sse2
    #define MODULE_NAME_IS_i420_rgb_sse2
    
  3. 修改libvlcjni.h。删除yuv2rgb;是 ARM NEON 代码,不是 x86 代码。

  4. 修改Application.mk如下:

    APP_ABI := x86
    BUILD_WITH_NEON := 0
    OPT_CPPFLAGS += -frtti –fexceptions
    
  5. 删除ext\ffmpeg中的Android.mk;您必须将其替换为 x86 FFmpeg 版本。

编译 x86 FFmpeg:交叉编译

一般来说,开源程序通常支持交叉编译。FFmpeg 也不例外。这里有一个脚本文件,可以用来在 Linux 和 Android 上构建 FFmpeg:

#!/bin/bash
NDK=$ANDROID_NDK_ROOT  #your ndk root path
PLATFORM=$NDK/platforms/android-14/arch-x86
#PREBUILT=$NDK/toolchains/arm-linux-androideabi-4.4.3/prebuilt/linux-x86
PREBUILT=$NDK/toolchains/x86-4.4.3/prebuilt/linux-x86
function build_one
{
./configure --target-os=linux \
    --prefix=$PREFIX \
    --enable-cross-compile \
    --extra-libs="-lgcc" \
    --arch=x86 \
    --cc=$PREBUILT/bin/i686-android-linux-gcc \
    --cross-prefix=$PREBUILT/bin/i686-android-linux- \
    --nm=$PREBUILT/bin/i686-android-linux-nm \
    --sysroot=$PLATFORM \
    --extra-cflags=" -O3 -fpic -DANDROID -DHAVE_SYS_UIO_H=1 -Dipv6mr_interface=ipv6mr_ifindex -fasm -Wno-psabi -fno-short-enums -fno-strict-aliasing -finline-limit=300 $OPTIMIZE_CFLAGS " \
    --disable-shared --enable-static \
    --extra-ldflags="-Wl,-rpath-link=$PLATFORM/usr/lib -L$PLATFORM/usr/lib -nostdlib -lc -lm" \
    --disable-ffplay --disable-avfilter --disable-avdevice --disable-ffprobe \
--disable-yasm \
    $ADDITIONAL_CONFIGURE_FLAG

make clean
make  -j4 install
}

#x86
CPU=x86
OPTIMIZE_CFLAGS="-march=atom -ffast-math -msse3 -mfpmath=sse"
PREFIX=./android/$CPU
ADDITIONAL_CONFIGURE_FLAG=
build_one

运行这个脚本后,可以使用libavcode.alibavformat.alibavutil.alibswscale.a;将这些库作为预链接静态库链接到你的项目。

x86 FFmpeg 编译:Android.mk

最好交叉编译 FFmpeg 简单快捷。但是如果需要 FFmpeg 来编译Android.mk ,这个还是可以的。 Havlenapetr FFmpeg 可以用来构建这个脚本。

Havlenapetr 是早期的 Android FFmpeg 项目,因此具有相对简单的音频和视频同步功能。适合初学者学习如何在 Android 上移植 FFmpeg。它的项目 URL 是https://github.com/havlenapetr

当你下载了所有的工具,准备工作完成后,就该制作 faplayer x86 了,这是一款全格式的 x86 播放器。你必须首先在设备上播放 1080p MP4 这将使用 Android 默认播放器。软件调谐时,修改PlayerActivity.java中的selectMediaPlayer功能,将useDefault设置为false。安卓 2.3 再玩一次 1080p MP4。正如预期的那样,性能是次优的。幸运的是,通过软件调优,您可以提高性能。(如果您无法在 Android 4.0 上显示图像,请参阅本章后面标题为如何使用 Android 4.0 NDK 显示图像的章节。)

如何确定 CPU 使用率并找到热点

强烈建议将英特尔的图形性能分析器(GPA) 和 VTune amplifier 作为调整工具,但是如果 GPU 的使用不是目标,还有其他调整工具选项,例如 OProfile(这将需要构建系统映像)。

在屏幕上动态显示 CPU 使用情况

CPU 使用情况可以通过/proc/stat查询,在 Linux 上使用命令cat /proc/stat(因此在 Android 上也是如此)。该命令将生成如下所示的字符串:

cpu  4884 440 2841 75755 1681 320 121 0 0 0
cpu02211 212 1639 38296  462 223  90 0 0 0
cpu12673 228 1202 37459 1219  97  31 0 0 0

下面是一些用 Java 写的 CPU 使用函数。您可以使用它们在屏幕上显示 CPU 使用情况:

/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq
/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq
/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
/proc/stat
    public static long getCurCpuFreq() {
        String result = "N/A";
        try {
                FileReader fr = new FileReader(
                           /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq");
                BufferedReader br = new BufferedReader(fr);
                String text = br.readLine();
                br.close();
                fr.close();
                result = text.trim();
        } catch (FileNotFoundException e) {
                e.printStackTrace();
        } catch (IOException e) {
                e.printStackTrace();
        }
        return Long.parseLong(result)/1000;
    }
    public static long getCurUsage() {
        try
        {
             BufferedReader reader = new BufferedReader( new InputStreamReader( new FileInputStream( "/proc/stat" ) ), 1000);
             String load = reader.readLine();
             reader.close();
             String[] toks = load.split(" ");
             long currTotal = Long.parseLong(toks[2]) + Long.parseLong(toks[3]) + Long.parseLong(toks[4])+Long.parseLong(toks[6])
+Long.parseLong(toks[7])+Long.parseLong(toks[8]);
             long currIdle =
Long.parseLong(toks[5]);

             usage =(long) ((currTotal - total) * 100.0f / (currTotal - total + currIdle - idle));
             total = currTotal;
             idle = currIdle;
        }
        catch( IOException ex )
        {
            ex.printStackTrace();
        }
        return usage;
    }

获取函数运行时间

  1. 使用function clock()(必须包含time.h)。

  2. 使用register rdtsc

    static inline uint64_t read_time(void)
    {
        uint32_t a, d;
        __asm__ volatile("rdtsc" : "=a" (a), "=d" (d));
        return ((uint64_t)d << 32) + a;
    }
    

这两个函数返回函数的运行时。

使用 Yasm 获得性能最佳的 x86 库

Yasm 是一个 x86 ASM 汇编程序。在 FFmpeg 移植的情况下,通常建议您在编译 ARM 版本时添加-disable-yasm选项。但是如果你正在编译一个 x86 版本,disable-yasm将会丢弃大量的优化代码,这会显著降低性能。

Yasm 是 NASM 汇编器在“新”BSD 许可下的完全重写(一些部分在其他许可下;详见http://yasm.tortall.net/的 Yasm 网站)。Yasm 目前支持 x86 和 AMD64 指令集;接受 NASM 和 GAS 汇编程序语法;输出二进制、ELF32、ELF64、32 位和 64 位 Mach-O、RDOFF2、COFF、Win32 和 Win64 对象格式。并生成 STABS、DWARF 2 和 CodeView 8 格式的源代码调试信息。

如何使用 Yasm

如果 Yasm 是交叉编译的,那就相当简单了。在 Linux 上下载了 Yasm 源码,安装了 make 之后,运行configuremakemake install就这么简单了。

./configure --enable-shared --prefix=/usr/local
make
make install

然后运行delete -disable-yasm并再次运行构建脚本。但是如果你直接使用Android.mk,注意谷歌 NDK 构建脚本不支持.asm。以下链接是对谷歌 NDK 构建脚本的改进。更多详情可以访问http://software.intel.com/en-us/articles/using-yasm-compiler-on-android-ndkbuild

使用 Yasm 的结果

对于 1080p mp4 软件解码,图 12-1 比较了三种配置。

9781430261308_Fig12-01.jpg

图 12-1 。YASM 比较,单位为纳秒

YASM: -enable yasm –enable asm
NO-YASM: -disable yasm -enable asm
NO-SIMD: -disable yasm -disable asm

启用 Yasm 可以显著提高性能。在前面的例子中,仅通过启用 Yasm,平均时间就减少了 57.6%。(同时启用 Yasm 和 asm 时,平均时间下降了 150%;有关 SIMD 和 ASM 的更多信息,请参见本章的下一节。)下载并安装 Yasm 对于许多种类的开源优化项目来说都是很有价值的一步,包括那些基于 x264、Vp8 和 manifestly x86 的项目。

使用 SSE(英特尔的流式 SIMD 扩展)优化色彩空间转换

图像的颜色空间 以机器可读的格式表示颜色。正如不同的人可能会将文森特·梵高的虹膜描述为“紫色”或“靛蓝”,等离子电视以 RGB 格式呈现颜色 1 而这幅画的海报的打印文件使用 CMYK 代码。 2 要在这些不同的色彩空间格式之间转换图像,必须进行色彩空间转换。视频一般是 YUV 格式;液晶屏是 RGB 格式;而相机输出一般是 nv21 格式。FFmpeg 提供了swscale函数来执行这种转换。对于大型图像文件,色彩空间转换将消耗更多的 CPU 能力,正如您在表 12-1 中看到的。使用英特尔的 SIMD 流扩展(SSE)指令集可以产生 6-16 倍的性能提升。

表 12-1 。SSE 优化

|

3040×1824 NV21-RGB888

|

SWS _ 双线性

|

SWS _ FAST _ 双线性

|
| --- | --- | --- |
| 不使用 Yasm | 425 毫秒 | 158 毫秒 |
| 使用 Yasm | 179 毫秒 | 155 毫秒 |
| 使用 SSE NV21-RGB888 | 27 毫秒 | 27 毫秒 |

SSE (SIMD 技术)是 x86 Android (ARM 有 NEON——也是 SIMD 技术)上最重要的优化技术,尤其是对于多媒体 app。这是因为它提供了全面的性能优化。

什么是 SIMD?

单指令多数据(SIMD) 设备具有多个处理元件,可同时对多个数据点执行相同的操作。大多数现代 CPU 设计包括 SIMD 指令,以提高多媒体性能。英特尔的 Medfield CPUs 支持 MMX、MMX2、SSE、SSE2、SSE3 和 SSSE3,但不支持 SSE4 和 AVX。SIMD 支持也可以在运行时动态检查(参见 FFmpeg 1.0 上的cpuidcpu.c上的函数ff_get_cpu_flags_x86)。

有三种方法可以实现 SIMD 码。

  • C/C++ 语言级的内在函数,用emmintrin.h定义。直到现在,除了 WEBP,很少有开源库使用它。如果 SIMD 代码以这种方式实现,它很容易适应所有的硬件平台。(例如,它可以与 ARM 平台的 NEON 代码互换。)
  • 内嵌汇编程序(使用最广泛)。它不需要单独的组装和链接步骤,比单独的组装器更方便。
  • 单独的汇编程序。它有多种风格(NASM、TASM、MASM 等),文件扩展名为.s.asm。(扩展名为.asm的汇编程序,安卓 NDK 无法正常编译;您必须使用上一节“如何使用 Yasm”中提供的补丁程序)

SIMD 是如何运作的

要添加两个 8 位整数数组,通用 C 代码如下所示:

Int s[16];
for(int i=0;i<16;i++){
     S[i]=data1[i]+data2[i];  //ensure s[i] is 0∼255
}

但是如果你使用 SSE,你只需要:

movups data1,xmm1
movups data2,xmm2
paddusb xmm1,xmm2
movntq xmm2,S

使用一条指令paddusb,可以同时执行 16 个add操作。这听起来很棒,但它实际上有局限性。所有的数据必须组织良好,算法可以矢量化。然而,这确实提高了性能,尤其是对于多媒体应用。

SIMD 有五种类型的指令:

  • 数据移动,如movdmovqmovups
  • 布尔逻辑:psllwpsrlw
  • 数学:paddbpmulhw
  • 比较 : pcmpeqb
  • 数据打包:packssdwpunpcklbwpshufb

数据打包(见图 12-2 和 12-3 )对 SIMD 来说是最困难的部分。在下面两张图中,您可以看到两种不同的操作,它们的组织和结构,以及它们如何执行数据打包过程。

9781430261308_Fig12-02.jpg

图 12-2 。使用 Punpcklbw 进行数据打包

9781430261308_Fig12-03.jpg

图 12-3 。用 Packssdw 打包数据

压缩混洗字节(pshufb)取寄存器中的字节 R =【R0 R1 R2...R15]和 M = [M0 M1 M2...M15]并用 RM0RM1RM2代替 R...rM15;除了如果 M i 的最高位被置位,它用 0 替换第 I 个条目。这显示在以下代码中:

![image

R0 := (mask0 & 0x80) ? 0 : SELECT(a, mask0 & 0x07)
R1 := (mask1 & 0x80) ? 0 : SELECT(a, mask1 & 0x07)
...
R15 := (mask15 & 0x80) ? 0 : SELECT(a, mask15 & 0x0f)

pshufb指令可以根据 128 位掩码将任意 8 位数据放入任意位置。

实施 NV21-RGB SSE 代码

FFmpeg yuv2rgb是 MMX2 码,所以必须先修改成 SSE 码,因为 MMX 是 8 位对齐,SSE 是 16 位对齐。您必须将数据放大到 16 位:

  1. Modify swscale_internal.h and yuv2rgb_mmx.c:

    DECLARE_ALIGNED(8, uint64_t, redDither);
    ==>
    DECLARE_ALIGNED(16, uint64_t, redDither);
    DECLARE_ALIGNED(8, uint64_t, redDither1);
    
    DECLARE_ASM_CONST(16, uint64_t, mmx_redmask) = 0xf8f8f8f8f8f8f8f8ULL;
    ==>
    DECLARE_ASM_CONST(8, uint64_t, mmx_redmask1) = 0xf8f8f8f8f8f8f8f8ULL;
    DECLARE_ASM_CONST(16, uint64_t, mmx_redmask) = 0xf8f8f8f8f8f8f8f8ULL;
    

    现在redDithermmx_redmask可以作为 8 位数据或者 16 位数据。

  2. Change the mov and mm instructions:

    #if HAVE_SSE2
         #define MM1 "%xmm"
         #define MM "%%xmm"
         #define MOVD "movq"
         #define MOVQ "movups"
         #define MOVNTQ "movntps"
         #define SFENCE "sfence"
         #define SIMD8   "16"
    #else
    #if HAVE_MMX2
         #define MM1 "%mm"
         #define MM "%%mm"
         #define MOVD "movd"
         #define MOVQ "movq"
         #define MOVNTQ "movntq"
         #define SFENCE "sfence"
         #define SIMD8 "8"
    #endif
    

    MMX 使用一个mm寄存器,而 SSE 使用一个xmm寄存器。因为 SSE 有 128 位数据长度(16 字节),所以使用 SSE 时数据偏移量是 16(SIMD 8 是 16)。

  3. RGB_PACK24 must be rewritten due to fact that the data length of MMX and SSE are different.

    DECLARE_ASM_CONST(16, uint8_t, rmask1[16]) = {0x00,0x80,0x80,0x01,0x80,0x80,0x02,0x80,0x80,0x03,0x80,0x80,0x04,0x80,0x80,0x05};
    ...
    MOVQ"      "MM""red",          "MM"5 \n"\
    "pshufb    "MANGLE(rmask1)",   "MM"5 \n"\
    MOVNTQ"    "MM"5,              (%1) \n"\
    

    这里用的是pshufb。关键思想是用pshufb把每个 R,G,B 值放到正确的位置,用它得到RGB888数据。以下代码显示了每个 RGB 段由什么组成。例如,RGB0 是 R0、G0 和 B0。

    image

  4. 添加ff_nv2rgb_init_mmx,在ff_get_unscaled_swscale :

        /* yuv2bgr */
        if ((srcFormat==PIX_FMT_YUV420P || srcFormat==PIX_FMT_YUV422P || srcFormat==PIX_FMT_YUVA420P) && isAnyRGB(dstFormat)
            && !(flags & SWS_ACCURATE_RND) && !(dstH&1)) {
            c->swScale= ff_yuv2rgb_get_func_ptr(c);
        }
        /* nv2bgr */
        if (srcFormat==PIX_FMT_NV21 && isAnyRGB(dstFormat)&& !(flags & SWS_ACCURATE_RND) && !(dstH&1)) {
            c->swScale= ff_nv2rgb_get_func_ptr(c);
        }
    

    函数中添加ff_nv2rgb_get_func_ptr

如何使用 Android 4.0 NDK 显示图像

Surface_lockSurface_unlockAndPost可用于 NDK 层,在设备上显示图像。这两个功能在安卓 4.0 上从libsurfaceflinger_client.so移到libgui.so,又移到libui.so

使用nm (Windows 为nm.exe)可以对libui.so进行分析。比如在 Windows 上,可以用nm.exe libui.so >>1.txt,用open 1.txtSurface_lock。可以找到字符串"_ZN7android7Surface4lockEPNS0_11SurfaceInfoEPNS_6RegionE",配合dlsym使用,然后从libui.so获取函数句柄。

    clz = (*env)->GetObjectClass(env, s);
    f_Surface_mSurface = (*env)->GetFieldID(env, clz, "mSurface", "I");
    if (f_Surface_mSurface == 0)
    {
        jthrowable e = (*env)->ExceptionOccurred(env);
        if (e)
        {
            (*env)->DeleteLocalRef(env, e);
            (*env)->ExceptionClear(env);
        }
        f_Surface_mSurface = (*env)->GetFieldID(env, clz, "mNativeSurface", "I");
    }
    (*env)->DeleteLocalRef(env, clz);
    surface = (*env)->GetIntField(env, s, f_Surface_mSurface);

有了这个,就可以添加jni函数上面的代码;它接收 Java 层表面对象,并从该对象获取表面句柄。f_Surface_mSurface和 Surface_lock 和Surface_unlockAndPost函数可用于显示 NDK 图层上的图像。

通用交叉编译脚本

开源配置文件一般可以用于 Android 的交叉编译。以下是 x86 平台的通用脚本。它使用 SSE 来优化 JPEG 编码和解码。

#!/bin/bash

HOSTCONF=x86
BUILDCONF=i686-pc-linux-gnu
NDK=$ANDROID_NDK_ROOT

TOOLCHAIN=$NDK/toolchains/x86-4.4.3/prebuilt/linux-x86
PLATFORM=$NDK/platforms/android-14/arch-x86
PREFIX=/home/lym/libjpeg-turbo-1.2.1/android/x86
ELF=$TOOLCHAIN/i686-android-linux/lib/ldscripts/elf_i386.x

export ARCH=x86
export SYSROOT=$PLATFORM
export PATH=$PATH:$TOOLCHAIN/bin:$SYSROOT
export CROSS_COMPILE=i686-android-linux
export CC=${CROSS_COMPILE}-gcc
export CXX=${CROSS_COMPILE}-g++
export AR=${CROSS_COMPILE}-ar
export AS=${CROSS_COMPILE}-as
export LD=${CROSS_COMPILE}-ld
export RANLIB=${CROSS_COMPILE}-ranlib
export NM=${CROSS_COMPILE}-nm
export STRIP=${CROSS_COMPILE}-strip
export CFLAGS="-I$PLATFORM/usr/include -O3 -nostdlib -fpic -DANDROID -fasm -Wno-psabi -fno-short-enums -fno-strict-aliasing -finline-limit=300 -fomit-frame-pointer -march=i686 -msse3 -mfpmath=sse"
export CXXFLAGS=$CFLAGS
export LDFLAGS="-Wl,-T,$ELF -Wl,-rpath-link=$PLATFORM/usr/lib -L$PLATFORM/usr/lib -nostdlib -lc -lm"

./configure  --enable-shared --host=$HOSTCONF --build=$BUILDCONF  --with-sysroot=$SYSROOT --prefix=$PREFIX
make clean
make  -j4 install

受益于 x86 平台的悠久历史,几乎所有开源项目都在 x86 平台上进行了优化,尤其是在多媒体领域。大量的算术函数被写入 SIMD 代码

(FFmpeg)、vp8、x264 和 OpenCV)。通常,您需要选择正确的源代码并使用正确的编译脚本。必要时,可以在 Linux 上调试 NDK 汇编代码。

使用硬件加速进行测试和分析

在 Android 4.0 NDK 中,视频和音频解码基于 OpenMAX AL 1.0.1,允许软件和硬件解码。由 Khronos 集团开发的 OpenMAX(开放媒体加速)是一个免版税的跨平台 API。它的 C 语言编程接口集为音频、视频和静态图像提供了对象抽象。它允许这些资源在各种平台上轻松移植。它面向处理或消费大量多媒体数据的设备,尤其是嵌入式和移动设备,如智能手机和平板电脑。OpenMAX 有三层接口,如图图 12-4 所示——应用层(AL)、集成层(IL)、开发层(DL)。

9781430261308_Fig12-04.jpg

图 12-4 。OpenMAX 图层

使用集成层(IL) 进行硬件编码

在 Android 4.1 之前,谷歌没有向开发者公开任何硬件编解码接口,所以大量多媒体 app 不得不使用 FFmpeg、x264、VP8 作为视频编解码。尤其是视频编码时,软件编码会占用大部分 CPU 资源(640×480 H.264 编码对于一个 ARM v9 1.2-GHz 双核会占用近 90%的 CPU 资源)。这对 Android 设备的性能是一个巨大的消耗。所以直到 4.1 版本,还没有为 Android 开发出精彩的视频录制应用。开发者和用户的兴趣当然是存在的,但是这样的应用是不可行的。现在,开发人员可以使用 OpenMAX 集成层(IL)来获得硬件编码器接口。(澄清一下,ARM 和 Intel 架构都可以使用这种方法进行硬件编码,但是 ARM 架构会遇到兼容性问题。)OpenMAX IL 由不同的供应商实现,谷歌不保证它的兼容性,因此不能保证它在所有支持 Android 的硬件系统上都能很好地工作。但是对于基于英特尔架构的 Android,兼容性问题已经得到解决。

如何在面向英特尔架构的 Android 上获得 OMX-伊尔界面

Libwrs_omxil_core_pvwrapped.so是 Medfield 英特尔架构平台上的 OMX-IL 接口层。开发人员可以如下加载它来访问 OMX-伊尔接口。

pf_init = dlsym( dll_handle, "OMX_Init" );
pf_deinit = dlsym( dll_handle, "OMX_Deinit" );
pf_get_handle = dlsym( dll_handle, "OMX_GetHandle" );
pf_free_handle = dlsym( dll_handle, "OMX_FreeHandle" );
pf_component_enum = dlsym( dll_handle, "OMX_ComponentNameEnum" );
pf_get_roles_of_component = dlsym( dll_handle, "OMX_GetRolesOfComponent" );

得到这些句柄后,就可以调用pf_component_enumpf_get_roles_of_component来得到正确的硬件编码接口。这里列出了所有的视频编解码接口:

component OMX.Intel.VideoDecoder.AVC
  - role: video_decoder.avc
component OMX.Intel.VideoDecoder.H263
  - role: video_decoder.h263
component OMX.Intel.VideoDecoder.WMV
  - role: video_decoder.wmv
component OMX.Intel.VideoDecoder.MPEG4
  - role: video_decoder.mpeg4
component OMX.Intel.VideoDecoder.PAVC
  - role: video_decoder.pavc
component OMX.Intel.VideoDecoder.AVC.secure
  - role: video_decoder.avc
component OMX.Intel.VideoEncoder.AVC
  - role: video_encoder.avc
component OMX.Intel.VideoEncoder.H263
  - role: video_encoder.h263
component OMX.Intel.VideoEncoder.MPEG4
  - role: video_encoder.mpeg4

可以根据自己的需求选择合适的组件。比如要做 MP4 编码,可以选择OMX.Intel.VideoEncoder.MPEG4,调用pf_get_handle获取硬件 MP4 编码句柄。

OMX-IL 是如何工作的?

为了创建或配置和连接 OpenMAX 组件,应用被编写为集成层(IL)客户端。该 IL 客户端用于调用不同组件的 OpenMAX APIs,如图 12-5 所示。在这个应用中,组件分配视频缓冲区以响应 IL 客户端上的 OMX API。IL 客户端负责从一个组件获取缓冲区,并将它们传递给其他组件。功能OMX_GetParameterOMX_SetParameter用作参数/配置set,而get. OMX_SendCommand用于向组件发送命令,包括启用/禁用端口命令和状态改变命令。OMX_EmptyThisBufferOMX_FillThisBuffer将缓冲区传递给组件。调用pf_get_handle时必须注册OmxEventHandlerOmxEmptyBufferDoneOmxFillBufferDone (OMX_CALLBACKTYPE)

9781430261308_Fig12-05.jpg

图 12-5 。OpenMAX 组件和集成层客户端

分配 OMX 缓冲区并调用OMX_SendCommand设置OMX_StateExecuting状态后,就可以使用FillThisBufferEmptyThisBuffer及其回调函数做硬件编码了。图 12-6 显示了调用顺序。

9781430261308_Fig12-06.jpg

图 12-6 。OMX-伊尔渲染管道

调用FillThisBuffer将一张 raw 图片填充到 OMX 本地缓冲区,调用EmptyThisBuffer让 OMX 组件进行硬件编码;当你完成编码或者当本地输出缓冲区已满时,OMX 组件将调用OnEmptyThisBufferDone来告诉客户端再次执行EmptyThisBuffer。所以一个FillThisBuffer可能会产生几个OnEmptyThisBufferDone的实例。如果 OMX 组件发现输入缓冲区是空的,它将调用OnFillThisBufferDone来告诉客户端填充更多的缓冲区空间。

演示:特效录像机

在本节中,您将使用硬件视频编码器来实现特殊效果视频录像机。这个想法是从相机获取数据,并在相机预览图像中添加一个标记。重新编码时,应将标记记录到视频文件中。这听起来是一个简单的设计,但在 Android 4.1 之前,唯一的选择是软件编码器(FFmpeg 或 x264),这会浪费大量的 CPU 资源。从 Android 4.1 开始,引入了新的类MediaCodec;它与 OMX-IL 相同,但由谷歌实现,谷歌保证其兼容性。

表 12-2 展示了三台录像机的 CPU 使用情况。录像机文件格式为. MP4,使用 MPEG-4 编码器。一般来说,VP8 视频编码对 CPU 的占用最少,其次是 MPEG-4 编码,而 H.264 编码占用的 CPU 资源最多)。

表 12-2 。硬件与软件编码器

image

如果您使用硬件编码器,所需的总 CPU 资源仅为 3.7%(硬件编码–预览),而软件编码器将需要 46.9%。本例中使用的分辨率为 1024×576。如果想要 1080P 的视频录制,软件解决方案是不可能的!

封装一个硬件视频编码器库

下面的代码展示了一个名为libomx.so的动态库。它提供了三个简单的功能— EncInitEncVideoEncRelease。用法也很简单——你调用EncInit初始化硬件编码器,调用EncVideo做硬件编码,调用EncRelease释放硬件编码器。两个主要结构是stEncConfigstEncPic:

–    stEncConfig (use in EncInit)
–         stcfg.id = ENC_MPEG4;      //choose the encoder
–         stcfg.type = ENC_DEFAULT;  //for feature use, now must this value
–         stcfg.w=1080;              //encoding size
–         stcfg.h=1920;
–         stcfg.framerate = 15;      //encoding framerate
–         stcfg.controlrate = enum OMX_VIDEO_CONTROLRATETYPE;
–         stcfg.bitrate = xxxx;      //your bitrate

–    stEncPic(use in EncVideo)
–         pic.w=1080;                //picture size
–         pic.h=1920;
–         pic.stride = 1080;         //picture stride
–         pic.pbuf[0]=pmem;          //yuv420 image data
–         pic.pbuf[1]=pmem+1920*1080;
–         pic.pbuf[2]=pmem+1920*1080/4;

实现相机预览

因为不能使用通用的 Google Android API 来开发这个演示,所以必须实现摄像头预览。我推荐用setPreviewCallbackWithBuffer来获取相机预览数据。虽然setPreviewCallbackWithBuffersetPreviewCallback都可以获得预览数据,但是前者更高效,并且会避免 Java 垃圾收集。

准则:为频繁使用保留足够的内存。这样做可以避免伪 Java 垃圾收集。

在预览回调,你应该传递图像数据到 NDK 层;不要添加不必要的代码。我添加了camera.getParameters().getPreviewSize().width作为例子来说明它消耗了大量的 CPU 资源。

准则:尽可能少地调用任何对象函数。将其值赋给一个变量,并使用该变量。

在 NDK 层,将预览数据从 NV21 转换为 RGB565(或 RGB88,根据您的屏幕配置),然后将数据显示到屏幕上(使用Surface_lockSurface_unlockAndPost)。

用 Traceview 分析 Java 代码

您可以使用 Traceview 来分析 Java 代码的性能。Traceview 已经通过一个新的插件集成到 Eclipse 中。该插件与 DDMS 插件集成在一起,因此使用开始/停止概要分析按钮将直接在 Eclipse 中打开跟踪,而不是启动独立的工具。此外,如果您按住 Ctrl 键单击(在 Mac 上按住 Command 键单击)某个方法,它将在编辑器中打开源文件(您必须添加android:debuggable="true")。

选择正确的包名com.Filters,按开始方法剖析,如图 12-7 所示。等待一会儿,然后停止剖析:你可以在图 12-8 的中看到跟踪结果。

9781430261308_Fig12-07.jpg

图 12-7 。在 Eclipse 中使用过滤器

9781430261308_Fig12-08.jpg

图 12-8 。剖析相机应用

从结果中,很容易看出Camera.getParameters将耗费大部分 CPU 资源(97%)。那就是PreviewCallback里的camera.getParameters().getPreviewSize().width。Traceview 只能分析 Java 代码。如果您想评测 NDK 代码,可以使用英特尔工具 VTune,它可以从http://software.intel.com/en-us/intel-vtune-amplifier-xe获得。

启动记录线程

要在 FFmpeg 中引用来自-example.c的输出,您可以使用它作为基础版本。剩下的工作是确定如何在记录线程中获取视频和声音数据。总体思路是,一旦获得一帧视频(或音频),就可以锁定缓冲区并调用一个记录线程开始工作。然而,这是非常低效的。相机预览线程、音频线程和硬件记录线程的并行性已经被破坏,大多数时候,CPU 只是等待。这就是CircleBuffer类的用武之地;生产者(相机预览线程、音频线程)会谨慎地将缓冲区填充到CircleBuffer(如果缓冲区已满,只需重叠最后一个缓冲区,这样即使记录线程很慢,数据也会一直刷新),消费者会谨慎地从中获取缓冲区(如果缓冲区为空,可以选择获取前一个缓冲区或等待)。

准则:尽可能保持所有线程的并行性。 CircleBuffer 一般是不错的选择。

添加特殊效果

现在,视频原始数据已经在 NDK 图层上处理过了,所以你可以很容易地添加特殊效果,比如视频标记。只需将您的标记图像(Y 数据,丢弃 UV 数据)阿尔法混合到视频原始数据中。这项工作可以由 CPU 完成,也可以由 GPU 完成。事实上,谷歌视频录制也支持有限(非常有限)的视频效果。它使用 GPU 而不是 CPU 来降低 CPU 负载。GPU 渲染(OpenGL-ES)非常复杂,很难理解。即使代码很简单,开发人员也应该对 OpenGL 有深入的了解。这里我只是解释一下 GPU 渲染的一个基本工作流程。

  1. 获取并初始化默认的 EGL 显示。

    EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    eglInitialize(mEglDisplay, &majorVersion, &minorVersion);
    
  2. 创建一个只有一个元素的整数数组。它将保存返回值,该值指示与由configAttributes数组指定的属性相匹配的 EGL 配置的数量。创建一个包含一个元素的EGLConfig数组来存储匹配属性的第一个 EGL 配置。调用eglChooseConfig()并作为参数提供您在步骤 1 中初始化的EGLDisplay对象、指定要匹配的配置属性的数组、第一个匹配的EGLConfig对象的占位符、EGLConfig占位符的大小以及存储匹配的配置数量的num_configs数组。将来自eglConfigs数组的单个配置存储在EGLConfig变量eglConfig中。

    EGLint configAttribs[] = {
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
        EGL_RED_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_BLUE_SIZE, 8,
        EGL_NONE
    };
    eglChooseConfig(mEglDisplay, configAttribs, &config, 1, &numConfigs);
    
  3. 调用eglCreateWindowSurface()创建一个 EGL 表面,并提供eglDisplayeglConfig作为参数,它们是您在步骤 1 和 2 中设置的EGLDisplayEGLConfig的实例。在下面的代码示例中,从一个从Screen类派生的类中调用eglCreateWindowSurface(),该参数将EGLSurface对象绑定到当前屏幕。

    eglCreateWindowSurface(mEglDisplay, config,mNativeWindow.get(), NULL);
    
  4. 调用eglCreateContext()创建一个 EGL 上下文。

    eglCreateContext(mEglDisplay, config, EGL_NO_CONTEXT,contextAttribs);
    
  5. 调用eglMakeCurrent()将 EGL 上下文绑定到 EGL 表面和 EGL 显示。

    eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext);
    
  6. 创建程序和加载着色器到 EGL。你的特效函数需要实现为一个着色器。

    loadShader(GL_FRAGMENT_SHADER, fSrc[i], &fShader);
    createProgram(vShader, fShader, &mProgram[i]);
    
  7. 做渲染。

    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mFrameWidth, mFrameHeight, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, pixels);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, 0);
    
  8. 如果你想看 EGL 的图像,你可以打电话给glReadPixels

其实,你也可以用GLSurfaceView 一个GLSurfaceView提供以下功能:

  • 管理一个 surface,它是一块特殊的内存,可以合成到 Android view 系统中。
  • 管理 EGL 显示,这使 OpenGL 能够呈现到图面中。
  • 接受用户提供的进行实际渲染的渲染器对象。
  • 在专用线程上呈现,以将呈现性能与 UI 线程分离。
  • 支持按需渲染和连续渲染。
  • 可选地包装、跟踪和/或错误检查渲染器的 OpenGL 调用。

有关GLSurfaceView的更多信息,请查看http://developer.android.com/reference/android/opengl/GLSurfaceView.html。你可以在http://www.learnopengles.com/how-to-use-opengl-es-2-in-an-android-live-wallpaper找到它的样品。

**在 Android 4.0 上使用 OpenMAX AL

OpenMAX AL API 通过为系统的媒体回放和录制功能提供通用抽象,为应用级多媒体解决方案提供跨一系列平台的可移植性。API 围绕一组高级对象组织这种抽象。应用从一个“引擎”对象获取所有对象,该对象封装了一个 OpenMAX AL 会话,并作为所有其他对象的保护伞。

使用原生多媒体 API (OpenMAX AL)的优势

从 Android 4.0 开始,谷歌包括了基于 Khronos group OpenMAX AL 1.0.1 标准的 Android 原生多媒体 API,截至 Android API level 14 (Android 平台版本 4.0)及更高版本。它为低级流式多媒体提供了直接、高效的路径。对于需要在将媒体数据传递给平台进行呈现之前保持对媒体数据的完全控制的应用来说,新路径是理想的。

例如,媒体应用现在可以从任何来源检索数据,应用专有的加密/解密,然后将数据发送到平台进行显示。应用现在还可以将处理后的数据作为 MPEG-2 传输流格式的音频/视频内容的多路复用流发送到平台。该平台对内容进行解复用、解码和呈现。音频轨道被渲染到活动的音频设备,而视频轨道被渲染到SurfaceSurfaceTexture。当渲染到一个SurfaceTexture 流格式时,应用可以使用 OpenGL 对每一帧应用后续的图形效果。

image 注意虽然它基于 OpenMAX AL,但 Android 原生多媒体 API 并不是 OpenMAX AL 1.0.1 配置文件(媒体播放器或媒体播放器/记录器)的符合实现。这是因为 Android 没有实现任何一个配置文件所需的所有特性。在接下来的“Android 扩展”一节中描述了任何已知的 Android 行为与规范不同的情况。Android OpenMAX AL 实现具有有限的功能,主要用于某些对性能敏感的本地流媒体应用,如视频播放器。表 12-3 表示 Android 的 OpenMAX AL 实现支持的对象和接口。阴影单元格表示该功能受支持。

表 12-3 。Android 的 OpenMAX AL 实现支持的对象和接口

image

演示:流媒体播放器

Google 提供了一个流媒体播放器的样本。您可以在您的android-sdk文件夹中查看samples\native-media\jni \native-media-jni.c中的样本。函数Java_com_example_nativemedia_NativeMedia_createStreamingMediaPlayer将创建一个本地流媒体播放器。

用法很简单,但是你要注意两点:

  • 视频源必须是 NativeWindow。可以调用ANativeWindow_fromSurfaceSurface获取原生窗口,它是从 Java 层传过来的。而Surface必须是GLSurfaceView才能保证硬件渲染。
  • 用于填充流缓冲区的寄存器XA_ANDROIDBUFFERQUEUEEVENT_PROCESSED回调。这个演示是从一个文件中读取缓冲区,但是你也可以从网上读取缓冲区,这样你就可以实现一个 P2P 流媒体播放器。

使用强大的媒体 API:Android 4.1 上的 MediaCodec

Android 有一个很棒的媒体库,允许各种强大的动作。直到最近,还没有编码和解码音频/视频的方法,这使得开发人员几乎可以做任何事情。幸运的是,Jelly Bean 版本引入了android.media.MediaCodec API。它的设计遵循与 OpenMAX(媒体行业的知名标准)相同的原则和架构,从纯粹的高级媒体播放器过渡到编码器/解码器级别。

示例代码:音频解码器

此示例代码显示了如何实现解码器。它使用两个类— MediaCodecMediaExtractor. MediaExtractor来促进从数据源中提取解复用的(通常是编码的)媒体数据。MediaCodec当然是用作低级编解码器。

首先你应该使用MediaExtractor 来获取媒体格式:

MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(sampleFD.getFileDescriptor(),sampleFD.getStartOffset(), sampleFD.getLength());
MediaFormat format = extractor.getTrackFormat(0);

其次,可以创建MediaCodec并进行配置。

MediaCodec codec;
ByteBuffer[] codecInputBuffers;
ByteBuffer[] codecOutputBuffers;

MediaCodec codec = MediaCodec.createByCodecName(name);
codec.configure(format, null,null,0);  //no display, so surface is null
codec.start();

最后,你做解码。像 OMX-伊尔一样,它有两个港口。你要调用dequeueInputBuffer发送解码缓冲到MediaCodec,调用dequeueOutputBuffer?? 接收外部缓冲。

int inputBufIndex = codec.dequeueInputBuffer(TIMEOUT_US);
if (inputBufIndex >= 0) {
    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
    int sampleSize = extractor.readSampleData(dstBuf, 0);
    long presentationTimeUs = 0;
    if (sampleSize < 0) {
            sawInputEOS = true;  sampleSize = 0;
    } else {
            presentationTimeUs = extractor.getSampleTime();
    }
    codec.queueInputBuffer(inputBufIndex, 0, sampleSize,
                    presentationTimeUs,
                    sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
    if(!sawInputEOS){
            extractor.advance();
    }
}
final int res = codec.dequeueOutputBuffer(info, TIMEOUT_US);
if(res >= 0){
        int outputBufIndex = res;
        ByteBuffer buf = codecOutputBuffers[outputBufIndex];
        final byte[] chunk = new byte[info.size];
        buf.get(chunk);
        buf.clear();
        if(chunk.length > 0){
               audioTrack.write(chunk,0,chunk.length);
        }
        codec.releaseOutputBuffer(outputBufIndex, false);
        if((info.flags && MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0){
                sawOutputEOS = true;
        }
} else if(res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED){
        codecOutputBuffers = codec.getOutputBuffers();

} else if(res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
        final MediaFormat offormat = codec.getOutputFormat();
mAudioTrack.setPlaybackRate(oformat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
}

在 NDK 使用 media codec

MediaCodec是一个 Java 层类,但是你必须用 C 代码进行解码(或编码),这是在 NDK 层。因此,在 NDK 中调用 Java 类是很重要的。这可以通过使用jni功能FindClass来完成。

这里显示了FindClass的示例代码:

jclass audio_record_class = jni_env->FindClass("android/media/AudioRecord");
int size = jni_env->CallStaticIntMethod(audio_record_class
,jni_env->GetStaticMethodID(audio_record_class,"getMinBufferSize", "(III)I")
              ,prefered_rate
              ,2/*CHANNEL_CONFIGURATION_MONO*/
              ,2/*  ENCODING_PCM_16BIT */);

用 surface 配置MediaCodec将提供最佳性能,因为它可以使用硬件渲染。您可以引用本地媒体(例如,OpenMAX AL)来添加表面(或 GLSurfaceView)。如果你使用的是普通的 surface,你可以使用 textview 作为字幕,imageview 作为播放栏。如果您使用的是 GLSurfaceView,可以扩展这个类并实现自己的渲染器。

概述

目前只有两种硬件加速技术可以用在 Android 应用层:OpenGL 和 OpenMAX。OpenGL 包括 NDK 中的 GLSurfaceView 和 OpenGL-ES 1.0 和 2.0,它通常用作渲染器或用于多媒体效果处理。OpenMAX 包括 NDK 的 OpenMAX AL、MediaCodec 和 OMX-IL(不是 Google 代码,但必须由作者实现)。每种技术都有使用场景和适用的 Android 版本。到目前为止,流行的 Android 版本是 2.3 和 4.0,所以本章只介绍了从 Android 2.3 到 Android 4.1 的版本。表 12-4 表示哪些硬件加速器可以与哪个版本的 Android 配合使用。

表 12-4 。硬件加速器与安卓版本的兼容性

image

OpenGL 比较复杂但是使用场景相对固定(视频效果和图像处理),所以表格只列出了 OpenMAX 的用法。

Android 4.1 MediaCodec 是多媒体应用的重要更新。它让应用能够在编码前或解码后处理图像。即使在 Android 4.1 之前,在英特尔架构上使用 Android 也可以获得这种能力,在 4.1 之后,硬件加速仍然可以提供越来越好的效果。

1 红绿蓝(RGB)码是基于光的三原色的加色模型。虹膜的颜色表示为 69、102、137。

2 虹膜的 CMYK 值为 50、26、0、46。**

十三、附录 A:参考

第一章:Android 操作系统的历史和发展

起源

http://www.webcitation.org/5wk7sIvVb

http://en.wikipedia.org/wiki/Andy_Rubin

2007 年首次发布 Android 版本

http://www.openhandsetalliance.com/press_110507.html

http://www.cbsnews.com/2100-500395_162-6209772.html

http://oxrep.oxfordjournals.org/content/17/2/248.short

http://www.google.com/intl/en/policies/terms/

安卓是什么?

http://en.wikipedia.org/wiki/Android_(operating_system)#cite_note-AndroidInc-6

http://books.google.com/books?hl=en&lr=&id=h0lltXyJ8aIC&oi=fnd&pg=PT7&dq=linux&ots=gN3lF-b7OW&sig=ticDFAx0zLF3ocyAyAZUtk8oink#v=onepage&q=linux&f=false

http://developer.android.com/guide/basics/what-is-android.html

http://kebomix.wordpress.com/2010/08/17/android-system-architecture/

http://os.ibds.kit.edu/downloads/sa_2010_braehler-stefan_android-architecture.pdf

http://www.scandevconf.se/db/Marakana-Android-Internals.pdf

http://en.wikipedia.org/wiki/WebKit

http://developer.android.com/about/versions/index.html

开放手机联盟(OHA)

http://en.wikipedia.org/wiki/Open_Handset_Alliance

http://www.openhandsetalliance.com/oha_faq.html

http://www.openhandsetalliance.com/oha_overview.html

http://www.openhandsetalliance.com/oha_members.html

http://www.acronyms.net/terms/o/Open-Handset-Alliance/index.pdf

Android 开源项目(AOSP)

http://www.springerlink.com/content/m0136q318k2p4361/

http://source.android.com/about/index.html

http://source.android.com/about/philosophy.html

http://developer.android.com/about/dashboards/index.html

Android 版本

http://en.wikipedia.org/wiki/Astro_(operating_system)#Android_1.0

http://news.cnet.com/8301-30686_3-10385806-266.html

http://reviews.cnet.com/8301-19736_7-20016542-251/a-brief-history-of-android-phones/

http://developer.android.com/about/dashboards/index.html

第二章:移动设备和操作系统的前景

移动领域的竞争

http://www.mobiledevicemanager.com/mobile-device-statistics/250-million-android-devices-in-use/

http://www.engadget.com/2012/07/02/comscore-may-2012-smartphone/

ios

http://en.wikipedia.org/wiki/IOS

http://downloadsquad.switched.com/2010/08/12/camera-out-of-app-store-after-revealing-volume-button-snap-tric

http://www.iphonehacks.com/2010/12/quick-snap-camera-plus-app-pulled-from-app-store-for-using-volume-buttons-to-activate-camera-shutter.html

http://en.wikipedia.org/wiki/IPod_Touch

http://en.wikipedia.org/wiki/IPhone

http://en.wikipedia.org/wiki/IPad

米戈

http://en.wikipedia.org/wiki/MeeGo

http://en.wikipedia.org/wiki/Tizen

黑莓

http://en.wikipedia.org/wiki/BlackBerry

windows 手机

http://en.wikipedia.org/wiki/Windows_Phone

http://en.wikipedia.org/wiki/Windows_Mobile

智能移动终端操作系统

http://en.wikipedia.org/wiki/Symbian

安卓之前

http://www.hongkiat.com/blog/evolution-of-mobile-phones/

http://en.wikipedia.org/wiki/Smartphone

http://en.wikipedia.org/wiki/IBM_Simon

http://en.wikipedia.org/wiki/Nokia_9000

http://en.wikipedia.org/wiki/Kyocera_6035

http://www.bitrebels.com/technology/the-evolution-of-smartphones-infographic/

http://researchinmotion.wikia.com/wiki/BlackBerry_5810

移动市场

http://www.eweek.com/c/a/Mobile-and-Wireless/10-Smartphones-That-Failed-to-Inspire-Buyers-In-2010-696525/

http://blog.pricesbolo.com/index.php/tag/10-smartphones-that-failed-in-2010/

http://timesofindia.indiatimes.com/tech/itslideshow/7010501.cms

http://timesofindia.indiatimes.com/tech/itslideshow/7010503.cms

http://timesofindia.indiatimes.com/tech/itslideshow/6519089.cms

http://en.wikipedia.org/wiki/List_of_best-selling_mobile_phones#2010

摩托罗拉 i1

http://www.intomobile.com/2010/08/02/review-boost-mobile-motorola-i1-does-prepaid-android-work-well/

http://en.wikipedia.org/wiki/Motorola_i1

http://reviews.cnet.com/smartphones/motorola-i1-boost-mobile/4505-6452_7-34117412-2.html

http://www.slashgear.com/motorola-i1-set-to-crash-onto-sprint-on-july-25th-1994629/

http://www.engadget.com/2010/01/20/motorola-launching-20-30-android-phones-in-2010/

机器人 X

http://sparxoo.com/2010/06/21/motorola-droid-x-heats-up-competition-with-apple/

http://en.wikipedia.org/wiki/Droid_X

http://www.pcworld.com/article/201259/Droid_X_Sells_Out_Despite_Verizon_Preparation.html

http://www.androidcentral.com/verizon-stores-selling-out-droid-xs

黑莓火炬

http://dvice.com/archives/2011/01/why-the-blackbe.php

http://gizmodo.com/5614843/the-blackberry-torchs-biggest-failure-rims-ridiculous-expectations

http://www.ixibo.com/balckberry-torch-a-failure-against-iphone-and-android/

http://www.zdnet.com/blog/btl/blackberry-torch-best-blackberry-ever-fails-to-generate-buzz/37573

http://business.financialpost.com/2010/08/17/rim-slides-on-sluggish-blackberry-torch-sales/

http://www.thestar.com/business/companies/rim/article/862297--torch-ignites-research-in-motion

苹果手机

http://www.slideshare.net/bkiprin/apples-iphone-launch-marketing-strategy-analysis-2858373

http://www.scribd.com/doc/21275028/Apple-iPhone-Marketing-Plan

http://ezinearticles.com/?iPhone-Marketing-Strategy&id=4718557

http://en.wikipedia.org/wiki/IPhone

http://www.techiewww.com/marketing/apple-iphone-marketing-strategy

移动市场:趋势

http://socialmediatoday.com/gonzogonzo/495583/great-trends-mobile-infographic

http://smallbiztrends.com/2012/05/mobile-trend-key-things.html

http://mashable.com/2012/04/30/mobile-trends-brands-marketing/

http://mashable.com/2012/08/22/mobile-trends-ecommerce/

http://www.clickz.com/clickz/column/2168103/mobile-trends-watch

http://memeburn.com/2011/12/five-mobile-trends-to-look-out-for-in-2012/

位置

http://blogs.jpost.com/content/2012-mobile-trends-commerce-product-ecosystems-location-integration

https://discussions.apple.com/thread/3374979?start=0&tstart=0

http://www.itbusinessedge.com/slideshows/show.aspx?c=87261&slide=2

平均使用

http://thenextweb.com/us/2010/05/04/twitter-facebook-soar-myspace-sags-market-share/

贸易

http://mashable.com/2012/08/22/mobile-trends-ecommerce/

http://www.itbusinessedge.com/slideshows/show.aspx?c=87261&slide=5

http://www.oracle.com/us/products/applications/web-commerce/ecommerce-trends-2012-1504949.pdf

http://www.fortune3.com/blog/2012/01/us-m-commerce-sales-2010-2015-statistics/

第三章:超越移动应用——技术基础

连接的设备

http://www.intel.com/p/en_US/embedded/innovation/connectivity/johnson-article-connectivity

http://www.intel.com/content/www/us/en/home-users/get-more-from-your-devices-with-connecting-apps-from-intel.html

http://newsroom.intel.com/community/intel_newsroom/blog/2012/03/06/new-intel-server-technology-powering-the-cloud-to-handle-15-billion-connected-devices

http://venturebeat.com/2011/10/17/intel-execs-predicts-15b-devices-will-be-connected-to-the-internet/

家庭计算

http://www.apartmenttherapy.com/how-many-americans-have-multip-143096

汽车的

http://www.wired.com/autopia/2012/06/gps-devices-are-dead/

http://lifehacker.com/5626711/the-best-android-apps-for-your-car

特殊要求

加固

http://en.wikipedia.org/wiki/Rugged_computer

http://en.wikipedia.org/wiki/IP_Code

医学的

http://medicalconnectivity.com/2011/04/03/emr-integration-for-medical-devices-the-basics/

http://en.wikipedia.org/wiki/Electronic_medical_record

安全通信

http://en.wikipedia.org/wiki/Type_1_product

http://en.wikipedia.org/wiki/Federal_Information_Processing_Standard

我们互联世界的网络纤维

蜂窝网络

http://en.wikipedia.org/wiki/Cellular_network

http://en.wikipedia.org/wiki/GSM

http://en.wikipedia.org/wiki/Code_division_multiple_access

http://en.wikipedia.org/wiki/Multimedia_Messaging_Service

http://en.wikipedia.org/wiki/Short_Message_Service

开放移动联盟

http://en.wikipedia.org/wiki/Open_Mobile_Alliance

无线电

http://en.wikipedia.org/wiki/Wi-Fi

http://en.wikipedia.org/wiki/Bluetooth#Pairing_and_bonding

移动界面

触摸屏

http://www.knowyourmobile.com/features/392511/touchscreen_lowdown_capacitive_vs_resistive.html

http://www.goodgearguide.com.au/article/355922/capacitive_vs_resistive_touchscreens/

http://www.knowyourcell.com/features/687370/touchscreen_lowdown_capacitive_vs_resistive.html

http://en.wikipedia.org/wiki/Touchscreen

电阻的

http://en.wikipedia.org/wiki/Resistive_touchscreen

振动传感器(触觉)

http://en.wikipedia.org/wiki/Haptic_technology

http://en.wikipedia.org/wiki/Vibrating_alert

加速计

http://en.wikipedia.org/wiki/Accelerometer

倾斜传感器

http://en.wikipedia.org/wiki/Tilt_sensor

硬件按钮

第四章:Android 开发——业务概述和考虑

市场占有率

http://techcrunch.com/2012/11/02/idc-android-market-share-reached-75-worldwide-in-q3-2012/

http://www.businessinsider.com/android-market-share-2012-11

http://www.huffingtonpost.com/2012/09/18/android-market-share-q3-2012_n_1893292.html

http://www.huffingtonpost.com/2012/11/02/android-market-share_n_2066986.html

http://www.t-gaap.com/2012/5/24/can-google-make-money-with-android?site_locale=en

http://www.wired.com/business/2012/10/profit-or-no-profit/?pid=707

http://venturebeat.com/2012/11/01/as-android-grabs-75-market-share-can-anyone-tell-me-why-this-is-not-mac-vs-pc-all-over-again/

http://film.wapka.mobi/site_342.xhtml

http://en.wikipedia.org/wiki/International_Data_Corporation

http://androidandme.com/2012/04/opinions/the-future-of-android-in-2012/

http://www.tapscape.com/smartphone-war-android-market-share-hits-75-share/

http://openceo.blogspot.com/2012/11/android-75-market-share-future-is-open.html

http://www.forbes.com/sites/darcytravlos/2012/08/22/five-reasons-why-google-android-versus-apple-ios-market-share-numbers-dont-matter/

http://techpinions.com/android-v-ios-part-6-the-future/9687

http://www.wired.com/business/2012/10/google-ad-prices/

http://www.splatf.com/2011/10/google-revenue/

http://hellboundbloggers.com/2012/04/28/why-android-is-popular/

http://www.nascentstuff.com/why-android-os-is-getting-so-popular/

http://forum.xda-developers.com/showthread.php?t=865371

http://artinandroid.blogspot.com/2011/11/why-android-is-so-successful.html

http://techcrunch.com/2012/05/15/3997-models-android-fragmentation-as-seen-by-the-developers-of-opensignalmaps/

https://play.google.com/store/apps

http://authors.library.caltech.edu/11284/1/MCAaer04.pdf

http://nick.typepad.com/blog/2012/01/androids-legacy-nonsense.html

http://www.phonearena.com/news/The-Update-Battle-Innovation-vs-legacy-support_id23282

安全

http://www.cs.rice.edu/∼sc40/pubs/enck-sec11.pdf

批准

http://pandodaily.com/2012/01/28/how-google-can-save-android-close-it-license-it-swim-in-the-profits/

http://www.unwiredview.com/2011/07/13/the-real-cost-of-android-potentially-60-per-device-in-patent-fees/

http://www.quora.com/Mobile-Software-Development/What-is-the-licensing-royalty-cost-of-Android-OS

http://www.techrepublic.com/blog/app-builder/app-store-fees-percentages-and-payouts-what-developers-need-to-know/1205

http://developer.android.com/distribute/googleplay/publish/register.html

第五章:英特尔移动式处理器

移动巨头的冲突:ARM 对 Intel

http://beta.fool.com/iamgreatness/2012/07/25/mobile-vs-desktop-intel-ready-crush-arm/7757/

http://seekingalpha.com/article/874181-arm-s-david-vs-intel-s-goliath-outcome-uncertain

http://techland.time.com/2012/07/16/arm-vs-intel-how-the-processor-wars-will-benefit-consumers-most/

http://en.wikipedia.org/wiki/Acorn_Computers

http://www.ot1.com/arm/armchap1.html

http://www.techulator.com/resources/7489-The-All-time-Processor-War-ARM-Intel.aspx

http://en.wikipedia.org/wiki/ARM_architecture

http://media.corporate-ir.net/media_files/irol/19/197211/reports/ar06.pdf

" ARM 控股有限公司报告了截至 2001 年 6 月 30 日的第二季度和半年的业绩."http://www.arm.com/about/newsroom/arm-holdings-plc-reports-results-for-the-second-quarter-and-half-year-ended-30-june-2013.php

http://it.bentley.edu/dommara_prud/arm/index.html

http://www.zdnet.com/amd-arms-power-advantages-could-wane-in-the-coming-years-7000006597/

美国英特尔公司(财富 500 强公司之一ˌ以生产 CPU 芯片著称)

http://www.intel.com/content/www/us/en/history/historic-timeline.html

http://en.wikipedia.org/wiki/Semiconductor_sales_leaders_by_year#Ranking_for_year_2011

http://www.intel.com/support/motherboards/desktop/sb/CS-033869.htm

http://en.wikipedia.org/wiki/Handheld_game_console

http://www.extremetech.com/wp-content/uploads/2011/11/Chart_USportableGameRevenue_MarketShare_2009-2011-resized-600.png

http://appleinsider.com/articles/10/03/22/iphone_ipod_touch_carve_19_gaming_share_from_sony_nintendo

Android Atom 平台

http://www.pcworld.com/article/259737/intel_porting_android_41_to_work_on_atom_tablets_smartphones.html

http://www.phonearena.com/news/Android-Intel-Atom-powered-Phones-Hands-on-Reviews-Lenovo-K800-Orange-Santa-Clara-Lava-Xolo-X900_id27528

http://www.tomshardware.com/news/intel-tablet-medfield-soc-cpu,14389.html

第六章:为英特尔应用开发安装 Android SDK

安装和设置

Java 开发工具包

http://www.oracle.com/technetwork/java/javase/downloads/index.html

黯然失色

http://www.eclipse.org/downloads/

阿帕奇人 Ant

http://ant.apache.org/

软件开发工具包

http://developer.android.com/sdk/index.html

竞争

http://developer.android.com/guide/developing/devices/index.html

http://developer.android.com/tools/devices/emulator.html

冰淇淋三明治模拟

http://software.intel.com/en-us/articles/android-43-jelly-bean-x86-emulator-system-image

http://www.computerworld.com/s/article/9230152/Android_4.0_The_ultimate_guide_plus_cheat_sheet_

http://source.android.com/source/initializing.html

姜饼仿真

http://blogs.computerworld.com/17479/android_gingerbread_faq

千伏计(kilovoltmeter 的缩写)

https://help.ubuntu.com/community/KVM/Installation

https://help.ubuntu.com/community/KVM

http://android-er.blogspot.com/2010/09/how-to-set-battery-status-of-android.html

英特尔工具

http://www.intel.com/software/android

http://int-software.intel.com/en-us/android

http://software.intel.com/en-us/articles/android-virtual-device-emulation-for-intel-architecture

第七章:创建和移植基于 NDK 的 Android 应用

http://www.cygwin.com/

http://developer.android.com/sdk/ndk/index.html

http://www.eclipse.org/cdt/downloads.php

http://download.eclipse.org/tools/cdt/releases/galileo/

第八章:调试 Android

http://www.intel.com/software/android

http://developer.android.com/sdk/installing.html

http://developer.android.com/guide/developing/device.html

http://developer.android.com/tools/extras/oem-usb.html

http://developer.android.com/tools/device.html#VendorIds

http://developer.android.com/guide/developing/tools/adb.html

http://developer.android.com/sdk/eclipse-adt.html#installing

http://source.android.com/source/downloading.html

http://www.windriver.com/products/JTAG-debugging/

http://www.lauterbach.com

http://software.intel.com/en-us/articles/embedded-using-intel-tools

http://developer.android.com/guide/developing/device.html

http://www.eclipse.org/downloads/

http://developer.android.com/sdk/index.html

约翰逊,兰迪和斯图尔特·克里斯蒂。" JTAG 101: IEEE 1149.x 和软件调试."http://www.intel.com/content/www/us/en/intelligent-systems/jtag-101-ieee-1149x-paper.html

第九章:x86 平台上 Android 应用的性能优化

http://intel.com/software/gpa

http://software.intel.com/en-us/vcsource/tools/intel-gpa

第十章:x86 NDK 和 C/C++ 优化

http://software.intel.com/en-us/articles/intel-integrated-performance-primitives-intel-ipp-intel-ipp-sample-code

http://www.princeton.edu/∼achaney/tmve/wiki100k/docs/Locality_of_reference.html

第十一章:在 Windows、Mac OS 和 Linux 上使用英特尔硬件加速执行管理器加速 x86 仿真上的 Android

“英特尔硬件加速执行管理器- Microsoft Windows 的安装说明。”http://software.intel.com/en-us/articles/installation-instructions-for-intel-hardware-accelerated-execution-manager-windows

“英特尔硬件加速执行管理器- Mac OS X 的安装说明”。http://software.intel.com/en-us/articles/installation-instructions-for-intel-hardware-accelerated-execution-manager-macosx

“如何在 Linux 上启动英特尔硬件辅助虚拟化(hypervisor)来加速英特尔 Android x86 姜饼仿真器。”http://software.intel.com/en-us/blogs/2012/03/12/how-to-start-intel-hardware-assisted-virtualization-hypervisor-on-linux-to-speed-up-intel-android-x86-gingerbread-emulator

第十二章:通过平台调整进行性能测试和分析应用

http://elinux.oimg/e0/The_OpenMAX_Integration_Layer_standard.pdf

http://www.ffmpeg.org/

https://github.com/shaobin0604/faplayer

https://github.com/havlenapetr

http://yasm.tortall.net/

http://software.intel.com/en-us/articles/using-yasm-compiler-on-android-ndkbuild

http://software.intel.com/en-us/intel-vtune-amplifier-xe

http://developer.android.com/reference/android/opengl/GLSurfaceView.html

http://www.learnopengles.com/how-to-use-opengl-es-2-in-an-android-live-wallpaper

posted @   绝不原创的飞龙  阅读(88)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~
点击右上角即可分享
微信分享提示