六、Android 的定制和安装
Keywords Embed System Image File Target Machine Android Phone Android System
由于任何嵌入式系统的特性,如资源限制,裁剪和定制是嵌入式操作系统的重要特性,Android 也不例外。本章提供了嵌入式操作系统定制的一般讨论,然后具体解释了 Android 的定制。
嵌入式操作系统的裁剪和定制
并非嵌入式操作系统提供的所有功能和服务都包含在特定的嵌入式应用中,原因有二。首先,嵌入式系统总是资源受限的,尤其是在存储空间方面;因此,在发布时不可能在系统中包含所有冗余功能。第二,许多商业嵌入式操作系统根据用户选择的组件收取许可费。因此,用户应该根据自己的需求定制嵌入式操作系统。OS 定制的原理如图 6-1 所示。
图 6-1。
Principle of OS customization
例如,Windows XP Embedded OS 提供了数以万计的组件,比桌面 Windows XP 的功能还要多。例如,对于基于 Windows XP Embedded 的地铁挡板门系统,不需要 Windows Media Player、Internet Explorer 浏览器、DirectX 设置面板和 Explorer 任务管理器等组件。消除这些组件减少了系统所需的硬件资源,从而降低了成本;这使得系统运行更快,从而提高效率。
大多数嵌入式操作系统都提供了定制和裁剪的手段。但是,有许多不同的裁剪模式:有些从编译源代码开始,需要用户配置条件编译的选项;有些从链接目标文件开始,根据用户的配置链接到不同的库文件;其余模式根据用户的选择从现有的二进制文件库中提取预编译文件。表 6-1 列出了常用嵌入式操作系统提供的定制模式。
表 6-1。
Customization Modes of Different Embedded Operating Systems
| 嵌入式操作系统 | 定制模式 | | --- | --- | | windows ce(windows ce) | 提供平台构建器 ide 和图形组件选项。根据选定的组件链接不同的库文件。 | | 嵌入式 Linux | 对于内核,之前通过`make config`生成配置文件;然后根据配置文件进行编译。 | | 嵌入式 | 提供目标设计器 ide 和图形组件选项。根据选择的组件提取所需的二进制文件;链接过程不需要编译。 | | μ C/OS-II 战斗机 | 根据头文件中 C 语言宏定义的值,有选择、有条件地编译某部分代码。 | | 嵌入式系统 | 选择 Tornado IDE 中需要的模块。 |在系统定制之后,您将获得一个运行在目标硬件设备上的嵌入式操作系统,并且已经针对特殊应用领域进行了优化。
Android 定制概述
理论上,Android 定制分为两个层次:Linux 内核的定制和整个映像的定制。Linux 内核的定制类似于嵌入式 Linux 的定制:两者都涉及相同的方法和步骤。安卓定制主要以图片定制为主。我们来看看为什么。
ROM 包/映像
安卓镜像俗称只读存储器(ROM)包,是安卓手机的系统包。之所以有这样的命名约定,是因为安卓手机之前的手机,包括智能手机(如诺基亚、WM)和非智能手机(如索尼爱立信、Moto P2K 平台、MTK),都有单独的 ROM 芯片存储系统文件。因此,系统文件被称为 ROM 包或 ROM 映像。
该映像是一个交叉编译的二进制 Linux 文件,可以在某个嵌入式设备上安装和运行,成为该设备的操作系统。为了更好地理解这个概念,让我们回顾一下典型的开发过程,如图 6-2 所示。
图 6-2。
Development process for Android software
对于嵌入式软件,一般来说,开发 Android 软件需要和通用软件一样的步骤:设计、编码、编译、链接、打包、部署、调试、优化。对于某些 Android 系统,还需要测试和验证步骤。部署在嵌入式逻辑设备上的 OS 也经历这样的阶段。比如,对于 Linux 系统,你获取它的内核源代码,交叉编译,生成可以在嵌入式目标机上执行的代码;然后你压缩打包这段代码,形成镜像文件(见图 6-3 )。最后一步是部署。与应用文件的部署不同,OS 映像文件的部署由于其操作的特殊性而被称为安装。
图 6-3。
Image use process
嵌入式系统中一个完整的可执行软件系统的镜像文件(包)由 bootloader、OS 内核(简称内核)、文件系统和用户应用组成。实际的映像文件通常采用分区(也称为独立层)结构来存储位于映像不同区域(模块)的所有部分,所有部分都从底层加载到系统中。典型嵌入式系统映像的示例如图 6-4 所示。
图 6-4。
Example of an embedded system image
Android 映像包括引导程序、核心操作系统、硬件适配模块、文件系统、用户体验和应用。Android 的核心 OS 层包括 Linux 内核和各种中间件模块。核心 OS 层下面是硬件适配层。为了适应不同的硬件,需要为操作系统安装多样化的驱动程序。没有这些驱动程序,操作系统就不能像往常一样使用硬件进行操作。因此,映像由驱动程序和用户开发的任何应用组成。
Android 镜像通常以压缩文件(.zip
、tar.gz
或类似文件格式)的形式存在,通常包含表 6-2 所示的文件和文件夹。压缩文件解压缩后可以看到文件结构。
表 6-2。
File Structure of an Android Image File
| 名字 | 财产 | 评论 | | --- | --- | --- | | `META-IN` | 目录 | 可选;在某些图像中可能不可用 | | `system` | 目录 | | | `boot.img` | 文件 | |文件和文件夹的功能和结构如下:
boot.img
文件:系统镜像,包括系统启动的 Linux 内核、bootloader、ramdisk。ramdisk 是一个小文件系统,它保存了初始化系统所需的核心文件。使用名为 mkbootimg 的开源工具创建了boot.img
文件。META-INF
目录:系统更新脚本,路径为META-INF\com\google\android\updater-script
。system\app
目录:所有系统提供的应用,如日历、通讯录、Gmail 等。您可以将您的应用的.apk
文件放在这个目录中,这样它就可以在 ROM 刷新时直接安装。system\bin
目录:top
等系统命令,可以通过 adb shell 登录后执行。system\etc
目录:配置文件。system\font
目录:各种字体。system\framework
目录:Java 核心文件,比如.jar
文件。在 Dalvik 虚拟机(DVM)下,支持用户通过 Java 开发的框架。system\lib
目录:由.so
文件组成的 Android 本地共享库,这些文件是 ELF 二进制形式的共享对象,由汇编、C 或 C++ 编译。system\media
目录:媒体文件如bootanimation.zip
,由.png
图片组成,用于引导动画和改变引导镜像。在audio
目录下是一些用作铃声和通知的音频文件。
Android 图像定制概述
Android 镜像定制,俗称创建 Android ROM(简称 creating ROM),是一个学术术语。Android 核心 OS 层有多个组件,应用在不同系统中有所不同;映像定制决定将哪些组件和应用写入目标系统的映像文件。该过程将个人定制的系统文件制作成可闪存的 ROM 映像。这也称为系统固件更新。
现成的 Android 映像可以通过 USB 闪存和 SD 卡安装到基于英特尔凌动处理器的系统(即手机、平板电脑等)上。那么具有 Android 映像的系统将能够在自启动时进入 Android 操作环境。最初被称为 TransFlash 卡的 MicroSD 卡是由 SanDisk 推出的。它是 15 × 11 × 1 毫米,大约指甲盖大小。它可以通过 SD 适配卡在 SD 卡插槽中使用,广泛用于手机。
您可以通过以下方式创建 Android ROM:
- 编译 Android 源代码,有点复杂。
- 基于现有的 ROM 创建或定制您自己的 ROM。
Android 图片定制的流程如图 6-5 所示。
图 6-5。
Process of Android image customization
Android 图像定制示例
下面的例子说明了定制 Android 的第二种方式:使用设备制造商为目标硬件发布的克隆 ROM 映像创建 ROM。这样,Android 定制包括对 Android 系统文件夹的结构解析、应用软件更新和 ROM 签名包的定制。步骤如下:
- 从 Android 的官方网站、您的手机制造商的官方网站(例如,联想 K900 手机的网站)或提供 Android 图像的网站下载编译的 ROM 包。例如,联想 K900 手机网站(www . Lenovo care . com . cn/product detail . aspx?id = 719)提供的 ROM 如表 6-3 所示。请注意,由于联想 K800 和 K900 手机在中国市场销售,软件日期仅由联想官方网站以中文提供。
表 6-3。
Information in the ROM Package on the Lenovo K900 Website
| ROM 名称 | 描述 | 安卓版本 | 发布日期 | | --- | --- | --- | --- | | K900_1_S_2_162_0074 | 官方更新 | 4.0.4 | 2012 年 8 月 8 日 | | K900_1_S_2_162_0086 | 官方更新 | 4.0.4 | 2012 年 8 月 15 日 | | K900_1_S_2_019_0113_130903 | 官方更新 | 4.2.0 | 2013 年 9 月 3 日 | | K900 _ 来源 | 官方更新 | Four point four | 2014 年 5 月 23 日 |- 将所有 ROM 文件压缩到一个文件夹中(在本例中命名为“NewsROM”)。
- 删除和添加 ROM 文件夹中的文件(本例中为 NewsROM ),以定制和定制 Android。
一些自定义示例如下:
- 转到
data\app
目录,检查预装的应用是否是您需要的。此时,您可以删除不必要的应用。您也可以添加您需要的默认安装的应用。 - 转到
system\app
目录,为您的设备定制系统应用。您可以删除不需要的系统应用或添加您特别构建或定制的应用(作为定制的.apk
文件)。小心:一些系统应用依赖于其他应用,因此最佳实践是在定制之前进行测试,以在实现对 Android 系统映像的更改之前修复依赖性和其他问题。 - 转到
system\media
目录进行修改,如更改启动映像或添加自定义铃声。 - 转到
system\bin
目录添加命令等等。
如果您担心意外删除一些文件从而导致启动失败,您应该采取保守的方法:仅对data/app
和system/app
文件夹中的文件执行删除或添加操作。
- 将修改后的 ROM 文件夹压缩为一个
.zip
文件。双击压缩文件时,确保显示内容,包括META-INF
、system
、boot.img
、data
(可选)。 - 安装和配置 Java 环境。以下步骤需要 Java 环境来支持自动签名工具的操作,因此您需要安装和设置 Java 操作。下载最新的 JDK(本例中为 jdk1.7.0),并安装;然后按照以下步骤操作:
- 设置 Java 环境变量如下:右键单击我的电脑,在弹出的快捷菜单中选择[属性]➤[高级]➤[环境变量]➤[系统变量]➤[新建]。
- 在对话框中,将【变量名】设置为“JAVA_HOME 变量值:JAVA 安装目录”。在同一个地方找到【路径】,双击,添加“C:\ JDK 1 . 7 . 0;。;变量值后的 C:\JDK1.7.0\bin "。
- 重启系统。
- 测试。在命令行窗口中输入 Java 命令。如果没有出现错误消息,则配置成功。
- 使用
sign
工具对.zip
包进行签名。步骤如下:- 下载自动签名工具并将其解压到一个目录下(本例中为
myautosign
)。该工具可在http://androidforums.com/developer-101/8665-how-signing-roms.html
下载。 - 将
.zip
文件包重命名为update.zip
,并复制到您解包自动签名的目录下(myautosign
目录)。 - 运行解压自动签名的目录下的
sign.bat
文件。 - 定制构建完成后,目录中包含一个
update_signed.zip
文件,这个文件就是你需要的签名 ROM 包和定制 ROM 包。
- 下载自动签名工具并将其解压到一个目录下(本例中为
Android 映像的安装/刷新
需要安装映像才能在目标机器上使用定制的映像。换句话说,镜像定制和使用的过程必须经过镜像生成(生产)和镜像安装两个阶段,如图 6-6 所示。
图 6-6。
Image generation and installation
映像安装意味着在目标设备或模拟器上安装 Android 映像。这一过程通常被称为重新刷新。重新刷机安卓手机相当于给手机重装系统,类似于电脑系统重装。一般来说,当计算机需要重新安装系统时,您会使用系统盘或映像文件。当 Android 手机需要刷新时,您可以通过工具将官方或第三方 ROM 镜像文件刻录到 ROM 中,并为手机安装新系统。
Android 官方网站经常为用户发布最新的 Android 图像系统,你可以直接下载图像文件,跳过图像生成阶段。对于用户来说,定制和安装过程非常简单:下载映像,然后刷新。
Android 安装还涉及恢复和擦除:
- 恢复是移动设备的一种模式。通过恢复,用户可以安装系统(即刷新 ROM),清空手机中的各种数据,对存储卡进行分区,备份和恢复数据等等。恢复类似于电脑上的 Ghost 一键恢复功能。
- 擦拭的意思是擦掉和除去。擦除是恢复模式中的一个选项;它从手机中删除各种数据,类似于恢复出厂默认设置。擦除最常用于重新刷新之前。用户可能会看到擦除提示,提示需要在刷新前清除数据。
如前所述,Android 安装本质上是软件交叉开发过程中的部署问题,但一般你采用离线编程,而不是在线编程。在安装过程中,您使用的介质是 SD 卡和其他便携式外部存储设备。该过程如图 6-7 所示。安装分为两步:第一步,将来自主机的镜像放在便携 SD 卡外存设备上;第二,从便携外接存储设备启动机器,在目标机器上安装 Android。
图 6-7。
Android image installation
映像安装示例
以下是映像安装的示例。路径/目录可能因不同的 OEM 或不同的 Android 版本而异(此示例基于联想手机):
- 清空手机的 SD 卡。此步骤是可选的,可以在主机或电话上完成。在主机上完成步骤非常简单:从手机上拔下 SD 卡,插入主机的 SD 卡读卡器,在主机上(比如在 Windows 中)删除可移动磁盘上的所有文件。
按照以下步骤清空手机上的 SD 卡:
- 将手机连接到主机。
- 在主机的命令行窗口中连续执行以下命令:
adb devices
adb remount
adb shell
su
rm -r /system/sd/*
- 将自定义的 ROM 文件(示例中为
update_signed.zip
)复制到 SD 卡上,重命名为update.zip
。 - 确保 SD 卡已插入手机。重启手机,进入恢复模式。请遵循以下步骤:
- 正常关机。
- 同时按下设备的电源键和键:手机震动启动,进入 BKB 预配置操作系统模式。快速双击按钮,系统进入测试模式。
- 按和移动到第六个选项(标清更新),点击左下角的回车。自动刷新开始。
- 重启。
- (注意:
sdcard
通常安装在/storage/sdcard0
或/sdcard
下,然而,如果你使用的是来自不同 OEM 的设备或另一个 Android 版本,位置可能会有所不同。)
整个刷新过程需要几分钟。手机震动两下自动重启;第一次重启需要更长时间,然后出现熟悉的四叶草界面。
重启后,选择设置➤系统信息检查手机,网络,电池,和版本信息;IMEI 码;和内部版本号,以确认升级是否成功。
使用 flash_device.sh 实现过程自动化
有一个脚本将为您执行前面描述的所有过程。该脚本位于以下位置:
<Path-to-your-project>/vendor/intel/support/flash_device.sh
您可以将该脚本添加到您的bin
文件夹中,并从终端窗口运行它。您应该可以在 OEM 的用户手册中找到关于此主题的章节。
英特尔构建工具套件
英特尔开发了一套 Android 构建工具套件(见图 6-8 )来帮助开发人员轻松快速地进行 Android 系统构建和定制。该套件提供以下功能:
图 6-8。
Intel Build Tools Suite
- 设备定制
- 能够生成定制的固件模块和 Android 操作系统映像
- 最终定制和本地化
- 能够编译单个图像并将图像加载到支持的设备中
- 验证配置就绪的能力
- 故障排除和校准
摘要
本章完成了对 Android 系统级主题的讨论。从下一章开始,您将开始学习在 x86 平台上为 Android 开发应用,并了解如何在 Android 平台上开发适合移动设备 UX 和交互特性的用户界面。你从学习 Android 图形用户界面(GUI)设计开始,因为它是人机交互(HCI)不可或缺的一部分。由于手机或平板电脑的资源有限,Android 系统的 GUI 设计比桌面系统更具挑战性。此外,用户对用户友好的体验有更严格的要求和期望。界面设计已经成为决定市场上 Android 应用成功的重要因素之一。
Open Access This chapter is licensed under the terms of the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License ( http://creativecommons.org/licenses/by-nc-nd/4.0/ ), which permits any noncommercial use, sharing, distribution and reproduction in any medium or format, as long as you give appropriate credit to the original author(s) and the source, provide a link to the Creative Commons licence and indicate if you modified the licensed material. You do not have permission under this licence to share adapted material derived from this chapter or parts of it. The images or other third party material in this chapter are included in the chapter’s Creative Commons licence, unless indicated otherwise in a credit line to the material. If material is not included in the chapter’s Creative Commons licence and your intended use is not permitted by statutory regulation or exceeds the permitted use, you will need to obtain permission directly from the copyright holder.
七、Android 应用的 GUI 设计第一部分:概述
Keywords Virtual Machine Desktop Computer Touch Screen Content Provider Java Virtual Machine
自从 20 世纪 80 年代出现以来,图形用户界面(GUI)的概念已经成为人机交互不可或缺的一部分。随着嵌入式系统的发展,它们也逐渐采用了这个概念。运行在英特尔凌动硬件平台上的 Android 嵌入式操作系统处于这一趋势的最前沿。
由于资源有限,Android 系统的 GUI 设计比桌面系统更具挑战性。此外,用户对高质量的用户体验有着更加严格的要求和期望。界面设计已经成为决定市场上系统和应用成功的重要因素之一。本章介绍了如何在 Android 嵌入式系统上开发适合典型用户交互的用户界面。
嵌入式应用的图形用户界面综述
如今,软件的用户界面(UI)和用户体验(UX)是决定软件是否被用户接受并取得市场成功的越来越重要的因素。UX 设计基于输入/输出或交互设备的类型,必须符合它们的特性。与桌面计算机系统相比,Android 系统具有不同的交互设备和模式。如果一个桌面的 UI 设计被不加选择地复制,一个 Android 设备将呈现一个可怕的 UI 和难以忍受的 UX,用户无法接受。此外,随着对引人注目的用户体验有了更高的期望,开发人员在设计系统 ui 和 UXs 时必须更加细致和小心,使它们符合嵌入式应用的特征。
本章首先介绍了桌面系统的通用 GUI 设计方法,然后展示了为嵌入式系统设计 UI 的不同之处。目的是帮助你快速掌握 Android 应用 GUI 设计的一般方法和原则。
Android 设备交互模式的特征
通用台式计算机具有强大的输入/输出(或交互)设备,如大的高分辨率屏幕、全键盘和鼠标以及各种交互方式。典型的台式计算机屏幕至少为 17 英寸,分辨率至少为 1280×960 像素。键盘一般是全键盘或者增强型键盘。在全键盘上,字母、数字和其他字符位于相应的键上,也就是说,全键盘提供与所有字符对应的键。增强型键盘有额外的按键。全键盘上的键间距约为 19 mm,方便用户进行选择。
基于屏幕、键盘和鼠标的桌面计算机的 GUI 交互模式被称为 WIMP(窗口、图标、菜单和指针),这是一种使用这些元素以及包括按钮、工具栏和对话框在内的交互元素的 GUI 风格。WIMP 依靠屏幕、键盘和鼠标设备来完成交互。例如,鼠标(或类似于鼠标的设备,如光笔)用于指向,键盘用于输入字符,屏幕显示输出。
除了屏幕、键盘、鼠标和其他标准交互硬件之外,台式计算机还可以配备操纵杆、头盔、数据手套和其他多媒体交互设备来实现多媒体计算功能。通过安装摄像头、麦克风、扬声器和其他设备,并凭借其强大的计算能力,用户可以以语音、手势、面部表情和其他形式与台式计算机进行交互。
台式电脑一般也配有 CD-r om/DVD 等大容量便携式外部存储设备。有了这些外部存储设备,台式电脑就可以通过 CD/DVD 发布软件,验证所有权和证书。
由于嵌入式系统的可嵌入性和有限的资源,以及用户对便携性和移动性的需求,Android 系统具有不同于桌面系统的交互模态、方法和能力。由于这些特点和条件,Android 系统上的交互比桌面系统上的交互要求更高,也更难实现。
接下来描述 Android 设备和桌面计算机之间的主要区别。
各种尺寸、密度和规格的屏幕
与台式电脑上的大型高分辨率屏幕不同,Android 设备屏幕更小,具有各种尺寸和密度,以每英寸点数(DPI)来衡量。比如 K900 智能手机的屏幕是 5.5 英寸,分辨率为 1920 ×1080 像素,有的智能手机屏幕只有 3.2 英寸。
Android 设备屏幕的长宽比并不是台式电脑使用的 16:9 或 4:3 的常规长宽比。如果 Android 设备采用桌面计算机的交互模式,将会导致许多问题,如显示模糊和选择目标错误。
键盘和特殊按键
台式电脑有全键盘,一个键对应一个字符,键与键之间的距离很大,这使得打字很方便。如果 Android 设备有键盘,通常是小键盘而不是全键盘。小键盘的按键比全键盘少;几个字符通常共享一个键。与全键盘相比,小键盘的按键更小、间隔更紧,这使得选择和键入字符更加困难。因此,小键盘不如全键盘使用方便。此外,一些小键盘提供了标准全键盘上没有的特殊键,因此用户必须在 Android 设备上调整他们的输入。
一般来说,在 Android 设备上,按键和按钮是一个统一的概念。无论您按下按钮还是按键,该操作都被作为具有统一编号方案的键盘事件来处理。Android 中的键盘事件都有对应的android.view.KeyEvent
类。图 7-1 的按钮/按键标注对应于表 7-1 中列出的事件信息。
图 7-1。
Keyboard and buttons of an Android phone
详情参见android.view.KeyEvent
的帮助文档。表 7-1 的内容为摘录。
表 7-1。
Android Event Information Corresponding to Key and Button Events
触摸屏和触控笔,取代了鼠标
触摸屏是覆盖显示设备以记录触摸位置的输入设备。通过使用触摸屏,用户可以对显示的信息有更直观的反应。触摸屏广泛应用于 Android 设备,并取代鼠标进行用户输入。最常见的触摸屏类型是电阻式触摸屏、电容式触摸屏、表面声波触摸屏和红外触摸屏,其中电阻式和电容式触摸屏最常应用于 Android 设备。用户可以直接点击屏幕上的视频和图像进行观看。
手写笔可以用来执行类似于触摸的功能。有些触控笔是触摸屏的辅助工具,可以代替手指,帮助用户完成精细的指点、选择、画线等操作,尤其是在触摸屏比较小的情况下。其他触控笔与其他系统组件一起实现触摸和输入功能。使用第一种辅助工具触控笔,用户可以用手指触摸和输入字符。但是第二种输入笔是不可或缺的输入工具,用来代替手指。
触摸和触控笔可以执行鼠标通常会做的大部分功能,如点击和拖动,但不能实现鼠标的所有功能,如同时单击右键和左键/右键。在设计嵌入式应用时,应该将交互方式控制在触摸屏或触控笔所能提供的功能范围内,避免不可用的操作。
屏幕键盘
屏幕键盘,也称为虚拟键盘或软键盘,通过软件显示在屏幕上。用户敲击虚拟键就像他们敲击物理键盘上的键一样。
很少多模态交互
多模态交互是指人机交互,其模式涉及人类的五种感官。它允许用户通过语音、手写和手势等输入方式进行交互。因为计算能力有限,安卓设备一般不采用多模态交互。
少数大容量便携式外部存储设备
大多数 Android 设备没有 CD-ROM/DVD 驱动器、硬盘或其他大容量便携式存储外设,如通常在台式计算机上配置的固态驱动器(SSD)。这些设备不能在 Android 设备上安装软件或验证所有权和证书。然而,Android 设备通常支持 microSD 卡,现在的容量高达 128 GB 越来越多的基于云的存储解决方案,如 Dropbox、One Drive 和 Google Drive,正在为 Android 设备开发,可以从谷歌 Play 商店下载 Android 兼容的客户端应用。
嵌入式系统的用户界面设计原则
本节介绍了将传统桌面应用转换为嵌入式应用时的交互设计问题和纠正措施。
屏幕尺寸的考虑因素
与台式电脑系统相比,Android 系统的屏幕更小,显示密度和长宽比也不同。这样的屏幕差异导致应用从桌面系统迁移到 Android 系统时出现很多问题。如果开发人员按比例缩小桌面系统屏幕,图形元素会变得太小而看不清楚。特别是经常很难看到文字和图标,选择并点击一些按钮,并在屏幕上适当放置一些应用图片。如果开发人员将应用图形元素迁移到 Android 系统而不改变它们的大小,屏幕空间是有限的,只能容纳少数图形元素。
文本和图标的大小
另一个问题是文本和图标的大小。当一个应用从典型的 15 英寸桌面屏幕缩小到典型的 5 或 7 英寸手机或平板电脑屏幕时,它的文本太小,看不清楚。除了文本字体的大小之外,文本窗口(如聊天窗口)也会变得太小而无法阅读文本。试图缩小字体以适应较小的窗口会使文本难以识别。
因此,嵌入式系统的设计应尽量少用文本提示信息;例如,用图形或声音信息替换文本。此外,在需要文本的地方,文本大小应该是可调整的。在 Android 上,res
目录下有一些预定义的字体和图标,比如drawable-hdpi
、drawable-mdpi
和drawable-xhdpi
。
按钮和其他图形元素的可点击性
与小文本的问题类似,按钮和其他图形元素在迁移应用时也会带来交互问题。在桌面系统上,按钮的大小是为鼠标点击而设计的,而在 Android 系统上,按钮的大小应该适合手指(在触摸屏上)或触控笔。因此,当移植一个基于 Windows 的应用来支持 Android 设备时,需要重新设计应用 UI;并且应该选择由 Android SDK 提供的预定义的可绘制图形,以便适合手指或手写笔。
开发人员应该使用更大、更清晰的按钮或图形元素来避免此类问题,并在图形元素之间留有足够的间隙以避免错误,当使用小触摸屏通过手指或触控笔进行选择时,错误是常见的。此外,如果应用在按钮附近有文本标签,标签应该是与按钮相连的可点击区域的一部分,这样按钮更容易点击。
应用窗口的大小
许多应用(如游戏)使用固定大小的窗口,而不是自动调整以填充任何大小屏幕的窗口。当这些应用迁移到 Android 系统时,由于屏幕的长宽比与其分辨率不匹配,可能会看不到部分画面,或者部分区域无法到达。
这些问题在智能手机和平板电脑上可能更复杂,因为它们的屏幕有各种密度,如小(426 dp × 320 dp)、正常(470 dp × 320 dp)、大(640 dp × 480 dp)和超大(960 dp × 720 dp)。它们的长宽比各不相同,不同于桌面系统通常采用的长宽比。
解决此类问题的一个好方法是将整个应用窗口按比例放置在智能手机或平板电脑屏幕上,比如大屏幕和超大屏幕,通常为 640 × 480 像素和 960 × 720 像素;或者重新排列 UI,充分利用整个宽屏区域;或者使整个应用窗口成为可滚动视图。此外,您可以允许用户使用多个触摸手指触摸来放大、缩小或移动屏幕上的应用窗口。
触摸屏和触控笔带来的考虑
如前所述,许多 Android 系统使用触摸屏和触控笔来执行一些传统的鼠标功能。这种输入设备被称为。然而,仅点击的触摸屏不能提供所有的鼠标功能。没有右键,不触摸屏幕时无法捕捉当前手指/手写笔位置。因此,桌面应用允许在不点击的情况下移动光标、左键和右键的不同操作等功能,无法在使用触摸屏和触控笔的 Android 系统上实现。
下面几节讲的是在使用只需轻触的触摸屏将应用从桌面系统迁移到 Android 系统时经常看到的几个问题。
正确解释仅点击触摸屏上光标(鼠标)的移动和输入
当没有按下鼠标键时,许多应用需要鼠标移动信息。这个操作叫做。例如,很多 PC 射击游戏 1 模拟用户的视野,以至于移动鼠标而不点击被解释为移动游戏玩家的视野;但是光标应该总是停留在新视野的中间。然而,具有仅点击触摸屏的嵌入式设备不支持移动光标而不点击的操作。一旦用户的手指触摸到屏幕,就会触发一个点击事件。当用户在屏幕上移动手指时,触发一系列不同位置的点击事件;这些事件被现有的游戏代码解释为额外的交互事件(即移动游戏玩家枪的瞄准位置)。
将这类应用迁移到 Android 系统时,需要修改原有的交互模式。比如这个问题可以修改为点击操作:一旦用户触摸屏幕,游戏屏幕要立即切换到视野,在视野中光标位于屏幕中心。这样,光标总是显示在屏幕中心,而不是用户实际触摸的位置。您在移动平台上受益的一个优势是,市场上的大多数智能手机和平板电脑都配备了加速度计、陀螺仪、GPS 传感器和指南针等传感器,它们允许应用从传感器中读取数据。因此,开发人员有更多的选择,而不仅仅是触摸输入。
更一般地,如果应用需要跟踪光标从点 A 到点 B 的移动,则仅点击触摸屏可以通过用户首先点击点 A 然后点击点 B 来定义该输入,而不需要跟踪点 A 和点 B 之间的移动
正确设置屏幕映射
许多应用以全屏模式运行。如果这样的应用不能完美地填充整个仅点击触摸屏(也就是说,它们比屏幕小或大),输入映射错误将导致:显示位置和点击位置之间存在偏差。
在将全屏应用迁移到具有低纵横比的仅点击触摸屏时,经常出现的一种情况是应用窗口位于屏幕中央,两边显示空白区域。例如,当分辨率为 640 × 480(或 800 × 600)像素的桌面应用窗口迁移到分辨率为 960 × 720(或 1280 × 800,Dell Venue 8 上的 WXGA)像素的仅点击触摸屏时,它会出现在屏幕上,如图 7-2 所示。由此产生的映射错误会导致应用错误地响应用户交互。当用户点击黄色箭头(目标)的位置时,应用识别的位置就是红色爆炸图标所在的点。当用户点击按钮时,也会出现这种错误。
图 7-2。
Screen-mapping errors due to a low aspect ratio
您应该考虑位置映射逻辑,并考虑这个空白空间,即使这个空白空间不是迁移应用窗口的一部分。通过进行这些改变,仅点击触摸屏可以正确地映射触摸位置。
另一种情况发生在桌面全屏窗口被迁移到具有更高纵横比的仅点击触摸屏时。原始应用窗口的高度不适合仅点击的触摸屏,并且映射错误发生在垂直方向而不是水平方向。
图 7-3 显示了原始应用窗口水平填充屏幕,但在具有更高纵横比的仅点击触摸屏上不是垂直填充屏幕。这里,当用户点击黄色箭头(目标)的位置时,应用识别的位置就是红色爆炸图标所在的点。这些错误是由物理显示和应用窗口之间的形状差异引起的。
图 7-3。
Screen-mapping errors due to a high aspect ratio
一种解决方案是确保操作系统准确地将仅点击触摸屏映射到屏幕的整个可视区域。操作系统提供特殊服务来完成屏幕拉伸和鼠标位置映射。另一个解决方案是在应用开发之初,考虑允许配置选项支持 Android SDK 提供的预配置显示密度和纵横比,例如分辨率为 640 × 480、960 × 720 或 1,080 × 800 像素的屏幕。这样,如果最终的尺寸变形是可接受的,应用可以自动拉伸窗口以覆盖整个屏幕。
如何解决悬停问题
许多应用允许悬停操作:也就是说,用户可以将鼠标放在某个对象上,或将鼠标放在应用图标上,以触发动画项目或显示工具提示。该操作常用于为游戏中的新玩家提供指令;但是它与仅点击触摸屏的特征不兼容,因为它们不支持鼠标悬停操作。
您应该考虑选择一个替代事件来触发动画或提示。例如,当用户触摸应用的操作时,自动触发相关的动画主题和提示。另一种方法是设计一种界面交互模式,将点击事件暂时解释为鼠标悬停事件。例如,按下某个按钮并移动光标的动作不会被解释为点击操作。
提供右击功能
如前所述,仅点击触摸屏一般不支持鼠标右键操作。一种常用的替代方法是延迟触摸(比点击时间长得多)来表示右键单击。如果用户意外地过早松开手指,这可能导致错误的操作发生。此外,该方法不能同时执行左键单击和右键单击(也称为双击)。
你应该提供一个用户交互界面来代替右击功能:例如,使用双击或者在屏幕上安装一个可点击的控件来代替右击。
键盘输入问题
如前所述,台式电脑使用全键盘,而 Android 系统通常有更简单的小键盘、按钮面板、用户可编程按钮和有限数量的其他输入设备。这些限制在设计桌面系统中看不到的嵌入式应用时会导致一些问题。
限制各种命令的输入
Android 系统上的键盘限制使得用户很难键入大量字符。因此,需要用户输入许多字符的应用,尤其是那些依赖于命令输入的应用,在迁移到 Android 系统时需要进行适当的调整。
一种解决方案是提供一种输入模式,通过减少命令的数量或选择性地使用方便的工具(如菜单项快捷键)来限制字符的数量。更灵活的解决方案是在屏幕上创建命令按钮,尤其是上下文相关的按钮(即只在需要时才出现的按钮)。
满足键盘需求
应用需要键盘输入,例如命名文件、创建个人数据、保存进度和支持在线聊天。大多数应用倾向于使用屏幕键盘输入字符,但屏幕键盘并不总是运行或显示在应用界面的前端,这使得字符输入问题难以解决。
一种解决方案是为应用设计一种与屏幕键盘应用没有明显冲突的模式(例如,不使用全屏默认操作模式),或者在 UI 中提供一个仅在需要时出现的屏幕键盘。另一种最小化键盘输入的简单方法是提供默认的文本字符串值,如个人数据的默认名称和保存文件的默认名称,并允许用户通过触摸来选择。若要获取文本字符串所需的其他信息(例如,文件名称的前缀和后缀),您可以添加一个选择按钮,该按钮提供您已建立的字符串列表,用户可以从中进行选择。通过组合从屏幕上提取的各种用户信息项或者甚至使用日期-时间标记,也可以唯一地获得保存文件的名称。一些文本输入服务(比如聊天服务)如果不是应用的核心功能,应该被禁用。这不会对用户体验造成任何负面影响。
软件分发和版权保护问题
台式电脑一般配有 CD-ROM/DVD 驱动器,其软件一般通过 CD/DVD 发行。此外,出于反盗版目的,CD/DVD 安装通常要求用户验证磁盘的所有权或从 CD/DVD 动态加载内容,尤其是视频文件。然而,Android 系统(例如智能手机和平板电脑)通常没有 CD-ROM/DVD 驱动器;Android 确实支持外置的 microSD 卡,但是直接从里面安装应用还是不支持的。
一个好的解决方案是允许用户通过互联网下载或安装应用,而不是从 CD/DVD 安装。消费者直接从苹果应用商店、Google Play 和亚马逊应用商店等应用商店购买和安装应用。这种流行的软件发布模式允许移动开发者使用证书、在线账户或其他基于软件的方式来验证所有权,而不是物理 CD/DVD。同样,您应该考虑提供将内容放在在线云服务上的选项,而不是要求用户从 CD/DVD 下载视频和其他内容。
Android 应用概述
以下部分描述了 Android 应用的应用文件框架和组件结构。
应用文件框架
图 7-4 显示了 HelloAndroid app 生成后的文件结构(这是一个 Eclipse 截图)。
图 7-4。
Example file structure of an Android project
即使您没有使用 Eclipse,您也可以直接访问项目文件夹并看到相同的文件结构,如下所示:
E:\Android Dev\workspace\HelloAndroid>TREE /F
E:.
│ .classpath
│ .project
│ AndroidManifest.xml
│ ic_launcher-web.png
│ proguard-project.txt
│ project.properties
│
├─.settings
│ org.eclipse.jdt.core.prefs
│
├─assets
├─bin
│ │ AndroidManifest.xml
│ │ classes.dex
│ │ HelloAndroid.apk
│ │ resources.ap_
│ │
│ ├─classes
│ │ └─com
│ │ └─example
│ │ └─helloandroid
│ │ BuildConfig.class
│ │ MainActivity.class
│ │ R$attr.class
│ │ R$dimen.class
│ │ R$drawable.class
│ │ R$id.class
│ │ R$layout.class
│ │ R$menu.class
│ │ R$string.class
│ │ R$style.class
│ │ R.class
│ │
│ └─res
│ ├─drawable-hdpi
│ │ ic_action_search.png
│ │ ic_launcher.png
│ │
│ ├─drawable-ldpi
│ │ ic_launcher.png
│ │
│ ├─drawable-mdpi
│ │ ic_action_search.png
│ │ ic_launcher.png
│ │
│ └─drawable-xhdpi
│ ic_action_search.png
│ ic_launcher.png
│
├─gen
│ └─com
│ └─example
│ └─helloandroid
│ BuildConfig.java
│ R.java
│
├─libs
│ android-support-v4.jar
│
├─res
│ ├─drawable-hdpi
│ │ ic_action_search.png
│ │ ic_launcher.png
│ │
│ ├─drawable-ldpi
│ │ ic_launcher.png
│ │
│ ├─drawable-mdpi
│ │ ic_action_search.png
│ │ ic_launcher.png
│ │
│ ├─drawable-xhdpi
│ │ ic_action_search.png
│ │ ic_launcher.png
│ │
│ ├─layout
│ │ activity_main.xml
│ │
│ ├─menu
│ │ activity_main.xml
│ │
│ ├─values
│ │ dimens.xml
│ │ strings.xml
│ │ styles.xml
│ │
│ ├─values-large
│ │ dimens.xml
│ │
│ ├─values-v11
│ │ styles.xml
│ │
│ └─values-v14
│ styles.xml
│
└─src
└─com
└─example
└─helloandroid
MainActivity.java
我们来解释一下这个 Android 项目文件结构的特点:
src
目录:包含所有源文件。R.java
文件:由 Eclipse 中集成的 Android SDK 自动生成。您不需要修改其内容。- Android 库:Android 应用使用的一组 Java 库。
- 目录:主要存储多媒体文件和其他文件。
- 目录:存储预配置的资源文件,如应用使用的可绘制布局。
values
目录:主要存放strings.xml
、colors.xml
、arrays.xml
。AndroidManifest.xml
:相当于一个应用配置文件。包含应用的名称、活动、服务、提供者、接收者、权限等等。- 目录:主要存储应用使用的图像资源。
- 目录:主要存储应用使用的布局文件。这些布局文件是 XML 文件。
类似于一般的 Java 项目,src
文件夹包含项目的所有.java
文件;一个res
文件夹包含所有的项目资源,比如应用图标(可绘制)、布局文件和常量值。
接下来的部分将介绍每个 Android 项目的必备文件AndroidManifest.xml
和其他 Java 项目中包含的gen
文件夹中的R.java
文件。
AndroidManifest.xml
该文件包含有关您的应用的信息,这些信息对于 Android 系统至关重要,系统在运行任何应用代码之前必须拥有这些信息。这些信息包括项目中使用的活动、服务、许可、提供者和接收者。示例如图 7-5 所示。
图 7-5。
The content of AndroidManifest.xml
displayed in Eclipse
该文件的代码如下:
<manifest xmlns:android="
http://schemas.android.com/apk/res/android
package="com.example.helloandroid"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="15" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MyMainActivity"
android:label="@string/title_activity_my_main" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
AndroidManifest.xml
文件是 XML 格式的文本文件,每个属性由一个name = value
对定义。例如,在 Android 中,label = "@ string / title_activity_my_main", label
表示 Android 应用的名称为activity_my_main
。
元素由一个或多个属性组成,每个元素由开始(<
)和结束(/>
)标记包围:
<Type Name [attribute set]> Content </ type name>
<Type Name Content />
格式[attribute set]
可以省略;例如,<intent-filter> ... </ intent-filter>
文本段对应元素的活动内容,<action... />
对应action
元素。
XML 元素嵌套在层中以指示它们的从属关系,如前面的示例所示。action
元素嵌套在intent-filter
元素中,说明了intent-filter
属性或设置的某些方面。关于 XML 的详细信息超出了本书的范围,但是有许多优秀的 XML 书籍可供参考。
在这个例子中,intent-filter
描述了一个活动启动的位置和时间,并且每当一个活动(或 OS)要执行一个操作时,创建一个intent
对象。intent
对象携带的信息可以描述你想做什么,你想处理哪些数据和数据类型,以及其他信息。Android 比较每个应用暴露的intent-filter
数据,找到最合适的活动来处理调用者指定的数据和操作。
表 7-2 中列出了AndroidManifest.xml
文件中主要属性条目的描述。
表 7-2。
The Main Attribute Entries in the AndroidManifest.xml
File
R.java
R.java
文件是在创建项目时自动生成的。这是一个只读文件,不能修改。R.java 是一个定义项目所有资源的索引文件。例如:
/* AUTO-GENERATED FILE. DO NOT MODIFY.
... ...
*/
package com.example.helloandroid;
public final class R {
public static final class attr {
}
public static final class dimen {
public static final int padding_large=0x7f040002;
public static final int padding_medium=0x7f040001;
public static final int padding_small=0x7f040000;
}
public static final class drawable {
public static final int ic_action_search=0x7f020000;
public static final int ic_launcher=0x7f020001;
}
public static final class id {
public static final int menu_settings=0x7f080000;
}
public static final class layout {
public static final int activity_my_main=0x7f030000;
}
public static final class menu {
public static final int activity_my_main=0x7f070000;
}
public static final class string {
public static final int app_name=0x7f050000;
public static final int hello_world=0x7f050001;
public static final int menu_settings=0x7f050002;
public static final int title_activity_my_main=0x7f050003;
}
public static final class style {
public static final int AppTheme=0x7f060000;
}
}
可以看到这段代码中定义了很多常量。这些常量的名称与res
文件夹中的文件名相同,证明R.java
文件存储了项目所有资源的索引。有了这个文件,在应用中使用资源和标识所需资源就更加方便了。因为此文件不允许手动编辑,所以只需在向项目中添加新资源时刷新项目。R.java
文件自动生成所有资源的索引。
常数定义文件
项目的values
子目录包含字符串、颜色和数组常量的定义文件;字符串常量定义在strings.xml
文件中。这些常量被 Android 项目中的其他文件使用。
Eclipse 为strings.xml
文件提供了两个图形视图选项卡,Resources 和 strings.xml。Resources 选项卡提供了name-value
的结构化视图,strings.xml 选项卡直接显示文本文件格式的内容。HelloAndroid 示例的strings.xml
文件如图 7-6 所示。
图 7-6。
IDE graphic view of the strings.xml
file of HelloAndroid
文件内容如下:
<resources>
<string name="app_name">HelloAndroid</string>
<string name="hello_world">Hello world!</string>
<string name="menu_settings">Settings</string>
<string name="title_activity_main">MainActivity</string>
</resources>
代码非常简单;它只定义了四个字符串常量(资源)。
布局文件
布局文件描述了每个屏幕小部件(窗口和小部件的组合)的大小、位置和排列。布局文件是应用的“面孔”。布局文件是 XML 格式的文本文件。
小部件是可视化的 UI 元素,如按钮和文本框。它们相当于 Windows 系统术语中的控件和容器。按钮、文本框、滚动条等等都是小部件。在 Android OS 中,小部件一般属于View
类及其子类,存储在android.widget
包中。
应用有一个主布局文件,对应于启动时应用的屏幕显示。例如,HelloAndroid 示例的布局文件和主界面如图 7-7 所示。创建应用时,Eclipse 会自动为应用的主屏幕显示生成一个布局文件。该文件位于项目文件夹的res\layout
目录中。生成的应用项目中的文件名在下一节中指定:在这种情况下,源代码文件名对应于[Layout Name]
键,因此文件被命名为activity_main.xml
。
图 7-7。
The main graphic layout and user interface
当你点击设计窗口(本例中为activity_main.xml
)时,可以看到 XML 格式的文本文件的相应内容,如图 7-8 所示。
图 7-8。
The main layout file of the HelloAndroid example
该文件的内容如下:
<RelativeLayout xmlns:android="
http://schemas.android.com/apk/res/android
xmlns:tools="
http://schemas.android.com/tools
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:padding="@dimen/padding_medium"
android:text="@string/hello_world"
tools:context=".MainActivity" />
</RelativeLayout>
在这段代码中,有几个布局参数:
<RelativeLayout>
:相对位置的布局配置。android:layout_width
:定制当前视图的屏幕宽度;match_parent
代表父容器(在本例中是活动)匹配;fill_parent
填满整个屏幕;wrap_content
,表示为文本字段,根据该视图的宽度或高度而变化。android:layout_height
:定制当前视图所占的屏幕高度。
此布局文件中未显示的另外两个通用参数如下:
android:orientation
:此处为水平布局。android:layout_weight
:为线性布局的多个视图指定一个重要性值。所有视图都被赋予一个layout_weight
值;默认值为零。
尽管布局文件是一个 XML 文件,但您不必理解它的格式或直接编辑它,因为 Android 开发工具和 Eclipse 提供了一个可视化的设计界面。您只需拖放小部件并在 Eclipse 中设置相应的属性,您的操作就会自动记录在布局文件中。当您在下面几节中浏览应用开发示例时,您可以看到这是如何工作的。
源代码文件
构建项目时,Eclipse 会生成一个默认的.java
源代码文件,其中包含项目的应用基本运行时代码。它位于src\com\example\XXX
目录下的项目文件夹中(其中XXX
是项目名称)。本例中生成的应用项目的文件名是与[Activity Name]
键相对应的源代码文件名,因此该文件被命名为MainActivity.java
。
MainActivity.java
的内容如下:
package com.example.flashlight;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.MenuItem;
import android.support.v4.app.NavUtils;
public class MyMainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_my_main, menu);
return true;
}
}
应用的组件结构
Android 应用框架为开发者提供了 API。因为应用是用 Java 构建的,所以程序的第一层包含各种控件的 UI 需求。例如,视图(View
组件)包含列表、网格、文本框、按钮,甚至嵌入式 web 浏览器。
一个 Android 应用通常由五个组件组成:
- 活动
- 意图接收者
- 服务
- 内容供应器
- 意图和意图过滤器
下面几节将对每个组件进行更多的讨论。
活动
具有可视化用户界面的应用是使用活动实现的。当用户从主屏幕或应用启动器中选择一个应用时,它会启动一个动作或活动。每个活动程序通常采用独立界面(屏幕)的形式。每个活动都是一个独立的类,它扩展并实现了活动的基类。这个类显示为 UI,由响应事件的View
组件组成。
大多数程序都有多个活动(换句话说,一个 Android 应用由一个或多个活动组成)。切换到另一个接口会加载新的活动。在某些情况下,先前的活动可能会给出返回值。例如,让用户选择照片的活动会将照片返回给调用者。
当用户打开一个新界面时,旧界面被挂起并放入历史堆栈(界面切换历史堆栈)。用户可以返回到历史堆栈界面中已经打开的活动。没有历史值的堆栈可以从历史堆栈界面中删除。Android 在运行应用的历史堆栈中保留所有生成的界面,从第一个界面到最后一个界面。
活动是一个容器,它本身不显示在 UI 中。您可以大致将一个活动想象成 Windows 操作系统中的一个窗口,但是视图窗口不仅用于显示,还用于完成一项任务。
意图和意图过滤器
Android 通过一个叫做intent
的特殊类来实现界面切换。一个intent
描述了程序做什么。数据结构的两个最重要的部分是动作和根据已建立的规则处理的数据(数据)。典型的操作有MAIN
(活动入口)、VIEW
、PICK
、EDIT
。操作中使用的数据使用统一资源标识符(URI)表示。例如,要查看一个人的联系信息,您需要使用VIEW
操作创建一个intent
,该数据是一个指向该人的 URI 的指针。
与一个intent
相关联的类被称为一个IntentFilter
。一个intent
将一个请求封装成一个对象;IntentFilter
然后描述一个活动(或者说,一个意图接收者,稍后解释)可以处理什么意图。在前面的例子中,显示一个人的联系信息的活动使用了一个IntentFilter
,它知道如何处理应用于这个人的数据VIEW
操作。使用IntentFilter
的AndroidManifest.xml
文件中的活动通常通过解析intent
活动开关来完成。首先,它使用startActivity (myIntent)
函数来启动新的活动,然后系统地检查所有已安装程序的IntentFilter
,然后找到与IntentFilter
对应的myIntent
最匹配的活动。这个新活动接收来自intent
的消息,然后开始。intent
-解析过程实时发生在被调用的startActivity
中。这个过程有两个优点:
- 活动只发出一个
intent
请求,可以重用其他组件的功能。 - 该活动总是可以被
IntentFilter
的一个等价的新活动替换。
服务
服务是没有用户界面的常驻系统程序。您应该为任何需要连续运行的应用使用服务,例如网络监视器或检查应用更新。
使用服务的两种方式是启动-停止模式和绑定-解除绑定模式。工艺流程图和功能如表 7-3 所示。
表 7-3。
The Usage Model of a Service
| 方式 | 开始 | 目标 | 访问 | 笔记 | | --- | --- | --- | --- | --- | | 开始/停止 | `Context.startService()` | `Context.stopService()` | | 即使`startService`调用的进程结束,服务仍然存在,直到进程调用`stopService()`或者服务导致自己的终止(`stopSelf()`被调用)。 | | 绑定/解除绑定 | `Context.bindService()` | `Context.unbindService()` | `Context.ServiceConnection()` | 调用`bindService()`时,进程是死的;那么它所绑定的服务必须被终止。 |当两种模式混合使用时,例如一种模式调用startService()
而其他模式调用bindService()
,那么只有当stopService
调用和unbindService
调用都发生时,服务才会被终止。
一个服务进程有自己的生命周期,Android 试图保留一个已经启动或绑定的服务进程。服务流程描述如下:
- 如果服务是方法
onCreate()
、onStart
或onDestroy()
的实现进程,那么主进程就变成前台进程,以确保这段代码不被停止。 - 如果服务已经启动,其重要性值低于可见流程,但高于所有不可见流程。因为只有少数进程对用户可见,只要内存不是特别低,服务就不会停止。
- 如果多个客户端绑定到该服务,只要其中任何一个客户端对用户可见,该服务就是可见的。
广播意图接收器
当你想执行一些与外部事件相关的代码时,比如在半夜执行一个任务或者响应电话铃声,使用IntentReceiver
。意向接收者没有 UI,使用NotificationManager
通知用户他们的事件已经发生。意向接收方在AndroidManifest.xml
文件中声明,但也可以使用Context.registerReceiver()
声明。程序不必连续运行来等待IntentReceiver
被调用。当意图接收器被触发时,系统启动您的程序。节目还可以使用Context.broadcastIntent()
将自己的意图广播发送给其他节目。
Android 应用可用于处理数据元素或响应事件(如接收文本消息)。Android 应用与一个AndroidManifest.xml
文件一起被部署到设备上。AndroidManifest.xml
包含必要的配置信息,以便应用正确安装在设备上。AndroidManifest.xml
还包括应用可以处理的必要类名和事件类型,以及运行应用的必要权限。例如,如果一个应用需要访问网络,比如说,下载一个文件,清单文件必须在许可证中明确列出。许多应用可能会启用这种特定的许可证。这种声明式安全性有助于降低恶意应用损坏设备的可能性。
内容供应器
您可以将内容供应器视为数据库服务器。内容供应器的任务是管理持久的数据访问,比如 SQLite 数据库。如果应用非常简单,您可能不需要创建内容提供者应用。如果您想要构建一个更大的应用,或者需要构建应用来为多个活动或应用提供数据,您可以使用内容提供程序进行数据访问。
如果你想让其他程序使用他们自己程序的数据,内容供应器是非常有用的。content-provider 类实现了一系列标准方法,允许其他程序存储和读取内容提供者可以处理的数据。
安卓模拟器
Android 不使用普通的 Java 虚拟机(JVM);而是使用 Dalvik 虚拟机(DVM)。DVM 和 JVM 是根本不同的。DVM 占用的内存更少,专门针对移动设备进行了优化,更适合在嵌入式环境中使用的移动电话。其他差异如下:
- 一般的 JVM 是基于栈的虚拟机,但是 DVM 是基于寄存器的虚拟机。后者更好,因为应用可以在硬件的基础上实现最大限度的优化,这更符合移动设备的特点。
- DVM 可以在有限的内存中同时运行多个虚拟机实例,因此每个 DVM 应用都作为独立的 Linux 进程执行。在一般的 JVM 中,所有的应用都在一个共享的 JVM 中运行,因此各个应用不是作为单独的进程运行的。由于每个应用都作为单独的进程运行,因此可以防止 DVM 在虚拟机崩溃的情况下关闭所有程序。
- DVM 提供了比一般 JVM 限制更少的许可平台。DVM 和 JVM 支持不同的通用代码。DVM 不运行标准的 Java 字节码,而是运行 Dalvik 可执行格式(
.dex
)。Android 应用的 Java 代码编译实际上由两个过程组成。第一步是将 Java 源代码编译成普通的 JVM 可执行代码,它使用文件名后缀.class.
,第二步是将字节码编译成 Dalvik 执行代码,它使用文件名后缀.dex
。第一步将项目目录下src
子目录下的源代码文件编译成bin\class
目录下的.class
文件;第二步将文件从bin\class
子目录移动到bin
目录中的classes.dex
文件。编译过程被集成到 Eclipse 构建过程中;但是,您也可以使用命令行手动编译。
Android 运行时简介(ART)
ART 是一个 Android 运行时,最初作为预览功能出现在 Google Android KitKat (4.4)中。它也被称为 Dalvik 版本 2,正在 Android 开源项目(AOSP)的积极开发中。所有装有 Android KitKat 的智能手机和平板电脑都将 Dalvik 作为默认运行时。这是因为一些 OEM 厂商仍然不支持 Android 实现中的 ART,大多数第三方应用仍然基于 Dalvik 构建,尚未添加对新 ART 的支持。
正如谷歌在 Android 开发者网站上所描述的那样,大多数现有应用在运行 ART 时应该可以工作。然而,一些在 Dalvik 上工作的技术在 ART 上并不工作。Dalvik 和 ART 之间的差异如表 7-4 所示。
表 7-4。
Dalvik vs. ART Summary
| | 达尔维克 | 艺术 | | --- | --- | --- | | 应用 | 带有 DEX 类文件的 APK 包 | 和达尔维克一样 | | 编译类型 | 动态编译(JIT) | 超前编译(AOT) | | 功能 | 稳定,并通过了广泛的质量保证 | 基本功能和稳定性 | | 安装时间 | 更快的 | 由于编译而变慢 | | 应用启动时间 | 由于 JIT 编译和解释,速度通常较慢 | 由于 AOT 编译,速度更快 | | 存储空间 | 较小的 | 更大,带有预编译二进制文件 | | 内存占用 | 由于 JIT 代码缓存而变大 | 较小的 |ART 提供了一些新特性来帮助应用开发、性能优化和调试,例如支持采样分析器和调试特性,如监控和垃圾收集。从 Dalvik 过渡到 ART 可能需要一些时间,Dalvik 和 ART 都将在 Android 中提供,以允许智能手机和平板电脑用户进行选择和切换。但是,未来的 64 位 Android 将基于 ART。
摘要
本章介绍了桌面系统的通用 GUI 设计方法,然后展示了为嵌入式系统设计 UI 和 UX 的不同之处。现在,您应该了解了 Android 应用 GUI 设计的一般方法和原则,并准备好学习 Android 特定的 GUI。下一章描述了活动的状态转换、Context
类、意图以及应用和活动之间的关系。
Open Access This chapter is licensed under the terms of the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License ( http://creativecommons.org/licenses/by-nc-nd/4.0/ ), which permits any noncommercial use, sharing, distribution and reproduction in any medium or format, as long as you give appropriate credit to the original author(s) and the source, provide a link to the Creative Commons licence and indicate if you modified the licensed material. You do not have permission under this licence to share adapted material derived from this chapter or parts of it. The images or other third party material in this chapter are included in the chapter’s Creative Commons licence, unless indicated otherwise in a credit line to the material. If material is not included in the chapter’s Creative Commons licence and your intended use is not permitted by statutory regulation or exceeds the permitted use, you will need to obtain permission directly from the copyright holder. Footnotes 1
一个典型的例子就是游戏《反恐精英》(CS)。
八、Android 应用的 GUI 设计第二部分:特定于 Android 的 GUI
Keywords Finish Function View Class Application Context Android Application Application Interface
本章描述了活动的状态转换,并讨论了Context
类、意图以及应用和活动之间的关系。
活动的状态转换
如第七章所述,活动是最重要的组成部分。活动有自己的状态和转换规则,它们是编写 Android 应用所需了解的基础。
活动状态
当创建或销毁活动时,它们会进入或退出活动堆栈。当它们这样做时,它们在四种可能的状态之间转换:
- 活动:处于活动状态的活动位于堆栈顶部时可见。通常,响应用户输入的是前台活动。Android 会保证不惜一切代价执行。如果需要,Android 将进一步销毁堆栈活动,以确保活动活动所需的资源。当另一个活动变为活动时,该活动暂停。
- 暂停:在某些情况下,活动是可见的,但没有焦点。此时此刻,它处于暂停状态。当活动活动完全透明或者是非全屏活动时,下面的活动将达到这种状态。暂停的活动被认为是活动的,但不接受用户输入事件。在极端情况下,Android 会终止暂停的活动,以将资源恢复到活动活动。当一项活动完全看不见时,它就停止了。
- 停止:当活动不可见时,它被停止。该活动保留在内存中,以保存所有状态和成员信息。但是当系统需要内存的时候,这个活动就被“拿出来拍了。”当活动停止时,保存数据和当前 UI 状态非常重要。一旦活动退出或关闭,它将变为非活动状态。
- 非活动的:当一个活动被终止时,它就变成非活动的。不活动的活动从活动堆栈中删除。当您需要使用或显示该活动时,需要再次启动它。
活动状态转换图如图 8-1 所示。
图 8-1。
Android activity state transition diagram
状态改变不是人为的,完全由 Android 内存管理器控制。Android 首先关闭包含非活动的应用,然后是那些停止活动的应用。在极端情况下,它会删除暂停的活动。
为了确保完美的用户体验,这些状态的转换对用户来说是不可见的。当活动从暂停、停止或非活动状态返回到活动状态时,UI 必须是非歧视性的。所以,当一个活动停止时,保存 UI 状态和数据是非常重要的。一旦活动被激活,它需要恢复保存的值。
活动的重要功能
活动状态转换触发相应的activity
类的函数(即 Java 方法)。Android 调用这些函数;开发人员不必显式调用它们。它们被称为状态转移函数。您可以覆盖状态转换函数,以便它们可以在指定的时间完成工作。还有一些用于控制活动状态的功能。这些功能构成了活动编程的基础。让我们来了解一下这些功能。
onCreate 状态转换函数
onCreate
功能原型如下:
void onCreate(Bundle savedInstanceState);
该函数在首次加载活动时运行。当您启动一个新程序时,它的主活动的onCreate
事件被执行。如果活动被销毁(OnDestroy
,稍后解释),然后重新加载到任务中,那么它的onCreate
事件参与者将被重新执行。
一个活动很可能被强制切换到后台。(一个切换到后台的活动,用户已经看不到了,但它仍然存在于一个任务的中间,比如当一个新的活动开始“覆盖”当前活动的时候;或者用户按下主屏幕按钮返回主屏幕;或者其他事件发生在当前活动之上的新活动中,例如传入的调用者接口。)如果用户在一段时间后没有再次查看该活动,则该活动可能会与任务和流程一起被系统自动销毁。如果您再次检查活动,则必须重新运行onCreate
事件初始化活动。
有时您可能希望用户从活动的最后一个打开的操作状态继续,而不是从头开始。例如,当用户在编辑文本消息时接收到突然的来电时,用户可能必须在通话后立即做其他事情,例如将来电号码保存给联系人。如果用户没有立即返回到文本编辑界面,则文本编辑界面被破坏。结果,当用户返回到 SMS 程序时,该用户可能想要从最后的编辑继续。在这种情况下,您可以通过outState
在活动状态或信息被破坏之前写入您想要保存的数据来覆盖活动的 void onSaveInstanceState (Bundle outState)
事件,这样当活动再次执行onCreate
事件时,它会传输之前通过savedInstanceState
保存的信息。此时,您可以有选择地使用信息来初始化活动,而不是从头开始。
onStart 状态转换功能
onStart
功能原型如下:
void onStart();
onStart
功能在onCreate
事件之后或当前活动切换到后台时执行。当用户从切换面板选择该活动切换回该活动时,如果该活动没有被销毁,并且只执行了onStop
事件,则该活动将跳过onCreate
事件活动,直接执行onStart
事件。
论状态转移函数
onResume
功能原型如下:
void onResume()
onResume
功能在OnStart
事件后或当前活动切换到后台后执行。当用户再次查看该活动时,如果该活动没有被销毁,并且没有执行onStop
事件(活动继续存在于任务中),该活动将跳过onCreate
和onStart
事件活动,直接执行onResume
事件。
暂停状态转移函数
onPause
功能原型如下:
void onPause()
当当前活动切换到后台时,执行onPause
功能。
停止状态转移函数
onStop
功能原型如下:
void onStop()
onStop
功能在onPause
事件之后执行。如果用户一段时间没有再次查看该活动,则执行该活动的onStop
事件。如果用户按下 Back 键,也会执行onStop
事件,并且该活动会从当前任务列表中删除。
重新启动状态转移函数
onRestart
功能原型如下:
void onRestart()
执行onStop
事件后,如果活动及其所在的流程没有被系统地破坏,或者如果用户再次查看该活动,则执行该活动的onRestart
事件。onRestart
事件跳过onCreate
事件活动,直接执行onStart
事件。
灾难状态转移函数
onDestroy
功能原型如下:
void onDestroy()
在活动的一个onStop
事件之后,如果用户没有再次查看该活动,则该活动被销毁。
结束功能
finish
功能原型如下:
void finish()
finish
函数关闭活动并将其从堆栈中移除,这导致对onDestroy()
状态转换函数的调用。解决这个问题的一种方法是让用户使用 Back 按钮导航到上一个活动。
除了活动开关之外,finish
函数触发活动的状态转换函数,context 类的startActivity
和startActivityForResult
方法(在接下来的小节中描述)也激活它。像Context.startActivity
这样的函数也会导致activity
对象的构造(也就是创建新的)。
表 8-1 中列出了触发和相应功能的典型原因。
表 8-1。
Triggers and Their Functions
| 典型触发原因 | 执行活动的相应方法 | 说明 | | --- | --- | --- | | `Context.startActivity[ForResult]()`注意:只要活动在屏幕上显示并可查看,这个方法就会被调用。 | `new Activity()` | | | `onCreate()` | 完成构造函数,将`activity`对象保存到 application 对象,并初始化各种控件(如`View`)。 | | `onStart()` | 类似于`View.onDraw()`。 | | `Activity.finish()` | `onDestroy()` | 完成构造函数,比如从应用中移除`activity`对象。 |表 8-1 中的Context.startActivity
等函数触发三个动作:构造新的Activity
对象、onCreate
和onStart
。当一个活动从屏幕外移到屏幕显示的顶部(即显示在用户面前)时,它通常只包括被onStart
调用的功能。
上下文类
Context
类是一个需要了解的重要 Android 概念。该类继承自Object
函数,其继承如下:
java.lang.Object
√??]
context 的字面意思是相邻区域的文本,位于框架包的android.content.Context
中。Context
类是一个LONG
类型,类似于 Win32 中的Handle
处理程序。Context
提供关于应用环境的全局信息接口。它是一个抽象类,它的执行由 Android 系统提供。它允许访问资源和应用的特征类型。同时可以启动应用级的操作,比如启动活动和广播接收意向。
许多方法要求通过上下文实例来标识调用方。比如Toast
的第一个参数是Context
;而且通常你用this
来代替 activity,表示调用者的实例是一个 activity。但是其他方法,比如按钮的onClick
( View
视图),如果使用this
会导致错误。在这种情况下,您可以使用ActivityName.this
来解决问题,因为该类实现了几个主要的 Android 特定模型的上下文,如活动、服务和广播接收器。
如果参数——尤其是类的构造函数参数(比如Dialog
)——是Context
类型,那么实际的参数通常是活动对象,一般是[this]
。例如,Dialog
构造器原型是
Dialog.Dialog(Context context)
这里有一个例子:
public class MyActivity extends Activity{
Dialog d = new Dialog(this);
Context
是 Android 大多数类的祖先,如 broadcasting、intents 等,它提供了全球信息应用环境的接口。表 8-2 列出了Context
的重要子类。你可以在 Android Context
类的帮助文档中找到详细的描述。
表 8-2。
Important Subclasses of
| 亚纲 | 说明 | | --- | --- | | `Activity` | 用户友好界面类 | | `Application` | 提供全局应用状态维护的基类 | | `IntentService` | 用于处理服务异步请求的基类(以`Intent`的方式表达) | | `Service` | 应用的一个组件,它代表与用户没有交互的耗时操作,或者代表为其他应用任务提供功能的任务 |类被称为子类,因为它们是Context
的直接或间接子类,并且具有类似 activities 的继承关系:
java.lang.Object
↳ android.content.Context
↳ android.content.ContextWrapper
↳ android.view.ContextThemeWrapper
↳ android.app.Activity
Context
可以用于 Android 中的很多操作,但它的主要功能是加载和访问资源。有两种常用的上下文:应用上下文和活动上下文。活动上下文通常在各种类和方法之间传递,类似于活动的代码onCreate
,如下所示:
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this); // Pass context to view control
setContentView(label);
}
当活动上下文被传递给视图时,意味着视图有一个指向活动的引用,并引用活动所占用的资源:视图层次结构、资源等等。
还可以使用应用上下文,它总是伴随着应用的生命,但与活动生命周期无关。应用上下文可以通过Context.getApplicationContext
或Activity.getApplication
方法获取。
Java 通常使用一个静态变量(singleton 等)来同步活动之间(程序内的类之间)的状态。Android 更可靠的方法是使用应用上下文来关联这些状态。
每个活动都有一个上下文,其中包含运行时状态。类似地,应用有一个上下文,Android 使用它来确保它是该上下文的唯一实例。
如果需要定制应用上下文,首先必须定义一个从android.app.Application
继承的定制类;然后在应用的AndroidManifest.xml
文件中描述这个类。Android 会自动创建这个类的一个实例。通过使用Context.getApplicationContext()
方法,您可以获得每个活动内部的应用上下文。下面的示例代码获取活动中的应用上下文:
class MyApp extends Application {
// MyApp is a custom class inherited from android.app.Application
public String aCertainFunc () {
......
}
}
class Blah extends Activity {
public void onCreate(Bundle b){
... ...
MyApp appState = ((MyApp)getApplicationContext());
// Get Application Context
appState.aCertainFunc();
//Use properties and methods of the application
... ...
}
}
您可以使用Context
的get
函数获得关于应用环境的全局信息。主要函数如表 8-3 所示,可以是ContextWrapper
或直接上下文方法。
表 8-3。
Commonly Used Methods for Obtaining Context
| 功能原型 | 功能 | | --- | --- | | `abstract Context ContextWrapper.getApplicationContext ()` | 返回对应于单个应用的全局上下文的当前进程。 | | `abstract ApplicationInfo ContextWrapper.getApplicationInfo ()` | 返回整个应用信息对应的上下文包。 | | `abstract ContentResolver ContextWrapper.getContentResolver ()` | 返回相应应用包的内容解析器实例。 | | `abstract PackageManager ContextWrapper.getPackageManager ()` | 返回用于查找所有包信息的包管理器实例。 | | `abstract String ContextWrapper.getPackageName ()` | 返回当前包名。 | | `abstract Resources ContextWrapper.getResources ()` | 返回(用户)应用包的资源实例。 | | `abstract SharedPreferences ContextWrapper.getSharedPreferences (String name, int mode)` | 查找并保存首选项文件的内容,该文件的名称由参数名称指定。返回您可以查找和修改的共享首选项(`SharedPreferences`)的值。当使用正确的名称时,只有一个`SharedPreferences`实例返回给调用者,这意味着一旦更改完成,结果就可以相互共享。 | | `public final String Context.getString (int resId)` | 从应用包的默认字符串表中返回本地化字符串。 | | `abstract Object ContextWrapper.getSystemService (String name)` | 根据变量名指定的名称返回正在处理的系统级服务。返回的对象类因请求的名称而异。 |意向介绍
Intent 可以用作一种消息传递机制,允许您声明采取行动的意图,通常带有特定的数据。您可以使用 intent 来实现 Android 设备上任何应用的组件之间的交互。意图将一组独立的组件变成一对一交互的系统。
它也可以用来广播消息。任何应用都可以注册一个广播接收器来侦听和响应这些意向广播。Intent 可用于创建内部、系统或第三方事件驱动的应用。
Intent 负责描述操作和应用的动作数据。Android 负责找到子意图下描述的对应组件,将意图传递给被调用的组件,完成组件调用。意图在调用方和被调用方之间扮演解耦的角色。
意图是运行时绑定的一种机制;它可以在运行程序的过程中连接两个不同的组件。通过意图,程序可以向 Android 请求或表达意愿;Android 根据意图的内容选择适当的组件来处理请求。例如,假设某个活动想要打开 web 浏览器来查看某个页面的内容;这个活动只需要向 Android 发出一个WEB_SEARCH_ACTION
请求。基于内容请求,Android 将检查组件注册语句中声明的意图过滤器,并为 web 浏览器找到一个活动。
当发出一个意向时,Android 会找到一个或多个活动、服务或broadcastReceiver
的精确匹配作为响应。因此,不同类型的意向消息不会重叠,也不会同时发送到一个活动或服务,因为startActivity()
消息只能发送到一个活动,而startService()
意向只能发送到一个服务。
意图的主要作用
意向的主要作用如下。
触发新活动或让现有活动实现新操作
在 Android 中,意图直接与活动交互。intent 最常见的用途是绑定应用组件。Intent 用于启动、停止和转移应用活动。换句话说,意图可以激活一个新的活动,或者使一个现有的活动执行一个新的操作。这可以通过调用Context.startActivity()
或Context.startActivityForResult()
方法来完成。
要在应用中打开一个不同的接口(对应于一个活动),您可以调用Context.startActivity()
函数来传递一个意图。Intent 既可以明确指定要打开的特定类,也可以包含实现目标所需的操作。在后一种情况下,运行时将使用一个众所周知的意图解析过程来选择打开哪个活动,在这个过程中,Context.startActivity()
会找到并启动一个与意图最匹配的活动。
触发新服务或向现有服务发送新请求
打开服务或向现有服务发送请求也是由 intent 类完成的。
触发广播接收器
您可以使用三种不同的方法发送BroadcastIntent
:Context.sendBroadcast()
、Context.sendOrderedBroadcast()
和Context.sendStickyBroadcast()
。
意图解析
意向转移流程有两种方式将目标消费者(如另一个活动、IntentReceiver
或服务)与意向的回应者匹配起来。
第一种是显式匹配,也称为直接意图。当构造一个intent
对象时,您必须将接收者指定为意图的组件属性之一(通过调用setComponent (ComponentName)
或setClass (Context, Class)
)。通过指定组件类,应用通知启动相应的组件。这种方法类似于普通的函数调用,但在粒度重用方面有所不同。
第二种是隐性匹配,也叫间接故意。当构造一个intent
对象时,意图的发送者不知道或不关心接收者是谁。组件意图中未指定该属性。这个意图需要包含足够的信息,以便系统可以从所有可用的组件中确定使用哪些组件来满足这个意图。这种方法与函数调用明显不同,有助于减少发送方和接收方之间的耦合。隐式匹配解析为单个活动。如果有多个活动可以基于特定数据实现给定的动作,Android 会选择最好的一个开始。
对于直接意图,Android 不需要做解析,因为目标组件非常清楚。但是,Android 需要解决间接意图。通过分析,它将间接意图映射到处理意图的活动、IntentReceiver
或服务。
意图解析的机制主要包括以下内容:
- 寻找所有的
<intent-filter>
和由这些过滤器定义的意图,这些过滤器注册在AndroidManifest.xml
中 - 通过
PackageManager
找到并处理意图的组件(PackageManager
可以获得当前设备上安装的应用包的信息)
意图过滤器非常重要。未声明的<intent-filter>
组件只能响应组件名匹配的显式意图请求,但不能响应隐式意图请求。声明的<intent-filter>
组件可以响应显式意图或隐式意图请求。当解析隐式意图请求时,Android 使用意图的三个属性——动作、类型和类别——来进行解析。下面介绍具体的解决方法。
动作测试
一个<intent-filter>
元素应该包含至少一个<action>
,否则没有任何意向请求可以匹配到<intent-filter>
。如果一个意向请求的动作在<intent-filter>
中至少有一个<action>
匹配,那么该意向通过了这个<
的动作测试。
如果意向请求或<intent-filter>
中没有具体动作类型的描述,则采用以下两种测试之一:
- 如果
<intent-filter>
不包含任何动作类型,那么不管意图请求是什么,都不匹配这个<intent-filter>
。 - 如果意向请求没有设置动作类型,只要
<intent-filter>
包含一个动作类型,该意向请求将成功通过<intent-filter>
的动作测试。
类别测试
对于通过类别测试的意向,意向中的每个类别都必须与过滤器中的类别相匹配。当每一类意向请求都与其中一个<intent-filter>
组件的<category>
完全匹配时,意向请求通过测试。<intent-filter>
的<category>
声明过多不会导致匹配失败。任何没有指定类别测试的<intent-filter>
只匹配没有为其设置配置的意图请求。
dota 测试
<data>
元素指定您想要接收的意向请求的数据 URI 和数据类型。一个 URI 被分成三个匹配的部分:方案、权威和路径。由setData()
设置的互联网请求的 URI 数据类型和方案必须与在<intent-filter>
中指定的相同。如果<intent-filter>
也指定了权限或路径,它们必须匹配才能通过测试。
这个决策过程可以表示如下:
- 如果意图指定了动作,那么目标组件的
<intent-filter>
的动作列表必须包含该动作。否则,不认为匹配。 - 如果意向没有提供类型,则系统从数据中获取数据类型。对于一些动作方法,目标组件的数据类型列表必须包含意图的数据类型。否则无法匹配。
- 如果意图的数据不是内容的 URI,并且类别和意图也没有指定其类型,则匹配基于意图的数据方案(例如,
http:
或mailto:
),并且意图的方案必须出现在目标组件的方案列表中。 - 如果意图指定一个或多个类别,这些类别必须全部出现在组件的类别列表中。例如,如果意图包含两个类别,
LAUNCHER_CATEGORY
和ALTERNATIVE_CATEGORY
,那么通过解析获得的目标组件必须至少包含这两个类别。
应用和活动之间的关系
初学者容易混淆应用和活动——特别是主要活动(应用启动时发生的活动)。事实上,它们是两个完全不同的对象。行为、属性等等并不相同。以下是应用和活动之间的差异列表:
- 不管一个应用启动多少次,只要不关闭,它的值(也就是对象)就是不变的。它只有一个实例。
- 不管一个应用从哪里启动,只要它没有关闭,它的值(也就是对象)就是常量。它只有一个实例。
- 当一个活动没有完成时,它的值(即对象)是不变的。每次调用
onStart()
时,活动显示在屏幕前面。 startActivity
每次启动的对象都不一样。你可以说startActivity
其实包含了新的对象。- 虽然在
startActivity
之后无法获得新的 activity 对象,但是当startActivity
启动其对应的 activity 对象时,Android 框架可以发送参数值(类似于函数调用的实际参数)。 - 更令人惊讶的是,Android 可以有一个活动共存于多个对象中。当一个活动关闭时,Android 会将结果返回给通过
startActivity
启动的主活动。这样一来,它会自动调用启动其活动对象的onActivityResult()
方法,可以避免随机分布。
- 虽然在
- 一个应用可以有多个活动对象。
基本的 Android 应用界面
在本节中,您将通过一个示例来了解使用集成在 Eclipse IDE 中的 Android SDK 进行 Android 开发。您使用 Android SDK 创建了一个名为 GuiExam 的应用,并按照流程的步骤学习了 Android 界面设计。
GuiExam 应用代码分析
本节提供了对 GuiExam 示例应用的分析。首先,让我们使用 Eclipse 中的 Android SDK 创建 GuiExam 应用。对于应用名称,键入GuiExam
。对于构建 SDK,选择 API 19,它包括 x86 指令。如图 8-2 所示,选择所有其他条目的系统默认配置。
图 8-2。
Initial setup when generating the GuiExam project
项目的文件结构如图 8-3 所示,用户界面如图 8-4 所示。
图 8-4。
The application interface of GuiExam
图 8-3。
File structure of the GuiExam application
应用唯一的 Java 文件(MainActivity.java
)的源代码如图 8-5 所示:
图 8-5。
The typical source codes in Java file MainActivity.java
您知道在创建事件时会调用MainActivity.OnCreate()
函数。函数的源代码非常简单。第 12 行调用超类函数,第 13 行调用setContentView
函数。此函数设置活动的 UI 显示。在 Android 项目中,大部分 UI 是由 view 和 view 子类实现的。View
表示一个可以处理事件的区域,也可以渲染这个区域。
第 13 行的代码表示视图是R.layout.activity_main
。项目的gen
目录下自动生成的R.Java
文件包含如下代码(节选):
Line # Source Code
......
8 package com.example.guiexam;
9
10 public final class R {
......
26 public static final class layout {
27 public static final int activity_main=0x7f030000;
28 }
29 public static final class id {
30 public static final int menu_settings=0x7f080000;
31 }
32 public static final class string {
33 public static final int app_name=0x7f050000;
34 public static final int hello_world=0x7f050001;
35 public static final int menu_settings=0x7f050002;
36 public static final int title_activity_main=0x7f050003;
37 }
......
41 }
可以看到R.layout.activity_main
是主布局文件activity_main.xml
的资源 ID。该文件内容如下:
Line# Source Code
1 <RelativeLayout xmlns:android="
http://schemas.android.com/apk/res/android
2 xmlns:tools="
http://schemas.android.com/tools
3 android:layout_width="match_parent"
4 android:layout_height="match_parent" >
5
6 <TextView
7 android:layout_width="wrap_content"
8 android:layout_height="wrap_content"
9 android:layout_centerHorizontal="true"
10 android:layout_centerVertical="true"
11 android:padding="@dimen/padding_medium"
12 android:text="@string/hello_world"
13 tools:context=".MainActivity" />14
15 </RelativeLayout>
这段代码的第一行表明内容是一个RelativeLayout
类。通过查看 Android 帮助文档,可以看到RelativeLayout
的继承关系是
java.lang.Object
↳ android.view.View
↳ android.view.ViewGroup
↳ android.widget.RelativeLayout
这个类确实被看作是一个视图类。这个布局包含一个TextView
类,它也是视图的子类。第 12 行表示其text
属性为@string/hello_world
,显示文本为strings.xml
中变量hello_world
的内容:“Hello world!”
作为布局的超类,ViewGroup
是一个特殊视图,可以包含其他视图对象,甚至是ViewGroup
本身。换句话说,ViewGroup
对象将其他视图或ViewGroup
的对象视为成员变量(在 Java 中称为 properties)。ViewGroup
对象中包含的内部视图对象称为小部件。由于ViewGroup
的特殊性,Android 可以自动设置各种复杂的应用界面。
使用布局作为界面
作为应用界面设计的一部分,您可以修改或设计布局。例如,您可以如下修改activity_main.xml
文件:
图 8-8。
The user interface of GuiExam after the layout has been modified
图 8-7。
Modifying the GuiExam layout to add a text-edit widget
- 将一个纯文本小部件从左栏的文本字段部分拖放到
activity_main
屏幕。将布局参数分支下的Width
属性更改为fill_parent
,然后拖动纯文本直到其填满整个布局,如图 8-7 所示。
图 8-6。
Modifying the GuiExam layout to add a button
- 将
TextView
的Text
属性改为“在此键入”。 - 从表单小部件栏中选择一个按钮小部件,并将其放入
activity_main
屏幕。将其Text
属性设置为“点击我”,如图 8-6 所示。
从这些例子中,你可以看到接口的一般结构。通过setContentView
(布局文件资源 ID)设置的活动是:活动包含一个布局,布局包含各种小部件,如图 8-9 所示。
图 8-9。
Interface structure of the activity
你可能想知道为什么 Android 会引入这种布局概念。事实上,这是 Android 的一个开发者青睐的特性,相比于 Windows 微软基础类(MFC)的编程接口。该布局隔离了设备上的屏幕大小、方向和其他细节的差异,这使得界面屏幕适应各种设备。因此,在不同设备平台上运行的应用可以自动调整小部件的大小和位置,而无需用户干预或修改代码。
例如,您创建的应用可以在不同的 Android 手机、平板电脑和电视设备平台上运行,而无需更改任何代码。小工具的位置和大小会自动调整。即使你将手机旋转 90 度,纵向或横向模式的界面也会自动调整大小,并保持在相对位置。布局也允许根据当地民族习惯排列 widgets(大多数国家从左到右排列,但也有一些国家从右到左排列)。界面设计需要考虑的细节都是由布局来完成的。你可以想象如果没有布局类会发生什么——你必须为每个设备的每个 Android 界面布局编写代码。这一级别的工作的复杂性是不可想象的。
将视图直接用作界面
前面您已经看到了活动的接口结构和代码框架。您还看到了大部分 UI 是由 view 和 view 子类实现的。因此,您可以使用setContentView
函数来指定一个视图对象,而不是一个布局。activity 类的setContentView
函数的原型包括以下内容。
此函数将布局资源设置为活动的接口:
void setContentView(int layoutResID)
第一种类型的函数将显式视图设置为活动的接口:
void setContentView(View view)
第 2 个和第 1 个类型 f 函数按照指定的格式设置一个显式视图作为活动的接口:
setContentView(View view, ViewGroup.LayoutParams params)
在这里,您将完成一个应用示例,该示例将视图直接用作活动接口,使用第二个函数 setContentView()您可以修改MainActivity.java
文件的代码,如下所示:
......
import android.widget.TextView;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tv = new TextView(this); // Create a TextView Object that belongs to current Activity
tv.setText("Hello My friends!"); // Set Display text of TextView
setContentView(tv); // Set View as the main display of the Activity
}
应用界面如图 8-10 所示。
图 8-10。
GuiExam sets the view directly as the interface
在这种情况下,您有作为应用接口的TextView
小部件,它是视图的直接子类;它们直接在setContentView
功能中设置。这样,TextView
显示的文本就成为了应用界面的输出。要使用TextView
类,您可以在文件的开头使用一个import android.widget.TextView
语句来导入该类的包。
组件 ID
现在让我们回过头来看看图 8-6 所示的应用布局。布局中添加的文本编辑小工具的 ID 属性为@ + id/editText1
,按钮的 ID 属性为@ + id/button1
(如图 8-5 )。这是什么意思?
再来看R.java
文件(节选):
Line # Source Code
......
8 package com.example.guiexam;
9
10 public final class R {
......
22 public static final class id {
23
public static final int button1=0x7f080001;
24
public static final int editText1=0x7f080002;
25 public static final int menu_settings=0x7f080003;
26 public static final int textView1=0x7f080000;
27 }
28 public static final class layout {
29 public static final int activity_main=0x7f030000;
30 }
......
43 }
对比“GuiExam 应用”部分的R.java
文件,可以看到第 23、24 行是新的;它们是新添加的按钮和文本编辑框的资源 ID 号。类型为int
,对应这些小部件的 ID 属性值。从R.java
文件中可以找到这些小部件的 ID——静态常量R.id.button1
是 ID 属性值为@ + id/button1
的小部件(按钮)的资源 ID,静态常量R.id.editText1
是 ID 属性值为@ + id/editText1
的小部件(文本编辑)的资源 ID。这是什么原因呢?让我想想。
Android 组件(包括小部件和活动)需要使用一个类型为int
的值作为标签,这个值就是组件标签的 id 属性值。ID 属性只能接受resources
类型的值。即值必须以@
开头,;比如@ id/abc
、@+id/xyz
等等。
@
符号用于提示 XML 文件的解析器解析@
后面的名称。例如,对于@string/button1
,解析器从values/string.xml
中读取这个变量的button1
值。
如果在@
后面使用了+
符号,则表示当你修改并保存一个布局文件时,系统会自动在R.java
中生成相应类型的int
变量。变量名是/
符号后的值;例如,@+id/xyz
在R.java
中生成int xyz = value
,其中值为十六进制数。如果R.java
中已经存在相同的变量名xyz
,系统不会生成新的变量;相反,组件使用这个现有的变量。
换句话说,如果您使用@+id/name
格式,并且在R.java
中存在一个名为name
的变量,那么组件将使用该变量的值作为标识符。如果变量不存在,系统会添加一个新变量,并为该变量分配相应的值(不重复)。
因为组件的 ID 属性可以是资源 ID,所以可以设置任何现有的资源 ID 值:例如,@drawable/icon
、@string/ok
或@+string/
。当然也可以设置一个 Android 系统中已经存在的资源 id,比如@id/android:list
,其中 ID 中的android:
修饰符表示系统的 R 类所在的包(在R.java
文件中)。您可以在 Java 代码编辑区输入android.R.id
,它会列出相应的资源 ID。例如,可以这样设置 ID 属性值。
出于刚才描述的原因,您通常将 Android 组件(包括小部件、活动等)的 ID 属性设置为@+id/XXX
格式。并且您使用R.id.XXX
来表示程序中组件的资源 ID 号。
按钮和事件
在“使用布局作为界面”一节中的例子中,您创建了一个包括Button
、EditText
和其他小部件的应用,但是当单击按钮时什么也没有发生。这是因为您没有分配对click
事件的响应。本节首先介绍 Android 事件和监听器函数的基础知识。在以后涉及 Android 多线程设计的章节中,您将回顾并进一步探索更多关于事件的高级知识。
在 Android 中,每个应用都维护一个事件循环。当应用启动时,它完成适当的初始化,然后进入事件循环状态,在此状态下,它等待用户操作,如单击触摸屏、按键(按钮)或一些其他输入操作。用户动作触发程序生成对事件的响应;系统根据事件位置生成并分发相应的事件类进行处理,比如Activity
或者View
。回调方法被集成到一个称为事件监听器的接口中。您可以通过覆盖接口的抽象函数来实现指定的事件响应。
不同类接收的事件的范围对于每个类都是不同的。例如,Activity
类可以接收keypress
事件,但不能接收touch
事件,而View
类可以接收touch
和keypress
事件。此外,不同类接收到的事件属性细节也各不相同。例如,View
类接收的touch
事件由许多触摸点、坐标值和其他信息组成。它被细分为按下、反弹和移动事件。但是Button
类是View
类的后代,它只检测按压动作,事件不提供触摸点的坐标或其他信息。换句话说,Button
处理视图的原始事件,将所有触摸事件整合成一个记录是否被点击的事件。
View
类的大多数事件响应接口使用Listener
作为后缀,因此很容易记住它们与事件监听器接口的关联。表 8-4 显示了多个类别及其事故响应功能的示例。
表 8-4。
Examples of Classes and Their Incident-Response Functions
| 班级 | 事件 | 监听器接口和功能 | | --- | --- | --- | | `Button` | 点击 | `onClick()``onClickListener`界面的功能 | | `RadioGroup` | 点击 | `onCheckChange()``onCheckChangeListener`界面的功能 | | `View` | 下拉列表 | `onTouch()``TouchListener`界面的功能 | | 输入焦点改变 | `onFocusChange()``onFocusChangeListener`界面的功能 | | 纽扣 | `onKey()``onKeyListener`界面的功能 |响应事件的过程如下。首先,定义监听器接口的实现类,并覆盖抽象函数。第二,调用set ... Listener()
等函数。然后将自定义监视器接口的实现类设置为相应对象的事件侦听器。
例如,您可以修改应用源来执行事件响应。实现 Java 接口有许多编码风格。下一节讨论运行这些样式的代码的结果是相同的几种方式。
内部类监听器
修改MainActivity.java
代码如下(增加或修改粗体文本):
Line # Source Code
1 package com.example.guiexam;
2 import android.os.Bundle;
3 import android.app.Activity;
4 import android.view.Menu;
5 import android.view.MenuItem;
6 import android.support.v4.app.NavUtils;
7 import android.widget.TextView;
8
import android.widget.Button; // Use Button class
9
10
import android.view.View; // Use View class
11
import android.view.View.OnClickListener; // Use View.OnClickListener class
12
import android.util.Log;
13
// Use Log.d debugging function
public class MainActivity extends Activity {
14
private int iClkTime = 1;
15
16
// Count of Button Click
17
@Override
18 public void onCreate(Bundle savedInstanceState) {
19 super.onCreate(savedInstanceState);
20 setContentView(R.layout.activity_main);
21
22
Button btn = (Button) findViewById(R.id.button1);
23
// Obtain Button object based on the resource ID number
24
final String prefixPrompt ="This is No. ";
25
// Define and set the value of the variable passed
26
final String suffixPrompt ="time(s) that Button is clicked";
27
// Define and set the value of the variable passed
28
btn.setOnClickListener(new /*View.*/OnClickListener(){
29
// Set the event response class of Button's click
30
31
public void onClick(View v) {
32
Log.d("ProgTraceInfo",prefixPrompt + (iClkTime++) + suffixPrompt);
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
在第 1822 行,您分别根据资源 IDEditText
和TextView
获得相应的对象。要使用OnClickListener
作为内部类,需要在变量前面添加final
修饰符。在第 23 行和第 24 行,当Button
的响应代码点击时,您首先使用EditText.getText()
获得EditText
的内容。因为该函数返回类型为Editable
的值,所以您通过CharSequence.toString()
函数将类型Editable
转换为类型String
(CharSequence
是Editable
的超类)。然后调用TextView.setText (CharSequence text)
函数刷新TextView
显示。
在 Android 中,类属性的访问函数通常以set
/ get
开头,比如EditText
内容的读/写函数:
Editable getText()
void setText(CharSequence text, TextView.BufferType type)
该应用的界面如图 8-11 所示;(a)是开始屏幕,(b)是在编辑文本框中输入文本后的屏幕,以及©显示单击按钮后的应用屏幕。
图 8-11。
The interface of the application with a TextView
, a Button
, and an EditText
使用 ImageView
前几节讨论了小部件的典型用途,并展示了小部件编程的基本概念。图像是多媒体应用的基础,因此也是 Android 应用的主要部分。本节介绍图像/图片显示小工具ImageView
的使用。通过本节中的例子,您将学习如何使用ImageView
并将文件添加到项目的资源中。
以下示例最初是在创建 GuiExam 应用时在小节中开发的。按照以下步骤将图片文件添加到项目中:
图 8-13。
The Package Explorer window after the image is added
- 在 Eclipse 中打开项目,并按 F5 键刷新项目。你可以在 Package Explorer 中看到添加到项目中的文件(本例中为
morphing.png
),如图 8-13 所示。
图 8-12。
Copy the image file into the project’s res
directory
- 将图像文件(本例中为
morphing.png
)复制到对应的/res/drawable-XXX
项目目录(存放不同分辨率图像的项目文件的目录),如图 8-12 所示。
要在布局中放置ImageView
小部件,请遵循以下步骤:
- 保存布局文件。
图 8-15。
The property settings of the ImageView
- 调整
ImageView
的大小和位置,并设置其属性。这一步可以使用图 8-15 所示的默认值。
图 8-14。
Place the ImageView
widget in the layout
- 点击选择“Hello world!”的
TextView
小工具项目,然后按 Del 键将小部件从布局中移除。 - 在
layout.xml
的编辑器窗口中,找到图像&媒体分支,将该分支的ImageView
拖放到布局文件中。弹出资源选择器对话框,点击选择项目资源,选择项目下刚刚导入的图片文件,点击确定完成操作。该过程如图 8-14 所示。
通常,此时,您必须编译 Java 代码。然而,在这个例子中,编译是不必要的。图 8-16 显示了应用的界面。
图 8-16。
Application interface of the ImageView
退出活动和应用
在前面的示例中,您可以按下手机的后退按钮来隐藏活动,但这样做不会关闭活动。正如您在“活动的状态转换”一节中看到的,当按下 Back 按钮时,已启动的活动仅从活动状态变为非活动状态,并保留在系统堆栈中。要关闭这些活动并将其从堆栈中移除,您应该使用Activity
类的finish
函数。
但是,结束活动并不意味着申请流程结束。即使应用的所有组件(活动、服务、广播意图接收器等)都已关闭,应用进程仍会继续存在。退出申请流程主要有两种方式。
一个是 Java 提供的强制结束进程的静态函数System.exit
;另一个是 Android 提供的静态函数Process.killProcess (pid)
终止指定的进程 ID (PID)。您可以通过Process.myPid()
静态函数来获取应用的进程 ID。
您可以将这些方法用于“使用 ImageView”一节中的示例具体步骤如下:
- 将
MainActivity.java
文件的源代码修改如下(粗体代码表示添加或修改,带删除线的行表示删除的代码):
图 8-17。
Add Close Activity and Exit Application buttons in the layout
- 在布局文件中添加两个按钮,属性分别为
Text
的“关闭活动”和“退出应用”,ID 属性分别为@+id/closeActivity
和@+id/exitApplication
。调整按钮的大小和位置,如图 8-17 所示。
Line # Source Code
1 package com.example.guiexam;
2 import android.os.Bundle;
3 import android.app.Activity;
4 import android.view.Menu;
5
//import android.view.MenuItem;
6
//import android.support.v4.app.NavUtils;
7
import android.widget.Button; // Use Button class
8
import android.view.View; // Use View class
9
import android.view.View.OnClickListener; // Use View.OnClickListenerClass
10
import android.os.Process; // Use killProcess method
11 public class MainActivity extends Activity {
12 @Override
13 public void onCreate(Bundle savedInstanceState) {
14 super.onCreate(savedInstanceState);
15 setContentView(R.layout.activity_main);
16
Button btn = (Button) findViewById(R.id.closeActivity);
17
// Get Button object of <Closed activity>
18
btn.setOnClickListener(new /*View.*/OnClickListener(){
19
// Set response code for Clicking
20
public void onClick(View v) {
21
finish(); // Close main activity
22
}
23
});
24
btn = (Button) findViewById(R.id.exitApplication);
25
// Get Button object of <Exit Application>
26
// Set the response code to Clicking
27
public void onClick(View v) {
28
finish(); // close main activity
29
Process.killProcess(Process.myPid()); // Exit application process
30
}
31
32
33
34
35 });
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
在第 5 行和第 6 行,您删除了未使用的import
语句。您在第 1621 行设置了“关闭活动”按钮的响应代码,在第 2228 行设置了“退出应用”按钮的响应代码。唯一的区别是后者增加了应用退出代码Process.killProcess (Process.myPid ())
。这两个按钮使用Activity
类的同一个finish()
函数来关闭活动。第 710 行的代码导入相关的类。
应用界面如图 8-18 所示。
图 8-18。
The Close Activity and Exit Application interface of the application
当您单击“关闭活动”或“退出应用”按钮时,应用的主界面将关闭。不同的是,应用进程(com.example.guiexam
)不会因为关闭活动而退出;但是对于退出应用,该过程关闭。这清楚地显示在 Eclipse 中 DDMS 视图的设备窗格中,您可以在其中看到目标机器上的进程列表,如图 8-19 所示。
图 8-19。
The process in DDMS when the Close Activity and Exit Application application is running
摘要
本章通过让您创建一个名为 GuiExam 的简单应用来介绍 Android 界面设计。您了解了活动的状态转换、Context
类、意图以及应用和活动之间的关系。您还看到了如何通过更改布局文件activity_main.xml
将布局用作接口,以及按钮、事件和内部事件监听器是如何工作的。下一章描述了如何使用 activity-intent 机制创建一个包含多个活动的应用,并展示了在AndroidManifest.xml
文件中需要做的修改。
Open Access This chapter is licensed under the terms of the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License ( http://creativecommons.org/licenses/by-nc-nd/4.0/ ), which permits any noncommercial use, sharing, distribution and reproduction in any medium or format, as long as you give appropriate credit to the original author(s) and the source, provide a link to the Creative Commons licence and indicate if you modified the licensed material. You do not have permission under this licence to share adapted material derived from this chapter or parts of it. The images or other third party material in this chapter are included in the chapter’s Creative Commons licence, unless indicated otherwise in a credit line to the material. If material is not included in the chapter’s Creative Commons licence and your intended use is not permitted by statutory regulation or exceeds the permitted use, you will need to obtain permission directly from the copyright holder.
九、Android 应用的 GUI 设计第三部分:设计复杂的应用
Keywords Intent Object Trigger Activity Source Code File Intent Intent Intent Class
在前一章中,您通过创建一个名为GuiExam
的简单应用学习了 Android 界面设计。这一章还涵盖了活动的状态转换、Context
类,以及对意图和应用与活动之间关系的介绍。您了解了如何将布局用作接口,以及按钮、事件和内部事件侦听器如何工作。在本章中,您将学习如何创建一个包含多个活动的应用;示例介绍了活动的显性和隐性触发机制。您将看到一个应用示例,其中的参数由另一个应用中的活动触发,这将有助于您理解活动参数的交换机制。
具有多个活动的应用
上例中的应用只有一个活动:主活动,它在应用启动时显示。本章演示了一个具有多个活动的应用,使用活动意图机制,并展示了在AndroidManifest.xml
文件中需要的更改。
如前所述,活动由意图触发。有两种意图解析方法:显式匹配(也称为直接意图)和(也称为间接意图)。触发活动也可以有参数和返回值。此外,Android 带有许多内置活动,因此触发的活动可以来自 Android 本身,也可以定制。基于这些情况,本章用四个例子来说明不同的活动。对于显式匹配,您可以看到有或没有参数和返回值的应用。对于隐式匹配,您会看到一个应用,它使用来自 Android 系统或用户定义的活动。
触发不带参数的活动的显式匹配
使用不带参数的显式匹配是活动意图最简单的触发机制。本节首先使用一个示例来介绍这种机制,稍后将介绍更复杂的机制。
显式匹配的活动意图触发机制的代码框架包括两部分:被调用方的活动(被触发)和调用方的活动(触发)。触发器不限于活动;它也可以是一种服务,例如广播意图接收器。但是因为到目前为止您只看到了活动的使用,所以本节中所有示例的触发器都是活动。
- 被调用方活动的源代码框架执行以下操作:
- 定义从活动继承的类。
- 如果有需要传递的参数,那么活动的源代码框架调用
onCreate
函数中的Activity.getIntent()
函数,获取触发该活动的Intent
对象,然后通过Intent.getData ()
、Intent.getXXXExtra ()
、Intent.getExtras ()
等函数获取正在传递的参数。 - 为正常活动模式编写代码。
- 如果触发器返回值,则在退出活动之前执行以下操作:
- 定义一个
Intent
对象 - 使用
Intent.putExtras()
等功能设置意图的数据值 - 通过调用
Activity.setResult()
函数设置活动的返回代码
- 定义一个
- 在
AndroidManifest.xml
文件中添加被调用者活动的代码。 - 被调用方活动的代码框架执行以下操作:
- 定义
Intent
对象,并指定触发器的上下文和被触发活动的class
属性。 - 如果需要将参数传递给活动,则通过调用类似于
setData()
、putExtras()
等意图的函数来设置Intent
对象的参数。 - 调用
Activity.startActivity(Intent intent)
函数触发不带参数的活动,或者调用Activity.startActivityForResult(Intent intent, int requestCode)
触发带参数的活动。 - 如果活动需要由返回值触发,那么代码框架重写
Activity
类的onActivityResult()
函数,该函数根据请求代码(requestCode
)、结果代码(resultCode
)和意图(Intent
)值采取不同的操作。
- 定义
在步骤 2a 中,使用了触发活动的 class 属性,这涉及到一种称为反射的 Java 机制。这种机制可以根据类名创建并返回该类的对象。被触发活动的对象在触发之前没有被构造;因此,触发活动也意味着创建该类的对象,以便后续操作可以继续。也就是说,触发活动包括新创建的类对象的操作。
下面两个例子详细说明了代码框架。本节描述第一个。在这个示例中,被触发的活动与触发器的活动属于同一个应用,并且被触发的活动不需要任何参数,也不返回任何值。新的活动是通过一个按钮触发的,它的活动界面类似于“退出活动和应用”一节中的示例界面在第八章中,图 8-16。整个应用界面如图 9-1 所示。
图 9-1。
The application interface with multiple activities in the same application without parameters
应用启动后,显示应用的主活动,如图 9-1(a) 所示。当点击“更改为无参数新界面”按钮时,app 显示新的活动,如图 9-1(b) 所示。点击关闭活动按钮,界面返回到应用的主活动,如图 9-1© 所示。
通过修改并重写第八章中部分的示例,创建此示例,如下所示:
- 为触发的活动生成相应的布局文件:
- 在应用的
res\layout
子目录中右键单击快捷菜单,选择新建➤其他项目。弹出一个新的对话框。选择\XML\XML File
子目录,点击下一步继续。在“新建 XML 文件”对话框中,输入文件名(在本例中为noparam_otheract.xml
),然后单击“完成”。整个过程如图 9-2 所示。
- 在应用的
Note
文件名是布局文件的名称。为了编译成功,必须只使用小写字母;否则,您将得到错误“无效文件名:必须只包含 a-z0-9_ …”
图 9-2。
The layout file for the triggered activity
在项目的包浏览器中可以看到新添加的xxx.xml
文件(本例中为noparam_otheract.xml
),如图 9-3 所示。
Note
右边的布局编辑器窗口还是空的,至今没有可见的界面。
- 在左侧面板中选择
Layouts
子目录,并将布局控件(在本例中为RelativeLayout
)拖动到右侧窗格中的窗口上。你会立即看到一个可视的(手机屏幕形状的)界面,如图 9-4 所示。
图 9-3。
Initial interface of the application’s newly added layout file
- 基于第八章中的“使用 ImageView”一节所述的相同方法,在新布局文件中放置一个
ImageView
和一个按钮。将ImageView
小部件的ID
属性设置为@+id/picture
,将Button
小部件的ID
属性设置为@+id/closeActivity
。Text
属性为“关闭活动”,如图 9-5 所示。最后,保存布局文件。
图 9-4。
Drag-and-drop layout for the newly added layout file
- 为布局文件(Java 源文件)添加相应的
Activity
类。为此,右键单击项目目录下的\src\com.example.XXX
,并在快捷菜单上选择新建➤类。在“新建 Java 类”对话框中,为“名称”输入对应于新布局文件的Activity
类名(在本例中为TheNoParameterOtherActivity
)。单击“完成”关闭对话框。整个过程如图 9-6 所示。
图 9-5。
Final configuration of the newly added layout file
图 9-6。
Corresponding class for the newly added layout file
可以看到新添加的 Java 文件(这里是TheNoParameterOtherActivity.java
)和初始代码,如图 9-7 。
- 编辑新添加的
.java
文件(TheNoParameterOtherActivity.java
)。该类执行触发的活动(被调用方)的活动。其源代码如下(加粗文字或修改):
图 9-7。
Corresponding class and initial source code of the newly added layout
Line # Source Code
1 package com.example.guiexam;
2
import android.os.Bundle; // Use Bundle class
3
import android.app.Activity; // Use Activity Class
4
import android.widget.Button; // Use Button class
5
import android.view.View; // Use View class
6
import android.view.View.OnClickListener; // Use OnClickListener Class
7 public class TheNoParameterOtherActivity extends Activity {
8 // Define Activity subclass
9
@Override
10
protected void onCreate(Bundle savedInstanceState) {
11
// Define onCreate method
12
super.onCreate(savedInstanceState);
13
// onCreate method of calling parent class
14
setContentView(R.layout.noparam_otheract);
15
// Set layout file
16
Button btn = (Button) findViewById(R.id.closeActivity);
17
// Set responding code for <Close Activity> Button
18
btn.setOnClickListener(new /*View.*/OnClickListener(){
19
public void onClick(View v) {
finish();
// Close this activity
}
});
}
}
在第 7 行,您为新创建的类添加了超类Activity
。第 8 行到第 18 行的代码类似于应用的主活动。注意,在第 14 行,代码调用了setContentView()
函数来为Activity
设置布局,其中的参数是在第一步中创建的新布局 XML 文件的前缀名。
-
编辑触发器(调用方)活动的代码。触发活动是应用的主要活动。源代码为
MainActivity.java
,布局文件为activity_main.xml
。编辑的步骤如下:- 编辑布局文件,删除原来的
TextView
小部件,添加一个按钮。将其ID
属性设置为@+id/goTONoParamNewAct
,将其Text
属性设置为“无参数切换界面”,如图 9-8 所示。
- 编辑布局文件,删除原来的
-
编辑触发活动的源代码文件(在本例中为
MainActivity.java
),如下所示(添加或修改粗体文本):
图 9-8。
Layout configuration for the trigger activity
Line # Source Code
1 package com.example.guiexam;
2 import android.os.Bundle;
3 import android.app.Activity;
4 import android.view.Menu;
5
import android.content.Intent; // Use Intent class
6
import android.widget.Button; // Use Button class
7
import android.view.View.OnClickListener;
8
import android.view.View;
9 public class MainActivity extends Activity {
10 @Override
11 public void onCreate(Bundle savedInstanceState) {
12 super.onCreate(savedInstanceState);
13 setContentView(R.layout.activity_main);
14
Button btn = (Button) findViewById(R.id.goTONoParamNewAct);
15
btn.setOnClickListener(new /*View.*/OnClickListener(){
16
public void onClick(View v) {
17
Intent intent = new Intent(MainActivity.this, TheNoParameterOtherActivity.class);
18
startActivity(intent);
19
}
20
});
21 }
22 @Override
23 public boolean onCreateOptionsMenu(Menu menu) {
24 getMenuInflater().inflate(R.menu.activity_main, menu);
25 return true;
26 }
27 }
第 17 行的代码定义了一个意图。这种情况下的构造函数原型是
Intent(Context packageContext, Class<?> cls)
第一个参数是触发活动,在本例中是主活动;this
因为用在内部类中,所以前面有类名修饰符。第二个参数是被调用方(被触发)活动的类。它使用.class
属性来构造它的对象(所有 Java 类都有.class
属性)。
第 18 行调用startActivity
,运行 intent。该函数不向触发的活动传递任何参数。函数原型是
void Activity.startActivity(Intent intent)
- 编辑
AndroidManifest.xml
文件。添加被调用方活动的描述性信息(添加了粗体文本)以注册新的Activity
类:
Line # Source Code
1 <manifest xmlns:android="
http://schemas.android.com/apk/res/android
2 package="com.example.guiexam"
3 android:versionCode="1"
4 android:versionName="1.0" >
... ... ...
10 <application
11 android:icon="@drawable/ic_launcher"
12 android:label="@string/app_name"
13 android:theme="@style/AppTheme" >
14 <activity
15 android:name=".MainActivity"
16 android:label="@string/title_activity_main" >
17 <intent-filter>
18 <action android:name="android.intent.action.MAIN" /> 19
20 <category android:name="android.intent.category.LAUNCHER" /> 21 </intent-filter>
22 </activity>
23``<activity android:name=".TheNoParameterOtherActivity" android:label="the other Activity"/>
25
26 </manifest>
您也可以用以下方法替换此 XML 代码:
- 方法 1:
<activity android:name="TheNoParameterOtherActivity" android:label=" the other Activity"> </activity>
- 方法 2:
<activity android:name=".TheNoParameterOtherActivity " />
- 方法 3:
<activity android:name=".TheNoParameterOtherActivity"></activity>
android: name
文本字段的内容是被调用者活动的类名:TheNoParameterOtherActivity
。
注意,如果在Activity
类android: name
的名称前添加一个句点(.
),编译器会在 XML 文件的这一行给出如下警告(只是警告,不是编译错误):
Exported activity does not require permission
触发活动与不同应用参数的显式匹配
前面几节介绍了在同一个应用中触发另一个没有参数的活动。触发器的活动是被调用者允许交换参数:触发器可以为被调用者指定某些参数,被调用者可以在退出时将这些参数值返回给触发器。此外,被调用者和触发器可以在完全不同的应用中。本节显示了一个应用示例,该应用的参数由另一个应用中的活动触发。这个例子将帮助您理解活动参数的交换机制。
使用与第八章中相同的GuiExam
应用。界面如图 9-9 所示。
图 9-9。
The interface of multiple activities in different applications
如图 9-9 所示,触发活动在GuiExam
应用中,其中有一个变量接受天气条件条目。图 9-9(a) 中的界面在GuiExam
应用打开时显示。点击进入新界面,修改天气盒,触发HelloAndroid
中的活动。当该活动开始时,显示在设置新天气文本框中传递的新天气情况,如图 9-9(b) 所示。现在,在 Set New Weather 中输入一个新的天气条件值,然后单击 OK Change 关闭触发器的活动。Set New Weather 返回的新值刷新触发器活动中的Weather
变量,如图 9-9(d) 所示。如果你点击取消更改,它做同样的事情并关闭活动,但是值Weather
不变,如图 9-9(f) 所示。
正在执行的应用的进程列表如图 9-10 所示(显示在 Eclipse 中主机的 DDMS 窗口中)。
图 9-10。
Process list in DDMS for the multiple-activity application
图 9-10 显示当应用启动时,只有触发器GuiExam
的进程在运行。但是当你点击进入新界面修改天气时,新活动被触发,新活动HelloAndroid
的流程运行,如图 9-10(b) 所示。当您点击确认变更或取消变更时,被触发的活动关闭,但是HelloAndroid
过程不会退出,如图 9-10© 所示。有趣的是,即使GuiExam
触发流程存在,被触发的活动所属的HelloAndroid
流程仍然处于运行状态。
构建步骤如下:
-
修改触发应用的
GuiExam
代码:- 通过删除原来的
TextView
小部件来编辑主布局文件(本例中为activity_main.xml
);然后添加三个新的TextView
部件和一个按钮。如下设置它们的属性:将两个TextView
的Text
属性设置为“这个接口是 GuiExam 应用中调用者的活动”和“今天的天气:”。将第三个TextView
的ID
属性设置为@+id/weatherInfo
。按钮的Text
属性为“进入新界面改变天气”,其ID
属性为@+id/modifyWeather
。如图 9-11 所示调整各部件的大小和位置。
- 通过删除原来的
-
修改
MainActivity.java
的内容,如下图所示:
图 9-11。
The main layout design for the GuiExam
trigger application
Line # Source Code
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; // Use Button class
6
import android.view.View; // Use View class
7
import android.view.View.OnClickListener; // Use View.OnClickListener class
8
import android.widget.TextView; // Use TextView class
9
import android.content.Intent; // Use Intentclass
10 public class MainActivity extends Activity {
11
public static final String INITWEATHER = "
阳光明媚; // /Initial Weather
12
public static final int MYREQUESTCODE =100;
13
// Request Code of triggered Activity
14
private TextView tv_weather;
15
// The TextView Widget that displays Weather info
16 @Override
17 public void onCreate(Bundle savedInstanceState) {
18 super.onCreate(savedInstanceState);
19 setContentView(R.layout.activity_main);
20
tv_weather = (TextView)findViewById(R.id.weatherInfo);
21
tv_weather.setText(INITWEATHER);
22
Button btn = (Button) findViewById(R.id.modifyWeather);
23
// Get Button object according to resource ID #
24
btn.setOnClickListener(new /*View.*/OnClickListener(){
25
// Set responding code click event
26
public void onClick(View v) {
27
Intent intent = new Intent();
28
intent.setClassName("com.example.helloandroid",
29
// the package ( application) that the triggered Activity is located
30
"com.example.helloandroid.TheWithParameterOtherActivity");
31
// triggered class ( full name)
String wthr = tv_weather.getText().toString();
32
// Acquire the value of weather TextView
33
intent.putExtra("weather",wthr); // Set parameter being passed to Activity
34 startActivityForResult(intent,
MYREQUESTCODE);
35
// Trigger Activity
36
}
37
});
38 }
39
40
@Override
41
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
42
// Triggered Activity finish return
43
super.onActivityResult(requestCode, resultCode, data);
44
if (requestCode == MYREQUESTCODE) {
45
// Determine whether the specified Activity end of the run
if (resultCode == RESULT_CANCELED)
46
{ }
47
// Select "Cancel" to exit the code, this case is empty
48
else if (resultCode == RESULT_OK) {
49
// Select <OK> to exit code
50
String wthr = null;
51
wthr = data.getStringExtra("weather");
// Get return value
if (wthr != null)
tv_weather.setText(wthr);
// Update TextView display of weather content
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
第 23–28 行的代码用其他应用中的参数触发活动。第 23–25 行建立了触发器意图,它使用了Intent.setClassName()
函数。原型是
Intent Intent.setClassName(String packageName, String className);
第一个参数是被触发的活动所在的包的名称,第二个参数是被触发的活动的类名(需要使用全名)。通过使用startActivity ...
函数触发活动,系统可以准确定位应用和活动类。
第 28 行将参数作为附加数据附加到意向中。Intent
有一系列的putExtra
函数来附加附加数据,还有一系列的getXXXExtra
函数来从意图中提取数据。附加数据也可以由Bundle
类组装。Intent
提供了添加数据的putExtras
函数和获取数据的getExtras
函数。putExtra
使用属性-值数据对或变量名-值数据对来添加和检索数据。在本例中,Intent.putExtra("weather", "XXX")
保存由weather
变量名和值“XXX”组成的数据对,作为意图的附加数据。
带有Intent.getStringExtra("weather")
的代码行从附加的意图数据中获取weather
变量的值,并返回字符串类型。
关于这些函数和Bundle
类的更多细节可以在 Android 网站的文档中找到。这里不再进一步讨论它们。
在第 33–46 行,您重写了Activity
类的onActivityResult
函数。当触发的活动关闭时,调用此函数。在第 36 行,首先根据请求代码确定哪个活动被关闭并返回。然后,根据结果代码和请求代码,判断它是由 OK 还是 Cancel 单击返回。第 40–50 行从返回的意向中获取协商的变量值。第 42 行根据变量的返回值更新接口。在这个函数中,如果用户点击 Cancel 返回,你什么都不做。
- 修改被调用程序应用
HelloAndroid
的代码,如图 9-12 所示:- 使用本章前面的“触发活动与不同应用的参数的显式匹配”一节中描述的方法,添加一个布局文件(在本例中名为
param_otheract.xml
),并将一个RelativeLayout
布局拖放到该文件中。 - 通过添加两个
TextView
部件、一个EditText
部件和两个Button
部件来编辑这个布局文件。按如下方式设置它们的属性:- 两个
TextView
小部件的Text
属性:“该接口是 HelloAndroid 应用中调用者的活动”和“将新天气设置为:” EditText
的ID
属性:@+id/editText_NewWeather
- 两个
Button
的Text
属性:“确认变更”和“取消变更” - 两个
Button
的ID
属性:@+id/button_Modify
和@+id/button_Cancel
- 两个
- 使用本章前面的“触发活动与不同应用的参数的显式匹配”一节中描述的方法,添加一个布局文件(在本例中名为
然后调整它们的大小和位置。
- 如“触发活动与不同应用参数的显式匹配”一节所述,为新的布局文件添加相应的类(本例中为
TheWithParameterOtherActivity)
),如图 9-13 所示。
图 9-12。
New layout design of the triggered (callee) application
- 为新添加的布局文件编辑类文件(在本例中为
TheWithParameterOtherActivity.java
)。内容如下:
图 9-13。
Add the corresponding class for the newly added layout file in the HelloAndroid
project
Line # Source Code
1 package com.example.helloandroid;
2
import android.os.Bundle; // Use Bundle Class
3``import android.app.Activity;
4
import android.content.Intent; // Use Intent Class
5
import android.widget.Button; // Use Button Class
6
import android.view.View; // Use View Class
7
import android.view.View.OnClickListener; // Use OnClickListener Class
8
import android.widget.EditText; // Use EditText Class
9 public class TheWithParameterOtherActivity extends Activity {
10
private String m_weather;
11
// Save new weather variable
12
@Override
13
protected void onCreate(Bundle savedInstanceState) {
14
// Define onCreate method
15
super.onCreate(savedInstanceState);
16
// method of call onCreate Super Class
17
setContentView(R.layout.withparam_otheract); // Set layout file
18
Intent intent = getIntent();
19
// Get Intent of triggering this Activity
20
m_weather = intent.getStringExtra("weather");
21
// Get extra data from Intent
22
final EditText et_weather = (EditText) findViewById(R.id.editText_NewWeather);
23
et_weather.setText(m_weather,null);
24
// Set initial value of "New Weather" EditText according to extra data of the Intent
25
Button btn_modify = (Button) findViewById(R.id.button_Modify);
26
btn_modify.setOnClickListener(new /*View.*/OnClickListener(){
27
// Set corresponding code of <Confirm Change>
28
public void onClick(View v) {
29
Intent intent = new Intent();
30
// Create and return the Intent of Data storage
31
String wthr = et_weather.getText().toString();
32
// Get new weather value from EditText
33
intent.putExtra("weather",wthr);
34
// Put new weather value to return Intent
35
setResult(RESULT_OK, intent);
36
// Set <Confirm> and return data
37
finish(); // Close Activity
}
});
Button btn_cancel = (Button) findViewById(R.id.button_Cancel);
btn_cancel.setOnClickListener(new /*View.*/OnClickListener(){
//Set corresponding code for <Cancel Change>
public void onClick(View v) {
setResult(RESULT_CANCELED, null);
// Set return value for <Cancel>
finish(); // Close this Activity
}
});
}
}
这段代码遵循活动的框架。它在第 11 行设置了活动布局,使得布局名称与步骤 1 中创建的布局文件名相同(没有扩展名)。在第 1922 行,它首先构造一个返回意图,然后将额外的数据作为返回数据添加到Intent
对象中。在第 21 行,它将活动和意图的返回值设置为返回数据载体。setResult
函数的原型是
final void Activity.setResult(int resultCode, Intent data);
如果resultCode
为RESULT_OK
,则用户已经点击确定返回;如果是RESULT_CANCELLED
,用户点击了取消返回。在这种情况下,返回数据载体意图可以为空,这在第 27 行中设置。
- 用以下代码修改由应用触发的
AndroidManifest.xml
:
Line # Source Code
1 <manifest xmlns:android="
http://schemas.android.com/apk/res/android
2 package="com.example.helloandroid"
3 android:versionCode="1"
4 android:versionName="1.0" >
5
6 <uses-sdk
7 android:minSdkVersion="8"
8 android:targetSdkVersion="15" /> 9
10 <application
11 android:icon="@drawable/ic_launcher"
12 android:label="@string/app_name"
13 android:theme="@style/AppTheme" >
14 <activity
15 android:name=".MainActivity"
16 android:label="@string/title_activity_main" >
17 <intent-filter>
18 <action android:name="android.intent.action.MAIN" /> 19
20 <category android:name="android.intent.category.LAUNCHER" /> 21 </intent-filter>
22 </activity>
23
<activity
24
android:name="TheWithParameterOtherActivity">
25
<intent-filter>
26``<action android:name="android.intent.action.DEFAULT" />``27
28
</activity>
29 </application>
30
31 </manifest>
- 第 24–29 行是新的。与前面的小节一样,您添加一个附加的活动描述并指定它的类名,这是在第二步中生成的触发活动的类名。有关修改
AndroidManifest.xml
文件的信息,请参见“触发不带参数的活动的显式匹配”一节。与该部分不同,您不仅添加了活动及其name
属性的文档,还添加了意图过滤指令和状态,以接受在Action
元素中描述的默认动作并触发这个Activity
类。在缺少活动的意图过滤器描述的情况下,无法激活活动。 - 运行被调用方应用来注册活动的组件。在被调用程序应用
HelloAndroid
执行一次之前,对AndroidManifest.xml
文件的修改不会注册到 Android 系统。因此,这是完成其包含活动的注册的重要步骤。
内置活动的隐式匹配
在前两节的例子中,在您通过Activity.startActivity()
函数或函数触发同一应用或不同应用的活动之前,Intent
对象的构造函数通过.class
属性或通过字符串中的类名显式指定了类。这样,系统可以找到要触发的类。这种方法被称为显式意图匹配。下一个示例显示如何触发未指定的类。相反,系统使用一种称为隐式意图匹配的方法来解决这个问题。
此外,Android 还有许多已经实现的活动,如拨号、发送短信等。本节中的示例解释了如何使用 can 隐式匹配来触发这些内置活动。应用界面如图 9-14 所示。
图 9-14。
The application interface when using implicit intent to trigger a built-in activity
用户启动GuiExam
应用并点击屏幕上的输入拨号活动按钮。它触发系统附带的拨号活动。
在这种情况下,您修改了GuiExam
项目,并将该应用用作触发器。隐式匹配触发的活动是拨号活动。构建这个示例的步骤如下。
-
在
GuiExam
应用的布局文件(activity_main.xml
)中,删除原来的TextView
小部件,添加一个按钮,将其ID
属性设置为@+id/goTODialAct
,将其Text
属性设置为“进入拨号活动”。如图 9-15 所示调整其尺寸和位置。 -
修改源代码文件(
MainActivity.java
),如下所示:
图 9-15。
Layout file of the application for the implicit match built-in activity
Line # Source Code
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; // Use Button Class
6
import android.view.View; // Use View Class
7
import android.view.View.OnClickListener; // Use View.OnClickListener Class
8
import android.content.Intent; // Use Intent Class
9
import android.net.Uri; // Use URI Class
10 public class MainActivity extends Activity {
11 @Override
12 public void onCreate(Bundle savedInstanceState) {
13 super.onCreate(savedInstanceState);
14 setContentView(R.layout.activity_main);
15
Button btn = (Button) findViewById(R.id.goTODialAct);
16
btn.setOnClickListener(new /*View.*/OnClickListener(){
17
// Set corresponding Code for Click Activity
18
public void onClick(View v) {
19
Intent intent = new Intent(Intent.ACTION_DIAL,Uri.parse("tel:13800138000"));
20
startActivity(intent); // Trigger corresponding Activity
21
}
22
});
}
23
24 @Override
25 public boolean onCreateOptionsMenu(Menu menu) {
26 getMenuInflater().inflate(R.menu.activity_main, menu);
27 return true;
28 }
}
第 16 行的代码定义了一个间接意图(即隐式匹配的意图。之所以称为间接意图,是因为对象的构造函数中没有指定需要触发的类;构造器只描述了需要触发完成拨号的类的功能。间接意图的构造函数如下:
Intent(String action)
Intent(String action, Uri uri)
这些函数需要在被调用时能够完成指定动作的类(活动)。两者的唯一区别是第二个函数也带有数据。
此示例使用第二个构造函数,它需要可以完成拨号的活动和作为电话号码字符串的额外数据。因为应用没有指定触发类型,所以 Android 从注册的类列表中找到处理这个动作的类(例如,Activity
)并触发事件的开始。
如果多个类可以处理指定的动作,Android 会弹出一个选择菜单,用户可以选择运行哪个。
参数action
可以使用系统预定义的字符串。在前面的例子中,Intent.ACTION_DIAL
是由Intent
类定义的ACTION_DIAL
的字符串常量。一些系统预定义的ACTION
示例如表 9-1 所示。
表 9-1。
Some System-Predefined Constants
| 动作常数名称 | 价值 | 描述 | | --- | --- | --- | | `ACTION_MAIN` | `android.intent.action.MAIN` | 作为任务的初始活动启动,没有数据输入,也没有返回输出。 | | `ACTION_VIEW` | `android.intent.action.VIEW` | 在意向 URI 中显示数据。 | | `ACTION_EDIT` | `android.intent.action.EDIT` | 请求编辑数据的活动。 | | `ACTION_DIAL` | `android.intent.action.DIAL` | 启动电话拨号器,并使用数据中预设的号码进行拨号。 | | `ACTION_CALL` | `android.intent.action.CALL` | 发起电话呼叫,并立即使用数据 URI 中的号码发起呼叫。 | | `ACTION_SEND` | `android.intent.action.SEND` | 开始发送特定数据的活动(接收者由活动解析选择)。 | | `ACTION_SENDTO` | `android.intent.action.SENDTO` | 通常,开始一项活动,向 URI 中指定的联系人发送消息。 | | `ACTION_ANSWER` | `android.intent.action.ANSWER` | 打开一个处理来电的活动。目前它是由本地电话拨号工具处理的。 | | `ACTION_INSERT` | `android.intent.action.INSERT` | 打开一个活动,该活动可以在特定数据字段中的附加光标处插入一个新项目。当它作为子活动被调用时,它必须返回新插入项目的 URI。 | | `ACTION_DELETE` | `android.intent.action.DELETE` | 开始一个活动,删除 URI 位置的数据端口。 | | `ACTION_WEB_SEARCH` | `android.intent.action.WEB_SEARCH` | 打开一个活动,并根据 URI 数据中的文本运行网页搜索。 |ACTION
常量名称是隐式匹配意图的构造函数中使用的第一个参数。在接收该动作的活动的AndroidManifest.xml
语句中使用的ACTION
常量的值在本节中不使用,但在下一节中使用。您可以在android.content.Intent
帮助文档中找到关于预定义的ACTION
值的更多信息。
使用自定义活动的隐式匹配
前面的例子使用隐式匹配来触发 Android 系统附带的活动。在本节中,您将看到一个如何使用隐式匹配来触发自定义活动的示例。
这个示例应用的配置类似于“触发活动与不同应用的参数的显式匹配”一节中的配置触发应用托管在GuiExam
项目中,隐式匹配触发的自定义活动在HelloAndroid
应用中。界面如图 9-16 所示。
图 9-16。
The interface of implicit match that uses a custom activity
图 9-16(a) 显示了GuiExam
触发应用启动时的界面。当您单击“显示隐含意图的活动”按钮时,系统会根据ACTION_EDIT
动作的要求找到符合条件的候选活动,并显示这些候选活动的事件列表(b)。当用户自定义的HelloAndroid
应用被选中时,可以接收HelloAndroid
应用中意图过滤器所声明的ACTION_EDIT
动作的活动被显示©。当您点击关闭活动按钮时,应用返回到原来的GuiExam
活动界面(d)。
和前面的例子一样,这个例子是基于修改GuiExam
项目。步骤如下:
-
编辑主布局文件(
activity_main.xml
)。删除原来的TextView
小部件,然后添加一个TextView
和一个按钮。将TextView
的Text
属性设置为“该应用是调用方使用隐式意图触发的活动”。将按钮的Text
属性设置为“显示隐含意图触发的活动”,将其ID
属性设置为@+id/goToIndirectAct
,如图 9-17 所示。 -
编辑
MainActivity.java
如下:
图 9-17。
The main layout design for the GuiExam
trigger application
Line # Source Code
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; // Use Button Class
6``import android.view.View;
7
import android.view.View.OnClickListener; // Use View.OnClickListener class
8
import android.content.Intent; // Use Intent Class
9 public class MainActivity extends Activity {
10 @Override
11 public void onCreate(Bundle savedInstanceState) {
12 super.onCreate(savedInstanceState);
13 setContentView(R.layout.activity_main);
14
Button btn = (Button) findViewById(R.id.goToIndirectAct);
15
btn.setOnClickListener(new /*View.*/OnClickListener(){
16
// Set respond Code for Button Click event
17
public void onClick(View v) {
18
Intent intent = new Intent(Intent.ACTION_EDIT);
19
// Construct implicit Inent
20
startActivity(intent); // Trigger Activity
21
}
});
22 }
23
24 @Override
25 public boolean onCreateOptionsMenu(Menu menu) {
26 getMenuInflater().inflate(R.menu.activity_main, menu);
27 return true;
}
}
第 16 行和第 17 行中的代码定义了隐式意图并触发相应的活动,这与前面触发隐式活动的代码基本相同,但这里它使用了没有数据的意图的构造函数。
- 修改包含具有相应隐含意图的自定义活动的
HelloAndroid
应用:- 根据本章前面的“触发不带参数的活动的显式匹配”一节中描述的方法,将一个布局文件(
implicit_act.xml
)添加到项目中,并将一个RelativeLayout
布局拖放到文件中。 - 编辑布局文件,添加
TextView
、ImageView
和Button
小部件。按如下方式设置属性:TextView
的Text
属性:“该接口是 HelloAndroid 的一个活动,负责 ACTION_EDIT 触发的动作”。ImageView
:完全按照第八章的中的“使用 ImageView”一节进行设置。Button
的Text
属性:“关闭活动”。Button
的ID
属性:@+id/closeActivity
。
- 根据本章前面的“触发不带参数的活动的显式匹配”一节中描述的方法,将一个布局文件(
然后调整各自的尺寸和位置,如图 9-18 所示。
- 类似于“触发无参数活动的显式匹配”一节中描述的过程,为新的布局文件(
TheActivityToImplicitIntent
)向项目添加相应的类,如图 9-19 所示。
图 9-18。
Layout file for the custom activity of the corresponding implicit intent
- 为新添加的布局文件(
TheActivityToImplicitIntent.java
)编辑类文件,如下所示:
图 9-19。
New class for the custom activity of the corresponding implicit intent
Line # Source Code
1 package com.example.helloandroid;
2
import android.os.Bundle;
3
import android.app.Activity;
4
import android.widget.Button; // Use Button Class
5
import android.view.View; // Use View class
6
import android.view.View.OnClickListener; // Use View.OnClickListener class
7 public class TheActivityToImplicitIntent extends Activity {
8
@Override
9
public void onCreate(Bundle savedInstanceState) {
10
super.onCreate(savedInstanceState);
11
setContentView(R.layout.implicit_act);
12
Button btn = (Button) findViewById(R.id.closeActivity);
13
btn.setOnClickListener(new /*View.*/OnClickListener(){
14
// Set response code for <Close Activity> Click
15
public void onClick(View v) {
16
finish();
17
}
18
});
19
}
}
- 修改包含相应隐含意图的
HelloAndroid
自定义应用的AndroidManifest.xml
文件,如下所示:
Line # Source Code
1 <manifest xmlns:android="
http://schemas.android.com/apk/res/android
2
3 package="com.example.helloandroid"
4
5 android:versionCode="1"
6
7 android:versionName="1.0" >
8
9 <uses-sdk
10 android:minSdkVersion="8"
11 android:targetSdkVersion="15" /> 12
13 <application
14 android:icon="@drawable/ic_launcher"
15 android:label="@string/app_name"
16 android:theme="@style/AppTheme" >
17 <activity
18 android:name=".MainActivity"
19 android:label="@string/title_activity_main" >
20 <intent-filter>
21 <action android:name="android.intent.action.MAIN" /> 22
23 <category android:name="android.intent.category.LAUNCHER" /> 24 </intent-filter>
25 </activity>
26
<activity
27
android:name="TheActivityToImplicitIntent">
28
<intent-filter>
29
<action android:name="android.intent.action.DEFAULT" />
30
<action android:name="android.intent.action.EDIT" />
31
<category android:name="android.intent.category.DEFAULT" />
32
</intent-filter>
33
</activity>
</application>
</manifest>
第 24–32 行的代码(粗体)给出了接收隐含意图的活动信息。第 26 行指定您可以接收一个android.intent.action.EDIT
动作。该值对应于触发器意图构造函数的ACTION
参数Intent.ACTION_EDIT
的常数值(GuiExam
的MainActivity
类)。这是触发器和被呼叫者之间的预定接触信号。第 27 行进一步指定也可以接收默认数据类型。
- 运行应用
HelloAndroid
,它现在包含了对应隐式意图的定制活动,并在系统中注册了它的AndroidManifest.xml
文件。
摘要
到目前为止,已经有三章介绍了 Android 界面设计。简单的GuiExam
应用演示了活动的状态转换、Context
类、意图以及应用和活动之间的关系。您还了解了如何将布局用作接口,以及按钮、事件和内部事件侦听器如何工作。多个活动的例子介绍了活动的显式和隐式触发机制。您看到了一个应用的示例,该应用的参数由另一个应用中的活动触发,现在您已经理解了活动参数的交换机制。
到目前为止讨论的应用界面基本上类似于对话框界面。这种模式的缺点是难以获得准确的触摸屏输入,从而难以基于输入界面显示准确的图像。下一章涵盖了 Android 界面设计的最后一部分,介绍了基于视图的交互风格界面。在这个界面中,您可以通过精确的触摸屏输入来输入信息,并显示详细的图像,这是许多游戏应用所要求的。
Open Access This chapter is licensed under the terms of the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License ( http://creativecommons.org/licenses/by-nc-nd/4.0/ ), which permits any noncommercial use, sharing, distribution and reproduction in any medium or format, as long as you give appropriate credit to the original author(s) and the source, provide a link to the Creative Commons licence and indicate if you modified the licensed material. You do not have permission under this licence to share adapted material derived from this chapter or parts of it. The images or other third party material in this chapter are included in the chapter’s Creative Commons licence, unless indicated otherwise in a credit line to the material. If material is not included in the chapter’s Creative Commons licence and your intended use is not permitted by statutory regulation or exceeds the permitted use, you will need to obtain permission directly from the copyright holder.