Android开发秘籍(第2版)

978-7-115-35517-1
作者: 【美】Ronan Schwarz Phil Dutson James Steele Nelson To
译者: 钱昊
编辑: 杨海玲

图书目录:

详情

本书通过大量代码秘诀全面详尽地讲述了Android开发技术。从activity 和intent基础知识开始,再到用户界面、多媒体技术、硬件接口、网络通信、数据存储、基于位置的服务、调试等,书中贯穿了经Android 设备或者模拟器测试的可用范例。

图书摘要

PEARSON

Android开发秘籍(第2版)

The Android™ Developer's Cookbook Building Applications with the Android SDK Second Edition

[美]Ronan Schwarz Phil Dutson James Steele Nelson To 著

钱昊 译

人民邮电出版社

北京

图书在版编目(CIP)数据

Android开发秘籍:第2版/(美)施瓦茨(Schwarz,R.)等著;钱昊译.--2版.--北京:人民邮电出版社,2014.8

书名原文:The Android developer's cookbook:building applications with the Android SDK,second Edition

ISBN 978-7-115-35517-1

Ⅰ.①A… Ⅱ.①施…②钱… Ⅲ.①移动终端—应用程序—程序设计 Ⅳ.①TN929.53

中国版本图书馆CIP数据核字(2014)第130822号

内容提要

本书秉承“一个清晰可用的范例,胜过千言的文档”的原则,以一百多个范例为骨架,将知识、技巧和理念融入其中,从零开始,介绍了Android移动开发的方方面面。从Android及其设备的发展、Android项目的建立等入门内容,到Activity、Intent、视图、线程、服务、用户界面布局、事件等基本要素,再到多媒体、硬件接口、网络、位置服务、应用内计费、消息推送等高级特性,最后还介绍了原生开发、测试与调试。本书致力于让读者充分理解和利用Android的各种特性,并十分强调设备与版本的兼容性、代码的复用性、项目的健壮性、方法的多样性等良好的开发理念。

本书的内容由浅入深,方便Android开发初学者上手;书中介绍的技巧彼此相关又相对独立,因此也适合有一定经验的开发者查阅参考。

◆著 [美]Ronan Schwarz Phil Dutson James Steele Nelson To

译 钱昊

责任编辑 杨海玲

责任印制 彭志环 焦志炜

◆人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

北京鑫正大印刷有限公司印刷

◆开本:800×l000 1/16

印张:23

字数:521千字  2014年8月第2版

印数:1-3000册  2014年8月北京第1次印刷

著作权合同登记号 图字:01-2013-7660号

定价:59.00元

读者服务热线:(010)81055410 印装质量热线:(010)81055316

反盗版热线:(010)81055315

版权声明

Authorized translation from the English language edition,entitled The Android Developer's Cookbook,9780321897534 by Ronan Schwarz,Phil Dutson,James Steele,and Nelson To,published by Pearson Education,Inc.,publishing as Addison-Wesley,Copyright © 2013 by Pearson Education,Inc.

All rights reserved.No part of this book may be reproduced or transmitted in any form or by any means,electronic or mechanical,including photocopying,recording or by any information storage retrieval system,without permission from Pearson Education,Inc.

CHINESE SIMPLIFIED language edition published by PEARSON EDUCATION ASIA LTD.and POSTS & TELECOM PRESS Copyright © 2014.

本书中文简体字版由Pearson Education Asia Ltd.授权人民邮电出版社独家出版。未经出版者书面许可,不得以任何方式复制或抄袭本书内容。

本书封面贴有Pearson Education(培生教育出版集团)激光防伪标签,无标签者不得销售。

版权所有,侵权必究。

封面介绍

封面图片呈现了塞尔弗里奇百货公司[1]外墙的特写镜头,该建筑于2003年落成于英格兰伯明翰。其设计机构Future Systems宣称:其微光闪烁的设计营造了“精美的、如同蛇皮、或点缀着闪光金属片的帕科[2]服装一般的光彩熠熠的纹理”。它一落成,就立即被视为令人难忘的、有地标意义的建筑,成为重建的规模宏大的伯明翰Bull Ring购物商圈[3]的一部分。该区域作为众多商家落户的场所,已有超过850年的历史。塞尔弗里奇百货公司的建筑设计赢得了包括Royal Fine Arts Commission、Retail Week、U.K.’s Concrete Society等组织机构颁发的众多荣誉奖项。

[1].塞尔弗里奇百货公司(Selfridge’s department store)为英国一家有百年历史的著名百货公司,除本书所提到的伯明翰门店外,在伦敦、曼彻斯特等地尚有多家门店。——译者注

[2].帕科(Paco Rabonne)是创建于20世纪60年代的法国高级时装品牌,以大胆地将各种新材料运用于服装上而著称。于1966年首次推出由金属和塑料片制成的服装,在当时引起轰动。——译者注

[3].伯明翰Bull Ring(Birmingham’s Bull Ring)为英格兰最著名的商业区之一。除上面提到的塞尔弗里奇百货公司外,还包含众多其他门店及露天市场等。——译者注

对本书的赞誉

《Android开发秘籍(第2版)》包含了关于如何开发和营销一个成功的Android应用的各种技巧。每一个技巧都含有详细的解释和示例,讲解怎样正确地编写程序,使其能成为Google Play Store[1]上别具一格的应用。以理解不同Android版本的基本特征、从而设计和构建出能适应这些不同的应用为开端,本书将教授诸多引领你走上成功之路的开发技巧。你将学会在 Android 系统的不同层面上进行工作,从使用硬件接口(如NFC和USB),到能够指明如何有效地使用移动数据的网络接口,甚至还包括如何利用Google强大的计费接口(billing interface)。几位作者工作的杰出之处在于,他们为书中每个概念都配上了实用的、与现实生活息息相关的代码示例,这些示例可以被轻而易举地构建,并适用于各种各样的情况,使得本书成为一切 Android 开发者案头必备之书。

——David Brown,圣胡安(San Juan)学区信息数据管理员兼应用程序开发员

清晰易读、通俗易懂,但并非平淡无奇。这是我读过的Android开发领域书籍中最好的作品之一。如果你已经有了一定的基础,那么书中的技巧一定会引领你迈向大师的行列。

——Casey Doolittle,Icon Health and Fitness首席Java开发者

《Android开发秘籍(第2版)》提供了出类拔萃的Android开发基础知识。书中教授了诸如布局、Android生命周期及基于多种多线程技术的响应性等内容,这些都是成为Android达人所必需的。

——Kendell Fabricius,自由Android开发者

每个人从本书中都能得到需要的东西。我自从1.0版起就从事Android编程,但仍从本书中学到了前所未见的东西。

——Douglas Jones,Fullpower Technologies高级软件工程师

[1].最初名为Google Market,为Google官方提供的发布Android应用程序的平台。2013年3月更名为Google Play Store。——译者注

其他

献给我挚爱的妻子Susan和OpenIntents社区:谢谢你们给予我的支持。

——Ronan

献给Martin Simonnet和Niantic Project,为他们带给我的一切快乐致谢。

——Phil

深情地献给Wei!

——Jim

献给我亲爱的妈妈!

——Nelson

前言

Android是当今成长最快的移动操作系统。同时,Google Play Store拥有的有效应用已超过80万人,意味着 Android“生态系统”也在不断发展壮大。不同设备特性和不同无线运营商带来的多样性使Android几乎对所有人都有吸引力。

上网本(netbook)始终是一类能够自然而然地接受Android的平台,但Android的活力已经使其在平板电脑(tablet)、电视甚至汽车等诸多领域都茁壮成长。世界上许多顶级大公司,从银行,到快餐连锁企业,再到航空公司,已然共同营造了一个Android的世界,并提供相互兼容的各种服务。Android开发者获得了很多机遇,Android应用获得了比以往任何时候都多的受众这一事实,也增加了开发一个相关应用带给人们的满足感。

为什么要有一本Android秘籍

Android操作系统(后简称Android系统)简单易学,而且Google还提供了许多库(library),使功能丰富、复杂度高的应用的实现变得简单。唯一欠缺的,也正如 Android 开发社区中的许多人提到的那样,就是清楚明白的文档。Android是开源的这一事实,意味着任何人都能深入其中,用逆向工程[1]的方法得出一些文档。许多开发者电子公告栏(bulletin board)中都有运用这一方法演绎出的完美范例。尽管如此,如能写出一本可以一致地贯穿这一操作系统所有领域的著作,仍将是有意义的。

此外,一个清晰可用的范例,胜过千言的文档。开发者们遇到问题时,通常首选一种极限编程(extreme programming)方式,也就是找到一些功能与自己要实现的目标相近的工作代码范例,通过修改或扩展它们,来实现自己的需求。这些范例同时也起到编码风格的示范作用,帮助开发者塑造其他部分的代码。

本书通过许多自成一体的技巧,来满足读者不同的需求。而Android系统的各类基本概念则被融入到这些技巧之中。

目标读者

那些编写自己的Android应用程序的用户将从本书受益良多。书中大部分内容,都建立在假定读者对Java和Eclipse有基本的了解的基础上,但这种了解也不是严格必需的。Java是一种模块化程序设计语言,这使得绝大部分(如果不是全部)的技巧示例在经过较小的修改后可以被吸纳进读者自己的Android项目中。书中的各个主题所涵盖的范围及其创作动机,使得本书可以用于Android课程的补充读物。

使用书中的技巧

一般而言,本书中的代码技巧是相互独立的,而且囊括了对于开发运行一个可工作的应用程序所必需的所有信息。第1章和第2章给出了 Android 使用的概览,读者完全可以跳过它们,自由翻阅想读的章节。

本书首先是作为一本参考书编写的,主要以范例的形式传授知识,这些范例通过对富有趣味的技巧的实现,以求给读者带来最大的收益。每节的标题指明了该节中的技巧所介绍的主要技术。同时,那些为主要技巧提供支持的附加的技术也被包含在相应章节中。

在读过这本书后,开发者应该:

能够从零开始编写Android应用程序;

能够编写适用于多个Android版本的程序代码;

能够使用Android提供的各种各应用程序接口(Application Programming Interface,API);

积累大量能够被迅速融入到应用程序中的代码片段;

领会Android中实现同一任务的不同方法,并能从每种方法中获益;

理解Android编程技术中那些独一无二的方面。

本书的结构

第1章“Android概览”并不涉及代码,而是提供对Android各个方面的介绍。这是唯一一个不包含“技巧”的章节,但它提供了有用的背景材料。

第2章“应用程序基础:Activity和Intent”,给出了4种Android组件的概述,并解释了Android项目是如何组织的。Activity 作为构建应用的主要模块,是本节重点关注的对象。

第3章“线程、服务、接收器和警报”,介绍了线程(thread)、服务(service)、接收器(receiver)等后台任务,以及借助警报(alert)来实现的这些后台任务间的通知(notification)机制。

第4章“高级线程技术”,介绍了AsyncTask及装载器(loader)的使用。

第5章“用户界面布局”,涵盖了用户界面屏幕布局(Layout)和视图(View)的有关内容。

第6章“用户界面事件”介绍了由用户初始化的事件,例如各种触屏事件和手势。

第7章“高级用户界面技术”,涉及如何创建自定义视图、运用动画、提供可访问选项,以及以更大的屏幕工作等内容。

第8章“多媒体技术”,包括多媒体操作、视频和音频的录放等内容。

第9章“硬件接口”,介绍了 Android设备上可用的硬件 API,以及如何使用它们。

第10章“网络”,探讨了 Android设备与外界的交互,包括短信息(SMS)、Web浏览和社交网络等。

第11章“数据存储方法”,涵盖了多种Android上可用的数据存储技术,包括SQLite。

第12章“基于位置的服务”,聚焦于如何利用诸如GPS等不同方法访问位置信息以及如何使用Google地图(Google Maps)API等服务。

第13章“应用内计费”,提供了一套利用Google Play服务将应用内计费机制引入应用程序的指令集合。

第14章“推送消息”,涉及如何使用GCM处理应用程序的推送消息。

第15章“原生Android开发”,探讨了原生开发用到的组件和结构。

第16章“测试和调试”,提供了可用于贯穿整个开发周期的测试和调试框架。

更多的参考资料

Android相关的在线参考资料十分丰富,下面给出一些精华站点。

Android源代码:http://source.android.com/。

Android开发者主页:http://developer.android.com/。

开放源码目录:http://osdir.com/。

栈溢出论坛(Stack Overflow Discussion Threads):http://stackoverflow.com/。

Android开发者讲坛(Talk Android Developer Forums):www.talkandroid.com/android-forums/。

[1].所谓逆向工程(reverse engineering),就是根据已有的实现结果,分析推导出其具体实现方法。文中所说的通过逆向工程方法得出文档,指的是通过分析开源代码,掌握其技术特点和实现思路,从而撰写出相关说明文档。——译者注

作者介绍

Ronan“Zero”Schwarz是OpenIntents的创始人之一。OpenIntents是一家以欧洲为基地、专门从事Android开发的开源软件公司。Ronan拥有超过15年的编程经验,且涉足诸多领域,如增强现实(augmented reality)、Web、机器人学、商业系统等,以及包括C、Java、Assembler等在内的多种编程语言。他从2007年起就开始从事Android平台开发工作,在其他方面,还协助创建了 SplashPlay 和 Droidspray,它们分别入围第一届和第二届 Android 开发者挑战赛(Android Developer Challenge)的决赛圈,并名列前茅。

Phil Dutson 是 ICON Health and Fitness 的首席用户体验(UX)和移动开发员。他曾为NordicTrak、ProForm、Freemotion、Sears、Costco、Sam’s Club 及其他一些单位开发项目和提供解决方案。多年以来,他使用、修改和编写了许多移动设备(从他拥有的第一台Palm Pilot 5000[1]到现在手头的iOS和Android设备)上的程序。Phil还著有《jQuery, JQueryUI and jQuery Mobile》、《Sams Teach Yourself jQuery Mobile in 24 Hours》和《Creating QR and Tag Codes》等书籍。

James Steele 15年前,还是麻省理工学院(MIT)的一名物理学博士后时,他就投身于硅谷一家初创公司。15年后的今天,他还在不断地推出创新成果,将消费者市场和移动市场上的若干研究项目转化为产品。他活跃在硅谷的多个技术创新团体中,并且是Sensor Platforms的工程副总裁。

Nelson To在Android Market上发布的由他自己开发的应用数量已达到两位数。他还从事Android企业化应用工作,涉及Think Computer股份有限公司的PayPhone、AOL的AIM、斯坦福大学(Stanford University)的Education App和罗技公司(Logitech)的Google TV等产品。此外,他还协助组织了硅谷的Android Meetup社区,并在美国湾区和中国讲授Android课程。

[1].Palm Pilot 5000是Palm Computing公司于1996年3月推出的Pilot系列PDA掌上电脑中的一个机型。——译者注

第1章 Android概览

自2007年底开放手持设备联盟(Open Handset Allience)宣告成立以来,Android系统已经走过了一段很长的道路[1]。嵌入式系统上的开源操作系统并不是一个新鲜事物,但Google雄心勃勃的支持,毫无疑问地将Android在几年之内就推到了这一领域的最前沿。

在许多国家诸多无线运营商五花八门的通信协议之上,都有至少一种Android手机与其相适应。而其他的嵌入式设备,诸如平板电脑、上网本、电视、机顶盒,甚至汽车当中,也都已经引入了Android系统。

本章讨论了Android的许多对开发者有意义的基本方面,提供了创建Android应用程序的基础知识,为本书后续部分的各个技巧做了铺垫。

1.1 Android的演化

Google 在看到移动设备上因特网的使用和在线搜索量的迅猛发展后,于2005年并购了Android公司[2],以致力于移动设备平台的开发工作。Apple公司在2007年推出了iPhone,其中体现出的若干理念可谓石破天惊,例如多点触控技术和开放式的应用程序市场。Android迅速地适应并吸纳了这些特性,但又提供了许多与之相异的特性,比如给予开发者更多的控制权限,以及对多任务的支持。此外,Android将一些企业级的需求考虑在内,如交易支持、远程擦除及对虚拟专用网络(Virtual Private Network,VPN)的支持,以追逐Research In Motion (RIM)公司在企业市场领域的脚步,后者曾以其出色的黑莓(BlackBerry)机型在这一领域独领风骚。

支持设备的多样性以及快速的适应能力使Android得以扩大其用户基础,但这样的成长也为开发者带来了潜在的挑战。应用程序需要同时支持多种屏幕大小、分辨率、键盘、硬件传感器、操作系统版本、无线数据传输速率以及系统配置等。上述的任何一项都可能带来无法预测的行为。然而,在应用程序测试中遍历一切可能的环境又是一项无法完成的任务。

Android 因此被打造成为一套能确保跨平台体验尽可能一致的系统。通过将硬件的不同点加以抽象,Android系统尽力将应用程序与特定于设备的改动隔绝开来,使得程序可以被灵活地按需调整。针对新的硬件平台及操作系统升级不会过时的应用程序也是一个考虑因素,需要开发者对这一系统方法有较好的认识。Android提供的通用应用程序接口(API)以及如何确保设备和操作系统间的兼容性是贯穿本书的两条主线。

此外,同所有其他嵌入式平台一样,对应用程序进行广泛的测试是必需的。在这方面,Google以多种形式为第三方开发者提供了的支持,比如Eclipse上的Android开发工具包(ADT)插件(它也可以作为独立的工具使用),其中包含实时日志功能、可以运行原生 ARM 代码的现实模拟器,以及由用户向Google Play应用程序开发者发送的现场错误报告等。

1.2 Android的两面性

Android身上有若干值得玩味的两面性。预先了解它们对于理解Android是什么或不是什么都是有益的。

Android是一个嵌入式操作系统,其核心系统服务基于Linux内核开发,但Android本身并不是一套嵌入式Linux。例如,一些标准Linux实用程序,像X-Windows和GNU C程序库都不为Android所支持。Android应用程序是用Java框架编写的,但Android并非Java,诸如Swing一类的标准Java库在Android 上就不被支持。另外一些Java库,比如Timer,也并非首选,而是可以被 Android 自己的库所替代。这些库是经过优化的,专门针对资源有限的嵌入式环境。

Android 系统是开源的,意味着开发者可以查看和使用所有系统源代码,包括射频协议栈(radio stack)。这些源代码对于需要查看活动的Android代码范例的人而言,是首选的资源之一。在文档匮乏时,它们也有助于人们搞清 Android 的某些用法。同时还意味着,开发者们可以像任何核心应用中所做的那样使用系统,并可将系统组件替换为自己的组件。然而,Android设备确实包含一些开发者无权染指的专有软件(如GPS导航)。

1.3 运行Android的设备

全世界有种类数以百计的 Android 设备和为数众多的制造商,设备包括电话、平板电脑、电视、车载音响、运动器械及其他辅助设备。软件可通过android.os.Build来获取目标设备信息,例如:

if(android.os.Build.MODEL.equals("Nexus+One")) { ... }

所有Android支持的硬件由于操作系统的一些自然属性而具有一些共同点。Android系统由下列镜像文件(image)组成。

Bootloader(引导加载程序):在启动时对引导镜像(boot image)的加载过程进行初始化。

Boot image(引导镜像):内核(kernel)和内存虚拟盘(RAMDisk)。

System image(系统镜像):Android系统平台及应用程序。

Data image(数据镜像):在断电期间依然保存着的用户数据。

Recovery image(恢复镜像):重建或升级系统所用的文件。

Radio image(射频镜像):射频协议栈文件。

这些镜像被保存在非易失性闪存中,所以在设备断电期间仍然被保存。这块闪存被用作只读存储器(read-only memory,ROM),但它在必要时可以被改写(例如,对Android系统进行在线更新时)。

启动时,微处理器执行引导加载程序将内核和内存虚拟盘载入内存,以实现快速存取。接下来,微处理器执行一些指令,将系统镜像和数据镜像部分按需装载为内存页面。射频镜像常驻在基带处理器(baseband processor)中,后者则与射频硬件连接。

表1-1 比较了一些早期的和新近的智能手机机型。通过比较可以看出,这些设备负责运算处理的硬件架构是相似的:一个微处理器单元(MPU)、同步动态随机存取存储器(SDRAM,简称RAM),以及闪存(简称ROM)。屏幕大小用像素(pixel)表示,而每英寸点数(dpi)这一指标则取决于屏幕的物理尺寸。例如,HTC Magic机型屏幕对角线长为3.2英寸,分辨率则为320×480像素,相当于每英寸180像素,在Android的分类中属于中等像素密度的设备(平均水平为160 dpi)。所有智能手机还都提供CMOS图像传感器摄像头、蓝牙(BT)、Wi-Fi(802.11)等,不过规格各异。

续表

*数据来源于http://en.wikipedia.org/wiki/Comparison_of_Android_devices及http://pdadb.net/。

新机型除容量和性能有所提升外,还有区别于旧机型的若干新特性。一些设备支持4G,另一些则有调频收音机(FM)或额外的蜂窝式无线电台(cellular radio)、视频输出(通过HDMI或 micro-USB 接口),以及前置摄像头等。了解这些差别可以帮助开发者创建出色的应用。在内置硬件之外,许多Android设备还带有Micro Secure Digital(microSD)卡槽。

microSD卡可提供附加的存储空间,用于存储多媒体或额外的应用数据。不过在Android 2.2或更早的版本中,应用本身只能被存储在内部ROM里。

1.3.1 HTC系列机型

HTC是一家成立于1997年的台湾公司。HTC Dream(也被称为G1,其中的G代表Google)是运行Android系统的首款商用硬件,它发布于2008年10月。从那以后,HTC已经推出了超过20种运行Android系统的手机,包括Google的Nexus One、EVO 3D和One X+

Nexus One 是最先使用 1 GHz 微处理器的 Android 设备之一,这款微处理器为高通(Qualcom)公司的Snapdragon平台产品。Snapdragon内含高通自己生产的核心,而非ARM核心;还包含解码720p高清视频的电路。随后的大多数智能手机也都采用了1 GHz微处理器。Nexus One 机型的其他特征包括:使用双麦克风以在通话时降低背景噪音,以及能根据不同的系统通知(notification)呈现不同颜色的背光轨迹球等。

HTC EVO 4G于2010年6月发布,作为第一款支持WiMAX(802.16e-2005)的商用机型,在当时引起了不小的轰动。HTC还在2011年8月发布了EVO 3D机型,它与EVO 4G大体相似,但别具一格之处在于可以脱离3D眼镜来呈现3D效果。此外它还拥有两个后置摄像头,可以录制720p的3D视频。

1.3.2 摩托罗拉系列机型

摩托罗拉(Motorola)推出过将近十种不同标牌的Android机型。摩托罗拉Droid X拥有与HTC Droid Incredible机型近似的性能,包括高清视频摄制等。2011年Google收购了摩托罗拉移动部门,力图在市场层面对 Android 加以提升,推动创新进程,并利用摩托罗拉移动的专利布局保护Android的“生态系统”。

摩托罗拉开发的Droid RAZR MAXX和RAZR MAXX HD两款手机具有超乎寻常的电池寿命,还兼具相对苗条的外形。

1.3.3 三星系列机型

三星(Samsung)是移动市场的一支强势力量,且如今已是Android设备的第一大制造商。2012年的第4季度销售的全部Android设备中,三星所占份额高达42%。如今市场上最流行的三星手机要属Galaxy Note 2和Galaxy S3,此二者均支持蓝牙4.0、近场通信(NFC)及三星独有的S Beam和AllShare等特性。

三星Galaxy Nexus是第一款基于Android 4.2的手机,并且是最先内嵌NFC模块的机型之一。三星还是最早推出试图弥合手机和平板电脑之间鸿沟的智能机型的厂商。Galaxy Note 和Galaxy Note 2的屏幕要大于5英寸,因此有人将这类手机称做“phablet[3]”。

1.3.4 平板电脑

在苹果公司推出iPad之后,人们期望Android制造商也能推出自己的平板电脑。平板电脑可被粗略地定义为拥有4.8英寸或更大尺寸的屏幕以及Wi-Fi连接的设备。由于许多平板电脑都支持3G无线服务,它们感觉更像是大屏幕的智能手机。

2009年年底,爱可视(Archos)成为最早将Android平板电脑投向市场的厂商之一。最初的型号拥有对角线长4.8英寸的屏幕,并被称为Archos 5。此后,Archos系列又涌现出了屏幕尺寸在7英寸~10英寸间的若干新机型。有的机型配备了真正的硬盘驱动器,有些则采用闪存来存储信息。三星也推出了屏幕尺寸在7英寸~10.1英寸间的多款平板电脑。

亚马逊(Amazon)则推出了Kindle Fire系列,包括4种不同款式。这些平板电脑屏幕尺寸从7英寸~8.9英寸不等,处理器有单核也有双核。它们均运行在一种Android的修改版系统之上,该系统可与Amazon Appstore及Amazon MP3、Amazon Video等连接。

Google还与华硕(Asus)合作发布了Nexus 7,这是一款运行在Android 4.2.1系统上7英寸平板电脑。没过多久,Google又与三星合作制造了Nexus 10。Nexus 10是第一款分辨率高达2560×1600的平板电脑,从而可与支持retina技术的MacBook Pro电脑以及较新的全尺寸iPad相媲美。表1-2给出了几种不同型号的Android平板电脑的对比。

1.3.5 其他设备

既然Android是一种通用型嵌入式平台,那么除智能手机和平板电脑外,它也能应用于其他地方。第一辆基于Android的汽车荣威(Roewe)350由上海汽车工业集团生产。Android主要发挥GPS导航功能,同时也支持上网浏览。

萨博(Saab)公司也推出运行在Android平台上的、名为IQon的信息及娱乐系统。该系统能向驾驶员提供关于引擎工作负荷、时速、转矩等机械数据的实时反馈。它通过一个触屏式并支持3G或4G数据连接的8英寸嵌入式控制台来显示这些信息。而某些信息的获取需要通过给汽车的引擎控制单元(ECU)安装一个售后零件(aftermarket part)来实现。这种将Android直接植入ECU的创意是有趣而激动人心的。

Android还被移植进了一些新颖刺激的平台,比如手表和OUYA主机。Pebble手表是Kickstarter上的一个项目,力图制造可以与Android和iOS设备通信的手表。它可以通过使用某个Android设备的软件开发工具包(SDK)对其进行访问,并通过蓝牙通信显示呼叫者ID、当前时间、接收到的短信息、邮件提醒等。OUYA主机是将Android系统运用到极致的一个出色案例,它是一款专用于Android游戏的主机(与PlayStation及Xbox系列主机类似)。尽管这一新生事物在本书写作时还未向公众发布,但OUYA已经承诺每年将推出廉价而又前沿的硬件装备。

1.4 Android设备间的硬件差异

每一款Android设备上可用的硬件各不相同,这在表1-1中已有所反映。总体而言,多数差异对开发者而言是透明的,这里不深入讨论。然而,了解其中某些硬件差异有助于我们编写设备无关的代码。在此我们将探讨屏幕、用户输入法和传感器这几方面。

1.4.1 屏幕

液晶显示(LCD)和发光二极管(LED)是目前显示器使用的两种技术。在 Android 手机上,则分别体现为薄膜晶体管(TFT)液晶屏和主动矩阵有机发光二极管(AMOLED)屏。

TFT屏的一个优势是较长的寿命;而AMOLED的好处则在于无需背光,从而有更深的黑色和更低的能耗。

总的说来,Android 设备可以依照屏幕尺寸被分为小、中、大、超大,或按像素密度分为低、中、高、极高几类。注意,实际的像素密度可能多种多样,但总可以被归为低、中、高、极高中的某一种。表1-3给出了典型的屏幕尺寸、分辨率及一些与屏幕尺寸有关的名称。

1.4.2 用户输入法

触摸屏使得用户可以与视觉显示进行互动。目前有三种类型的触屏技术。

电阻屏:在玻璃屏幕的上层覆盖两层电阻材料层。当手指、手写笔或其他物体施压时,两层电阻材料发生接触,而触点可以被确定。电阻屏性价比高,但透光率只能达到75%,并且最近才实现多点触摸。

电容屏:玻璃屏上覆盖有一层带电材料。当手指或其他导体与该层接触,一些电荷被释放,引起电容变化,从而可检测出触点所在。电容屏透光率可达90%,但精确度不如电阻屏。

表面声波:这是一种靠发送和接收超声波来定位的先进技术。当手指或其他物体接触屏幕时,引起的声波被吸收和测量,从而确定触点。这是一种最为经久耐用的解决方案,但更适合诸如银行自动柜员机一类的大尺寸的屏幕。

目前绝大多数的Android设备要么采用电阻屏,要么采用电容屏技术,并且均支持多点触摸。此外,Android设备还可能采用下列某种替代性的屏幕访问方法。

十字键(D-Pad):一种上下左右型的控制杆。

轨迹球(Trackball):一种使用滚动球定位的指针设备,与鼠标类似。

触摸板(Trackpad):用一块特殊的方形表面来定位的指针设备。

1.4.3 传感器

在某种程度上,智能手机正在成为一个传感器的集合体,带给用户丰富的体验。除每部手机都必备的麦克风外,首先被引入手机的附加传感器要属摄像头。不同的手机摄像头在性能上大相径庭,这已成为人们选择手机时的一个重要考虑因素。类似多样性也体现在其他的附加传感器上。

多数智能手机至少包含以下三种基本的传感器:用于测量重力的三轴加速度计(accelerometer)、用于测量周围磁场的三轴磁力计(magnetometer),以及用于测量周边温度的温度传感器。例如,HTC Dream(G1)包含下列传感器(可以通过调用 getSensorList()函数列出它们,这将在第9章中进一步讲述)。

AK8976A三轴加速度计。

AK8976A三轴磁场传感器。

AK8976A方向传感器。

AK8976A温度传感器。

AK8976A是出自旭化成微系统公司(Asahi Kasei Microsystems,AKM)的传感器包,整合了压阻式加速度计、霍尔效应磁力计及温度传感器。这些传感器都提供8位精度的数据。方向传感器则是一个虚拟传感器,实际是组合使用加速度计和磁力计来测定方向。

作为对比,我们看看摩托罗拉Droid手机采用的传感器。

LIS331DLH三轴加速度计。

AK8973三轴磁场传感器。

AK8973温度传感器。

SFH7743近距离传感器。

方向传感器。

LM3530光传感器。

LIS331DLH是来自意法半导体公司(ST Microelectronics)的12位电容式加速度计,它提供更为精确的数据,并且采样频率可达1 kHz。AK8973是一个AKM传感器包,包含8位霍尔效应磁力计及温度传感器。

此外,Droid 还包含另两个传感器。SFH7743 是光电半导体的短程近距离传感器,可在约40 mm距离范围内有物体(比如耳朵)存在时关闭屏幕。LM3530是美国国家半导体公司(National Semiconductor)生产的带有可编程光传感器的LED驱动器,可以检测周围光线并据此将屏幕背光和LED闪光灯调节到适当亮度。

关于Android设备上可用的传感器我们再举一例,即HTC EVO 4G,它含有下列传感器。

BMA150三轴加速度计。

AK8973三轴磁场传感器。

AK8973方向传感器。

CM3602近距离传感器。

CM3602光传感器。

BMA150是博世传感器公司(Bosch Sensortec)生产的10位加速度计,采样率可达1.5 kHz。CM3602则是Capella微系统公司生产的短程近距离传感器和环境光传感器的二合一产品。

总体而言,理解不同的Android设备拥有不同的底层硬件是很有必要的,这些不同会导致性能和传感器精度的差异。

1.5 Android的特性

Android的具体特性以及如何利用它们乃是贯穿全书的一大主题。从更广泛的层面讲, Android的一些关键特性是其主要卖点和与众不同之处。认识Android的这些强项并充分利用它们将会带来益处。

1.5.1 多线程应用微件

Android系统并不限制处理器某一时间只能处理单个应用。应用程序以及单个应用程序内不同线程的优先级由系统管理。这样的好处在于,在用户的前台进程占用设备时,后台任务依然可以保持运行。例如,当用户在玩游戏时,另一个后台进程可以检查股票价格并在需要时发出警告。

窗口微件(App Widget)是可以嵌入到其他应用程序(比如主屏幕)中的迷你应用程序。它们可以在其他应用程序运行期间处理事件(比如开启一个音乐流或更新外界温度信息)。

多线程的好处在于丰富了用户体验。然而,你必须小心谨慎,以防耗能的应用将电池电量榨干。多线程特性将在第3章中进一步讨论。

1.5.2 触摸、手势和多点触摸

触摸屏对于手持设备而言是一种直观的用户界面。如果善加利用,可以使用户轻松上手。当手指接触屏幕后,拖曳和投掷是与图形交互的自然而然的方式。多点触控提供了一种同时追踪多个手指触摸轨迹的办法,常用于缩放或旋转视图。

许多触摸事件对开发者是透明可用的,而无需实现其具体的行为。可以根据需要自定义手势。在应用程序间设法保持触摸事件用法的一致性是很重要的。触摸事件将在第6章中深入探讨。

1.5.3 硬键盘和软键盘

手持设备使用户发生两极分化的特性之一就是到底应该使用物理键盘(或称为硬键盘)还是软件键盘(或称为软键盘)。硬键盘提供的真实触感和明确的键位设置对于一些人而言会使得输入更迅速,但另一些人则更喜欢软键盘的简洁设计和易用性。

Android 设备种类繁多,两种键盘类型都有。这给开发者带来的麻烦就是要兼容二者。软键盘的一个劣势就是屏幕的一部分要专门用于输入,任何用户界面布局都需要将这一点纳入考虑范围并进行测试。

1.6 Android开发

本书着眼于Android开发的最主要方面:编写Android代码。然而,略谈一谈开发的其他方面,如设计和发布,也是适宜的。

1.6.1 良好的应用设计

一个出色的应用程序应具备三个要素:好想法、好代码、好设计。通常,最后一点最不为人们所重视,因为多数开发者独立工作,且自身并不具备图形设计人员的素质。Google应该已经意识到了这一点,因此创建了一套设计指南,包括图标设计、窗口微件设计、Activity和任务设计,以及菜单设计等。这些可以在http://developer.android.com/guide/practices/ui_guidelines/中找到。Google还做了更进一步的工作,创建了一个专门演示设计原则及其在Android应用上的实现方法的网站,网址为http://developer.android.com/design/index.html。

良好的设计无论怎样强调都不过分。它可以让应用显得与众不同,提高用户的接受度,并让用户赞不绝口。市场上许多最为成功的应用都是开发者与图形设计者通力合作的产物。应把应用开发时间的相当一部分投入到考虑它的最佳设计方案上来。

1.6.2 保持向前兼容[4]

新的Android版本通常在API层面上是逐渐增强且向前兼容的。事实上,一个设备,只有当其通过Android API的兼容性测试后,才能被称为Android设备。然而,如果一个应用对底层系统进行了改动,就无法确保兼容性。为确保未来的 Android 更新被安装到设备上时,应用程序能够向前兼容,Google提出了以下规则。

不要使用内部的或不被支持的API。

不要未经询问用户就直接修改设置。未来的系统版本可能会出于安全考虑对设置操作加以限制。例如,应用曾经可以自行打开GPS或数据漫游开关,但如今已经不被允许了。

不要在布局上做得太过火。虽然不常见,但太复杂的布局(层数超过 10 或总数超过30)的确可能导致程序崩溃。

不要对硬件做不恰当的假定。不是所有的Android设备都包含全套可能支持的硬件。应确保对所需硬件进行检查,并在硬件不存在时处理相应的例外。

确保设备方向变化不会干扰应用程序运行,或者引发不可预测的行为。屏幕方向是可以锁定的,这在第2章中会提到。

注意Android并不保证向后兼容,因此最好如第2章中将要提到的那样,声明应用所支持的最低SDK版本,这样设备可以载入适当的兼容性设定。如何在旧程序上使用其他新特性这一问题,在本书中将会多次提及。

1.6.3 确保健壮性

与兼容性同等重要,在应用程序设计和测试中还应考虑健壮性(robustness)。下面给出确保健壮性的几条建议:

优先使用Android库而非Java库。Android库专为嵌入式设备而设计,并且覆盖了应用程序的多种需求。在某些情况下,如使用第三方插件或应用程序框架时,可能要用到Java库。但在二者均可用的情况下,用Android库更佳。

注意内存分配。要对变量进行初始化。尽量对对象进行重用而不是再分配,这会提升应用程序运行速度并避免对垃圾收集(garbage collection)机制的过度使用。可以用Dalvik调试监视服务器(DDMS)工具对内存分配进行跟踪,这会在第16章中详述。

使用LogCat工具,并检查由其产生的警告或错误。这同样将在第16章中探讨。

调试要彻底,尽量涵盖不同的环境和设备。

1.7 软件开发工具包(SDK)

Android SDK由平台、工具和示例代码以及开发Android应用所需的文档组成。它被构建成为Java开发工具包的附加组件,并包含一个面向Eclipse集成开发环境(IDE)的集成插件。

1.7.1 安装和升级

网上许多地方都有针对如何安装Android SDK的按部就班式的指导。例如,在Google的网站 http://developer.android.com/sdk/上就有关于安装过程的全套链接。如今 Google 已将 SDK的所有必要组成部分捆绑为一个可以方便下载的ADT Bundle[5]。该包包含已安装好ADT插件的Eclipse、Android SDK工具、Android平台工具、最新的Android平台以及用在模拟器上的最新的Android系统镜像。ADT Bundle适用于Windows、Mac和Linux系统。

这个包是一个预先配置好的 zip 压缩文件,我们需要做的全部事情就是将包解压,并启动Eclipse程序。启动后,程序将询问建立工作区(workspace)的路径,选定后,将出现一个界面,可以帮助我们建立新工程或者学习关于Android开发的更多知识。

对于那些不愿下载整个包,而宁愿只安装他们需要部分的开发者而言,下面的概要给出的一般过程阐明了最常见的安装步骤。应当在开发环境的主机上执行这些步骤。

(1)安装Java开发包(例如,要使用Android 2.1或更高版本,需安装JDK 6.0;JDK 5.0则是Android开发所要求的最低版本)。

(2)安装Eclipse经典版(例如4.2.1版)。在Windows下,只需将下载包解压到某个正确路径就可使用。

(3)安装Android SDK初始包(starter package),比如r21版。在Windows下,同样只需将下载包解压到一定的路径即可使用。

(4)启动Eclipse并选择Help→Install New Software…[6]菜单项,然后键入https://dl-ssl. google.com/android/eclipse/,以安装Android DDMS和Android开发工具(ADT)。

(5)在 Eclipse 中选择 Window→Preferences...(在 Mac 系统下,则选择 Eclipse→Preferences),然后选择Android菜单项。找到你解压Android SDK的路径并选择Apply。

(6)在Eclipse中选择Window→Android SDK and AVD Manager→Available Packages菜单项,并选择需要安装的API(例如Android SDK文档、SDK平台、Google API以及API 17)。

(7)仍然是在Android SDK and AVD Manager菜单中,创建一个Android虚拟设备运行模拟器,或者安装USB驱动在连接到电脑上的真实手机上运行应用。

(8)在Eclipse中选择Run→Run Configurations...为每个Android应用程序创建一套新的运行配置(或者与此类似地,创建一套调试配置)。Android JUnit测试也可以在此处进行配置。

至此,开发环境应该配置成便于 Android 应用的开发,并让其在模拟器或真实的 Android设备上运行了。对 SDK 进行版本更新也是件容易事,只要在 Eclipse 中选择 Help→Software Updates...,然后挑选适当版本即可。

1.7.2 软件特性和API级别

Android系统会定期推出新特性,或是进行功能强化(如改善运行效率),又或是修复bug。操作系统改进的一个主要动力是新设备上硬件性能的提升。事实上,大多数操作系统版本都是伴随着新硬件的推出而发布的(比如Eclair版[7]的发布是伴随着Droid设备的推出)。

一些旧的Android设备无法支持新版本系统的要求,因此不能随着新操作系统的发布而升级。这就带来了体验各不相同的用户群体。开发者必须检查设备的性能,或者至少把所需的硬件特性告知用户。这项工作只需通过检查一个简单的数字——API 版本号——即可完成。附录D给出了Android不同版本及其变动的列表。

如今Android的发布符合一个大致的时间规律,每6~9个月会更新一次。尽管可以进行无线(over-the-air)升级,但这种方式有一定难度,因此不太被采用。而硬件制造商也希望保持一定的稳定性,这意味着市面上的主流产品并不一定会马上进行升级。但是,每当一个新版本发布,其带来的新特性对开发者而言是值得一试的。

1.7.3 用模拟器或Android设备进行调试

模拟器会在开发用的电脑上开启一个看上去与真实Android手机类似的窗口,并执行真实的 ARM 指令。注意,模拟器的初始化启动过程比较缓慢,即便在高配置的电脑上也是如此。尽管有办法对模拟器进行配置,使其模拟真实 Android 设备的多个方面,如电话呼入、有限的数据传输率、屏幕方向的改变等,但另一些特性(诸如传感器、音频和视频)还是有别于真实设备的。近期模拟器增加了利用宿主机GPU的能力,这有助于提升模拟器上产生视觉效果和图形变换的速度。对于那些开发者无法获取的设备,用模拟器对它们进行基本功能验证不失为一种好办法。例如,可以对平板电脑的屏幕尺寸进行模拟,而无需真正购买一台。

要注意,必须事先创建一个目标虚拟设备,才能正确地运行模拟器。Eclipse 提供了一种很好的方式来管理Android虚拟设备(AVD)。表1-4给出了模拟器的各种功能所对应的快捷键的速查清单。

一般而言,首次测试最好使用Android手机来完成。这样可确保功能的完整性,还可以检验模拟器无法全完模拟的实时问题。要想把 Android 设备作为开发者平台,只需将其用自带的USB 线与电脑连接,并确保 USB 驱动被成功检测出来(Mac 系统中检测过程会自动完成;Windows系统的驱动包含在SDK里;Linux用户则要参考Google提供的相关页面)。

为启用开发者功能,要对Android设备上的某些设置进行调整。在主屏幕下分别选择MENU(菜单)→Settings(设置)→Applications(应用程序)→Unknown sources(未知来源)和MENU(菜单)→Settings(设置)→Applications(应用程序)→Development(开发)→USB debugging(USB调试)[8],从而允许通过USB线安装应用程序。有关Android调试的更多详细内容参见第16章。

1.7.4 使用Android调试桥

通常通过命令行访问Android设备比较方便,只要用USB线将设备连接到电脑即可。SDK自带的Android调试桥(Android Debug Bridge,ADB)可用于访问Android设备。例如,想要在运行Linux的计算机上登入到Android设备,可键入如下命令:

> adb shell

这样一来,就可在设备上使用很多UNIX命令。使用exit命令可退出shell。也可以把一条命令加在shell命令之后来直接执行,而无需进入和退出shell:

> adb shell mkdir /sdcard/app_bkup/

要想从设备上复制文件,可使用pull命令,如果需要的话,在复制同时还可以重命名文件:

> adb pull /system/app/VoiceSearchWithKeyboard.apk VSwithKeyboard.apk

若想把文件复制到设备,则可使用push命令:

> adb push VSwithKeyboard.apk /sdcard/app_bkup/

要从设备上移除某个应用程序,例如com.dummy.game,可以键入如下命令:

> adb uninstall com.dummy.game

这些都是最为常用的命令,另外还有其他很多命令,其中一些将在第16章介绍。

1.7.5 签名和发布

若想应用程序被Google Play接受,要对其进行签名。要做到这一点,首先需要生成一个私钥,并对其进行妥善保存。然后要在发布模式下用私钥对应用程序进行打包。对应用程序进行升级时,要使用相同的私钥进行签名,以保证升级对用户是透明的。

Eclipse可以自动完成这些工作。只需右击要签名的项目,并选择Export...→Export Android Application开始打包工作。可以使用一个密码来生成私钥,该私钥会被保存起来,以备将来的应用程序和升级使用。接下来,通过菜单继续完成APK文件的创建。APK文件是Android项目在发布模式下打包、并用私钥签了名的版本,它已可以被发布到Google Play上。

1.8 Google Play

完成了应用程序的设计、开发、测试和签名工作后,就可以在Google Play上对其进行部署。要使用Google Play,首先要创建一个Google Checkout账户。该账户不仅用来支付25美元的开发者初始注册费,也用于供开发者获取付费应用的收益。创建的应用能亮相于众目睽睽之下,往往会令开发者激动不已。在上传后的数小时之内,应用程序就可能被来自全世界的用户数百次地浏览、下载、打分和评价。这里给出发布应用时需要考虑的若干事宜,谨供参考。

1.8.1 最终用户许可协议

在全球大部分地区,任何以有形形式发布的原创内容均会自动受到《伯尔尼公约》的版权保护。但通常作者还是会为发布内容添加带有发布日期的版权声明,如©2013。为Android应用添加版权符号的方法会在第5章中介绍。

更进一步地,可以使用最终用户许可协议(EULA)为开发者发布的软件提供保护,它是开发者(或开发商)与用户之间的一个合同。大多数EULA包含“许可证授权”、“版权”、“免责条款”等部分。为应用程序,特别是付费程序,加上 EULA 是一种常规做法。第 11 章中将会介绍为Android应用添加EULA的方法。

1.8.2 提升应用的曝光度

用户一般通过三种途径查找应用程序,对这些方法善加利用有助于吸引更多的眼球。

用户可能会在Play商店中通过浏览“最新应用”(Just In app)寻找新发布的应用程序,这取决于用户的Play商店的版本。应当为应用程序选择一个一目了然的名字,并将其置于适当的分类之下,比如“游戏”或“通信”。描述用语应当简洁明了,才能吸引更多的浏览量。“游戏”分类下充斥了大量的应用,因此要分出子类别。如果应用很有趣,但没有获胜目标或评分机制,可以考虑将其归入“娱乐”类别。即便做到以上这些,由于每月都有超过10 000的应用程序被上传到Android市场,你上传的应用程序还是会在一两天内被挤出“最新应用”行列。

Google有个专门的委员会负责检查新发布的Android应用程序,从中选出一些放在Play商店“应用”部分的几个显著位置。想要应用程序被选中,应确保它支持所有可能的屏幕分辨率和dpi级别,拥有清晰易懂的描述,并带有程序运行效果的图片及视频,还要不包含可能被用户认为侵犯隐私的服务(比如读取系统日志、无故发送短消息,或者在不必要的情况下使用精确定位而不是粗略定位)。

用户查找应用的第二种方式是通过关键词检索。我们可以推测用户可能选用的关键字,将它们包含在应用的标题或描述中。不同用户可能使用不同的语言,因此包含适当的国际通用关键字会有益处。

用户在Play商店中查找应用的最后一种可能途径是“热门程序排行”(Top app),这里包含了那些获得了最高评分和最大下载量的应用。想要让你的应用跻身这一行列,需要付出时间和努力来升级应用和修正 bug。这就引出了决定应用受欢迎程度的最后一个因素:健壮性。要确保应用不包含重大的bug,耗电量较低,且可以傻瓜式地退出。没有比“该应用要把我的电池耗光!”、“我无法卸载这个应用”这样的评价更能吓跑潜在用户的了。“热门”应用又被分为“免费”、“付费”和“时尚”几种。

还有一点需要注意:开发者和用户之间几乎所有的互动都要经过Google Play。提供开发者联系方式或支持网站通常是多余的,移动市场的用户很少会用到它们。

1.8.3 让应用脱颖而出

有时开发者创建了一个应用程序,却发现Android市场上已经发布了类似的软件。应当把这种情况当成机遇而并非挫折。可以通过更良好的设计、界面及运行状况迅速赢得用户群体。一般而言,原创是好的,但不是必须的。只要注意避免使用侵犯版权的内容即可。

1.8.4 为应用收费

每当新的应用程序或应用更新被上载到Android市场时,开发者都需要选择是免费提供它还是对其收费。下面列出主要的选择。

免费提供应用,让所有访问Google Play的人都能看到和安装它。

免费提供应用,但在其中包含广告。有时,开发者会为应用拉取赞助;更多时候开发者会和第三方整合商合作。广告商通常按广告的点击量付费,按广告印象(浏览量)付费的情况则较少。图1-1显示了一条来自AdMob(如今已成为Google的一部分)的横幅广告。这类广告需要应用程序拥有访问 Internet 及获取设备定位的权限。应考虑使用粗略定位而不是精确定位,以免损失某些潜在用户。

为应用收费。Google负责处理包括收费在内的交易全过程,但会在过程中抽取30%的费用,因此需要开发者在Google Wallet上建立一个商用账户。没有开通Google Checkout服务的国家无法查看或安装这些付费应用。一些开发者因此转而到第三方应用商店发布应用。

发布功能受限的免费版,但对完整版收费。这给了用户试用应用的机会,如果他们感觉良好,就会比较愿意购买完整版。对于某些应用而言,这种模式是上上之选(比如让一个游戏包含10个免费关卡),但并不是所有应用都适合这种方式。

在应用中售卖虚拟商品,或者采用应用内购买方式。售卖虚拟商品这一方式比较常见于“花钱买胜利”型的应用。这类应用可以免费获取,但需要用户花钱来提升装备质量、升级能力,甚至跳过游戏的某一部分。对于Facebook上的应用,这是一种重要经营方式,而在移动市场上也正在变得流行起来。

免费应用程序一般能获得较大的浏览量,即使那些最为莫名其妙和稀奇古怪的应用也能在发布到Play商店后的一个月内,被上千人浏览和下载。有的开发者明确指出自己的应用“绝对毫无用处”,但居然还能获得过万的下载量和四星级的评价。一些有用的免费应用下载量能达到5万,而特别有用的免费软件下载量可达10万以上。对于大多数开发者,这样的受欢迎程度相当令人满意。

移动广告目前尚处于幼年时期,通常不能吸引足够的用户点击从而让应用获利不菲。目前,最佳的获利方式是在Play商店上收费或者在应用内收费。只要应用对一定人群有用,描述清晰,并拥有一定数量的好评,就会有用户购买它。如果应用获得成功,或许还可以对其提价。

1.8.5 管理评价和更新

如果应用获得成功,多数独立开发者会继续发布更新版本,且会在修改时参考用户的反馈意见。这样做会吸引更多人下载应用,因为用户喜欢积极给予响应的开发者。随着下载量的增加,应用的口碑也随之提升。

一般来说,约200个用户中会有1人给应用打分,而这些人中又只有一小部分会发表评论。如果有人愿意花时间撰写评论,那么他的意见通常值得注意,特别是当评论有建设性时,例如“在HTC Hero上无法运行”或“不错的应用,如果能够……就更好了”。

响应用户评论而进行的升级会受到用户欣赏,从而吸引更多用户。在任何情况下,都应明确给出发布升级的理由。有的用户一天会收到两位数以上的可用更新提醒,如果看不到有说服力的理由,他们可能不会选择升级。

1.8.6 Google Play以外的其他选择

有一些独立的Android应用商店存在。它们也许访问起来不如Google Play方便,但可能为开发者提供其他好处,例如更好的应用曝光度,更多的应用收费点,以及不抽取费用等。另外,一些 Android 设备制造商为他们的设备定制了自己的应用商店。比如,想让中国和拉丁美洲市场上的摩托罗拉 Android 手机看到你的应用,可以通过摩托罗拉应用商店(http://developer.motorola.com/shop4apps)来实现。

还有若干第三方应用商店,下面列出了其中一些。请注意,有的第三方商店会发布非法、盗版或损坏了的软件。如果你已下定决心使用第三方商店,请务必首先对其做一定的调查工作以确保它值得信任。

百度应用商店(中国):http://as.baidu.com/。

亚马逊应用商店(Amazon Apps):www.amazon.com/appstore。

Opera移动应用商店(Opera Mobile Apps Store):http://apps.opera.com/en_us/。

SlideMe:http://slideme.org/。

Getjar:www.getjar.com/。

AppBrain:www.appbrain.com/。

[1].Android系统就是在开放手持设备联盟宣告成立的同时对外发布的。该联盟成立于2007年11月5日,由Google和其他34家手机制造商、软件开发商、电信运营商和芯片制造商等共同组成。——译者注

[2].Android公司创建于2003年10月,致力于开发名为Android的移动操作系统。2005年8月17日,Google收购了成立仅22个月的Android公司,并继续支持Android系统的开发。——译者注

[3].phablet为英文单词phone(手机)和tablet(平板电脑)的结合体。——译者注

[4].向前兼容(forward compatibility),在应用程序自身层面,可以理解为使应用程序较新版本产生的输出能被较早的版本所兼容(可能需要通过忽略早期版本中未实现的功能来达到此目的);而在应用程序与它所基于的操作系统的关系层面,则可理解为应用程序可以在未来发布的更新版系统上顺利运行。它与向后兼容(backward compatility)是一对截然相反的概念。后者在应用程序自身层面,意为应用程序的较新版本能够接受较早版本产生的结果;在应用程序与系统关系层面,则意味应用程序可以运行在任何操作系统的早期版本之上。具体到本节上下文,这里的向前兼容意为Android应用应能适应未来发布的Android系统更新。概念易混,特此辨析。——译者注

[5].Android Development Tools Bundle,可译为Android开发工具包。——译者注

[6].截至翻译完成时,ADT Bundle 并无官方中文版,因此对与开发环境交互时用到的各种命令和选项暂不翻译,保持英文原文。一部分感觉有必要的地方用括号注明中文含义。——译者注

[7].2009年5月以后发布的Android版本,除数字版本号外,还另有一个别名。且到目前为止,基本都用某种甜点的名字来命名。这里的Eclair即是一种法式甜点,可译为“闪电泡芙”。关于Android各个系统版本的详细情况可参见附录D。——译者注

[8].因 Android 版本差异,上述两个功能选项可能并不在作者给出的位置。比如,在译者的 4.0.4 版系统中,前者位于Settings(设置)→Security(安全)→Unknown sources(未知来源),后者则位于Settings→Developer options(开发人员选项)→USB debugging(USB调试)。——译者注

第2章 应用程序基础:Activity和Intent

每个Android应用程序都由单个的Android项目代表。本章给出了Android项目的目录结构,包括对于应用程序基本构建模块的简介,这为学习本书后面的技巧提供了有用的背景知识。本章的后半部分则转而介绍Activity以及负责调用Activity的Intent。

2.1 Android应用程序概览

Android 应用程序包含的功能五花八门,比如编辑文本、播放音乐、启动闹钟或是打开通讯录等。这些功能可以被分类对应到4类Android组件之中,如表2-1所示,每一类都对应一个Java基本类。

每个应用程序都由一个或多个这样的组件组成。当要用到某个组件时,Android 操作系统就会将其初始化。其他应用程序在指定的权限内也可以使用它们。

随着在操作系统中展现多种功能(有些功能甚至与预期的应用程序无关,如呼入电话),每个组件经历了生命周期的创建(create)、聚焦(focus)、失去焦点(defocus)和销毁(destroy)过程。对于优雅的操作(比如保存变量或者恢复用户界面元素)可以重写其默认行为,使交互对用户更加友好。

除了ContenProvider组件,每个组件都需要一个叫做Intent的异步消息来激活。Intent可包含一组(Bundle)描述该组件的辅助数据。这也提供了一种在组件之间传递消息的方法。

本章最后将使用最常见的组件Activity演示前面提到的概念。由于Activity总是和具体的用户交互相关,所以每个 Activity 在创建时会自动创建一个新窗口。当然还会提到一些关于UI的概要介绍。至于Service和BroadcastReceiver这两个组件我们将会在第3章中讲解,而ContentProvider则会在第11章中阐述。

技巧1:创建项目和Activity

创建Android项目或其组件最直截了当的方式,当属使用Eclipse集成开发环境(IDE)。这样做可以确保所有辅助文件被正确安装。用Eclipse新建Android项目的步骤具体如下。

(1)在Eclipse下,选择File→New→Android Application Project,这样会出现新建Android项目的界面。

(2)填写项目名称,比如SimpleActivityExample。

(3)填写应用程序的名称,比如Example of Basic Activity。

(4)填写包名称,例如com.cookbook.simpleactivity。

(5)选择 SDK 的最低要求,这将成为可以运行应用的最低 Android 版本。建议最低选择API Level 8或Android 2.2。

(6)从给出的选项中选择构建程序的目标 SDK 版本,此处应选择测试应用时会用到的最高Android版本。

(7)选择用于编译应用的 SDK 版本,这里应选择可用的最新版本,或者要用到的库所需的最低版本。

(8)选择应用程序的基本主题(theme)。以后还可以按需修改主题,不过在此早些指定一个没有坏处。

(9)接下来,设置另一些项目默认参数。选中Create custom launcher icon一项,以替换当前的默认图标。要在后面的步骤中创建主Activity,请确保你勾选了Create Activity项。

(10)在Configure Launcher Icon界面,可以在文本、一小组剪贴画或磁盘中的图片里面选定应用的图标。系统会基于选定的图像,分别创建符合4种标准分辨率的图标。

(11)要在同一步骤中创建主 Activity,应确保勾选了Create Activity项,并选择BlankActivity选项。Fragment的使用将在后面的技巧中予以介绍。

(12)填写Activity和布局的名字,或者维持默认值。要使用默认导航模式中的某一个,需要SDK版本为14或更高才行,因为这有赖于ActionBar的支持。

(13)点击Finish按钮完成这一实例工程的创建。

所有的 Activity 均继承了抽象类 Activity 或它的一个子类。每个 Activity 的入口为onCreate()方法。通常为初始化 Activity,都会对该方法进行重写,以完成设置 UI、创建按钮监听器(listener)、初始化参数,或是启动线程一类的工作。

如果主Activity并未随项目一起创建,或者需要另行创建一个Activity,那么可以采取以下步骤。

(1)创建一个继承Activity类的新类。在Eclipse下可以这样做:在项目上右击,选择New→Class,然后指定android.app.Activity作为父类。

(2)重写 onCreate()函数。在 Eclipse 下可这样做:在类文件上右击,选择 Source→Override/ImplementMethods...,然后选中 onCreate()方法。同大多数重写函数一样,重写onCreate()方法需要调用父类的该方法,如若不然,可能在运行时抛出异常。在此,应当首先调用super.onCreate()方法,以确保Activity的正确初始化,如代码清单2-1所示。

代码清单2-1 src/com/cookbook/simple_activity/SimpleActivity.java

package com.cookbook.simple_activity;

import android.app.Activity;

import android.os.Bundle;

public class SimpleActivity extends Activity {

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

}

}

(3)如果要用到UI,需在res/layout/目录下的一个XML文件中指定布局。此处该文件称为main.xml,如代码清单2-2所示。

代码清单2-2 res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent"

>

<TextView

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="@string/hello"

/>

</LinearLayout>

(4)使用setContentView()函数设置Activity的布局,并将XML布局文件对应的资源ID作为参数传递给它。此处该参数为R.layout.main,已显示在代码清单2-1中。

(5)在 AndroidManifest.xml 文件中声明 Activity 的属性,详细内容会在稍后的代码清单2-5中涵盖。

注意,字符串类型资源均在res/values文件夹下的strings.xml中定义,如代码清单2-3所示。该文件为需要被修改或重用的字符串提供了一个集结地。

代码清单2-3 res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>

<resources>

<string name="hello">Hello World, SimpleActivity!</string>

<string name="app_name">SimpleActivity</string>

</resources>

下面要对项目的结构及其他一些自动生成的内容进行详细探究。

2.1.1 项目目录结构及自动生成的内容

项目结构是用户生成的文件与自动生成文件的混合体。图 2-1 给出了 Eclipse Package Explorer(Eclipse包浏览器)中显示的项目结构的一个示例。

用户生成的文件包括以下这些。

src/目录包含了用户为应用编写或导入的Java包。每个包可以包含多个.java文件,分别代表不同的类。

res/layout/下包含了为每一屏界面指定布局的XML文件。

res/values/下含有被其他文件所引用的XML文件。

res/values-v11/用于存放Honeycomb[1]及更高版本设备使用的XML文件。

res/values-v14/用于存放Ice Cream Sandwich[2]及以上版本设备使用的XML文件。

res/drawable-xhdpi/、res/drawable-hdpi/、res/drawable-mdpi/和res/drawable-ldpi/分别包含应用在极高、高、中、低每英寸点数分辨率下所用的图片。

assets/下存有应用要用到的非媒体文件。

AndroidManifest.xml用于向Android系统指定该项目的特性。

每个名为 res/values-XX 的文件夹下均生成有 styles.xml。这是因为 Android 基本主题从Honeycomb版本起变为了Holo,这导致应用的主题拥有不同的父主题。

自动生成的文件则有以下这些。

gen/之下包含了自动产生的代码,包括生成的类文件R.java。

project.properties里含有项目设定。尽管是自动生成的,它也应当被纳入版本控制之下。

应用程序的资源包括描述布局的XML文件、定义包括如字符串和UI元素标签等的各种值的 XML 文件,以及其他支持文件,比如图片和声音。编译时,对资源的引用都会收集到一个名为R.java的自动生成的包装类(wrapper class)中。Android Asset打包工具会自动生成该文件。代码清单2-4给出了技巧1的项目产生的R.java的内容。

代码清单2-4 gen/com/cookbook/simple_activity/R.java

/* AUTO-GENERATED FILE. DO NOT MODIFY.

*

* This class was automatically generated by the

* aapt tool from the resource data it found. It

* should not be modified by hand.

*/

package com.cookbook.simple_activity;

public final class R {

public static final class attr {

}

public static final class drawable {

public static final int icon=0x7f020000;

}

public static final class layout {

public static final int main=0x7f030000;

}

public static final class string {

public static final int app _ name=0x7f040001;

public static final int hello=0x7f040000;

}

}

在这里,每个资源都被映射到一个独一无二的整型(integer)值。通过这种方式,R.java类提供了一种在Java代码中引用外部资源的办法。例如,要在Java中引用main.xml,就使用整型值R.layout.main。而要在XML文件里引用该资源,则应使用“@layout/main”字符串。

表2-2展示了在Java或XML文件中引用资源的方法。注意,要定义一个ID名为home_button 的新按钮,需要在其标识字符串前面加上一个加号,就像这样:“@+id/home_button”。关于资源更完整的细节请见第5章,此处介绍的内容对于学习本章的技巧已经足够。

2.1.2 Android包和manifest文件

Android项目,有时也被称为Android包,乃是Java包的集合。不同的Android包可以拥有相同的Java包名,但Android设备上安装的每个Android包的名字都应当是独一无二的。

为让操作系统访问这些包,每个应用程序必须在一个名为 AndroidManifest.xml 文件中声明它所有可用的组件。此外,该文件还包含运行应用所需的权限和行为。代码清单 2-5 给出了技巧1对应的AndroidManifest.xml的内容。

代码清单2-5 AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.cookbook.simple_activity"

android:versionCode="1"

android:versionName="1.0">

<application android:icon="@drawable/icon"

android:label="@string/app_name">

<activity android:name=".SimpleActivity"

android:label="@string/app_name">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

</application>

<uses-sdk android:minSdkVersion="8" />

</manifest>

第一行对于Android中的所有XML文件都是标准的和必须的,用于指定编码。manifest元素定义了Android包的名字和版本。versionCode是一个整型值,可在程序中对其求值,以确定版本的高低关系。versionName以人们可读懂的格式表示了声明的主版本号和子版本号。

application元素定义了用户会在Android设备菜单中看到的应用程序的图标和标签。标签是一个字符串,应当足够简短,以便在用户设备中正确显示在图标下方。通常,该名字最长为两个单词,每个单词由10个连续的英文字符构成。

activity元素定义了应用启动时调用的主Activity,以及Activity活动时标题栏(title bar)中显示的名称。在此需要给Java包指定名字,本例中为com.cookbook.simple_activity. SimpleActivity。由于 Java 包名通常与 Android 包名相同,常常使用简化形式.Simple-Activity。然而,最好记得Android包和Java包还是有区别的。

intent-filter元素向Android 系统说明组件的功能,为此,它可以包含多个action、category或data元素。该元素在本书的许多技巧中都会用到。

uses-sdk元素定义了运行应用所需的API级别。一般来说,API级别会像下面那样指定:

<uses-sdk android:minSdkVersion="integer"

android:targetSdkVersion="integer"

android:maxSdkVersion="integer" />

由于 Android 系统按向前兼容原则构建,强烈不推荐使用 maxSdkVersion 参数(在Android 2.0.1及以后版本中它已经被剔除掉了)。然而Google Play仍然在使用它作为一个过滤参数,在运行大于这一参数的SDK版本的设备上,该应用程序就会被显示为不可下载。

targetSdkVersion并非必须指定,但指定它可以让拥有相同SDK版本的设备关闭兼容性设置,这可能会提升操作速度。应该始终指定minSdkVersion的值,以确保一个应用运行在不支持其所需特性的平台上时不会崩溃。在指定该值时应总是选择可能的最低API级别。

AndroidManifest.xml 文件还可以包含运行该应用所需的权限设定。后面的章节会给出关于所提供选项的更完整介绍,以上所讲对于本章的技巧来说已经够用了。

技巧2:重命名应用程序的某些部分

有时候Android项目的某部分需要被重命名。也许有某个文件被(比如从本书中)手动复制进了项目。也许应用程序在开发期间改了名,也需要在文件系统树上反映出来。这项工作由自动化工具协助我们完成,同时确保那些交叉引用也被自动更新。例如,在Eclipse IDE里,有多种办法实现对应用程序某一部分的重命名。

要重命名Android项目,步骤如下。

(1)在项目上右击,选择Refactor→Move将其移动到文件系统中的一个新目录中。

(2)在项目上右击,选择Refactor→Rename重命名项目。

要重命名Android包,步骤如下。

(1)在包上右击,选择Refactor→Rename重命名包。

(2)编辑AndroidManifest.xml文件,确保新的包名在其中得到反映。

要重命名 Android 类(比如像 Activity、Service、BroadCastReceiver 和ContentProvider这样的主要组件),步骤如下。

(1)在.java文件上右击,选择Refactor→Rename重命名类。

(2)编辑AndroidManifest.xml文件,确保android:name之下的名字得到更新。

注意,重命名其他文件,诸如XML文件,通常需要手动更改Java代码中相应的引用。

技巧3:使用库项目

库项目(library project)允许资源和代码在其他应用程序中被复用,它们也被用作UI库以使较老的设备能支持新的特性。库项目在 SDK 工具版本 14 中被首次引入。库项目与一般的Android项目很类似,同样包含源码、资源文件夹和manifest文件。二者主要的区别在于库项目不能独立运行,且无法被编译成.apk文件。创建库项目的步骤如下。

(1)在Eclipse下,选择File→New→Android Application Project,这样会出现新Android项目的创建界面。

(2)填写项目名称,比如SimpleLibraryExample。

(3)填写应用程序的名称,比如Example of Basic Activity。

(4)填写包名称,例如com.cookbook.simplelibrary。

(5)从给出的选项中选择构建程序的目标SDK版本,这些选项基于开发机上已安装的SDK版本而提供。

(6)取消勾选Create custom launcher icon一项,因为库不需要图标。

(7)勾选Mark this project as a Library一项。

(8)要在库中包含Activity,应选中Create Activity项。此处创建的Activity会在主项目中用到,目前勾选上Create Activity并选择BlankActivity即可。

(9)填写活动名称,或者维持默认。

(10)把布局名改为lib_activity_main。由于所有资源最终都会被编译进单个R类文件中,最好给库的所有资源起一个统一的前缀,以避免命名冲突。

(11)点击Finish按钮完成库项目的创建。

要使用该库,需要一个主项目。依照技巧1的方法为工作区添加一个新项目,只需要进行一点微小的改动,即不需为该项目创建Activity,取而代之地,使用之前创建的库项目中的Activity。

要在主项目中引用库项目,操作如下。

(1)在Eclipse里右击项目并选择Properties→Android。

(2)向下滚动到库部分,并点击Add。

(3)会弹出一个对话框,显示工作区内所有可用的库项目,从中选择SimpleLibrary并按下OK按钮。

现在项目名称和引用路径已显示在Properties页中,会有一个绿箭头表明引用通过了检查。如果找不到引用路径,则会显示一个红叉。

添加了库项目后,建议对工作区做一次干净而完全的构建操作,以确保我们所做的变更能按预期工作。

在内部,库引用被保存在project.properties文件中,该文件同时为Eclipse和Ant所用。该文件内容应该像下面这样:

target=android-16

android.library.reference.1=../SimpleLibrary

代码清单2-6将库中的Activity添加到AndroidManifest.xml文件里,并将其设为默认启动的Activity。

代码清单2-6 主项目的AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.example.simpleproject"

android:versionCode="1"

android:versionName="1.0">

<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15" />

<application android:label="@string/app_name"

android:icon="@drawable/ic_launcher"

android:theme="@style/AppTheme">

<activity

android:name="com.cookbook.simplelibrary.LibMainActivity"

android:label="@string/title_activity_activity_main" >

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

</application>

</manifest>

在此注意,应用程序的包名与 Activity 的名字截然不同,由于 Activity 是从库项目中调用的,为其设定的名称必须是完全合格的包名和类名。

现在项目可以在Eclipse中运行了,库中的Activity会在前台显示。

库项目有很多用途,从为主项目设置不同主题、但运行在相同基础代码之上的white-label[3]应用,到使用像ActionBarSherlock这样的UI库为老Android设备带来新的外观和感觉。

2.2 Activity的生命周期

应用程序中的每个 Activity 都要经历自己的生命周期。Activity 于 onCreate()函数执行时被创建,该创建过程仅会执行一次。退出 Activity 时,则会执行 onDestroy()函数。在此二者之间,各种各样的事件可以使 Activity 进入各种不同状态,如图 2-2 所示。下一个技巧将对这些函数一一举例。

技巧4:使用Activity生命周期函数

本技巧提供了一种在Activity 工作时查看其生命周期的简单方法。为清楚起见,对每个被重写的函数都予以显式声明,并在其中加入一条Toast命令,这样在执行某个函数时,在屏幕上能有所反映(关于Toast微件的更多细节将在第3章中给出)。代码清单2-7给出了Activity的内容。在Android设备上运行它,并尝试各种情况。特别注意以下几点。

改变屏幕方向将会销毁并重建Activity。

按下Home键会使Activity暂停,但不会销毁它。

点击应用程序图标可能会启动一个Activity的新实例,甚至在旧的Activity并未销毁的情况下也会如此。

让屏幕休眠将会暂停Activity,而在唤醒时会恢复它(与接电话时的状况类似)。

代码清单2-7 src/com/cookbook/activity_lifecycle/ActivityLifecycle.java

package com.cookbook.activity_lifecycle;

import android.app.Activity;

import android.os.Bundle;

import android.widget.Toast;

public class ActivityLifecycle extends Activity {

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

Toast.makeText(this, "onCreate", Toast.LENGTH_SHORT).show();

}

@Override

protected void onStart() {

super.onStart();

Toast.makeText(this, "onStart", Toast.LENGTH_SHORT).show();

}

@Override

protected void onResume() {

super.onResume();

Toast.makeText(this, "onResume", Toast.LENGTH_SHORT).show();

}

@Override

protected void onRestart() {

super.onRestart();

Toast.makeText(this, "onRestart", Toast.LENGTH_SHORT).show();

}

@Override

protected void onPause() {

Toast.makeText(this, "onPause", Toast.LENGTH_SHORT).show();

super.onPause();

}

@Override

protected void onStop() {

Toast.makeText(this, "onStop", Toast.LENGTH_SHORT).show();

super.onStop();

}

@Override

protected void onDestroy() {

Toast.makeText(this, "onDestroy", Toast.LENGTH_SHORT).show();

super.onDestroy();

}

}

由此可见,不少用户操作会导致Activity暂停或杀死,甚至可能启动应用程序的多个版本。在开始新内容之前,有必要提一下另外两个可以控制这种行为的简单技巧。

技巧5:强制采用单任务模式

当跳出一个应用程序然后再次启动它时,可能在设备上产生同一 Activity 的多个实例。最终多余的实例会被杀死以释放内存,但期间可能引发莫名其妙的情况。为避免这种情况发生,开发者可以在AndroidManifest.xml文件中对每个Activity的此类行为进行控制。

要确保Activity只有一个实例在设备上运行,为拥有MAIN和LAUNCHER两个Intent过滤器的Activity元素内添加如下代码:

android:launchMode="singleInstance"

这样就使任务中的每个Activity始终只有一个实例。此外,任何子Activity都会作为一个单独的任务来启动。为进一步确保应用中的所有Activity都运行在同一个任务中,使用如下代码:

android:launchMode="singleTask"

这样就使多个Activity可以作为同一个任务来轻松地共享信息。

此外,有时我们会希望无论用户以何种方式进入Activity时,都能保持任务状态。例如,如果用户离开了应用,一段时间后又重新启动它,默认的做法通常是将任务重置为初始状态。为保证任务在用户返回时总是被还原到关闭之前的状态,可为任务的根Activity的activity元素指定如下属性:

android:alwaysRetainTaskState="true"

技巧6:强制规定屏幕方向

每个拥有加速度计的Android 设备都可以判定哪个方向是向下。当设备从纵向(portrait)模式切换到横向(landscape)模式[4]时,默认的动作是让应用程序的视图也随之旋转。然而,在技巧4 中我们看到,Activity会随着屏幕方向的改变而销毁和重启,一旦发生这种事,Activity的当前状态可能丢失,以致干扰用户体验。

处理屏幕变向的办法之一是在改变前保存状态信息,并在改变后予以恢复。更为简单且有用的一种办法是强制屏幕方向保持恒定。可以为AndroidManifest.xml文件中的每一个Activity指定screenOrientation属性。例如,要让Activity始终保持纵向模式,可以在Activity标签内添加如下属性:

android:screenOrientation="portrait"

类似地,若要保持横向模式,则使用下面的代码:

android:screenOrientation="landscape"

然而,光有这样的代码,在硬键盘滑出时仍会引发 Activity 的销毁和重启。此时,可以采用第三种办法,告诉 Android 系统:应用程序会处理屏幕变向和键盘滑出事件。可以通过在activity元素中增添如下属性来实现这一点:

android:configChanges="orientation|keyboardHidden"

该属性可以单独使用,也可与 screenOrientation 属性一起使用,从而向应用程序规定我们想要的行为。

技巧7:保存和恢复Activity信息

当 Activity 将被杀死时,会调用 onSaveInstanceState()函数。可重写该函数以保存有关信息。当Activity被重建时,会调用onRestoreInstanceState()函数,重写它可以恢复保存的信息。这样可以在应用经历生命周期变化时,让用户获得无缝的体验。注意,多数UI状态不需要我们亲自处理,因为默认状况下系统会关照它们。

onSaveInstanceState()有别于onPause(),例如,如果另一组件被启动并置于现有Activity 的前端,就会调用 onPause()函数。随后,如果当操作系统要回收资源时该 Activity仍处于暂停状态,就在结束它之前调用onSaveInstanceState()。

代码清单2-8给出一个保存和恢复实例状态的例子,该状态包含一个字符串和一个float型数组。

代码清单2-8 onSaveInstanceState()和onRestoreInstanceState()的示例

float[] localFloatArray = {3.14f, 2.718f, 0.577f};

String localUserName = "Euler";

@Override

protected void onSaveInstanceState(Bundle outState) {

super.onSaveInstanceState(outState);

//Save the relevant information

outState.putString("name", localUserName);

outState.putFloatArray("array", localFloatArray);

}

@Override

public void onRestoreInstanceState(Bundle savedInstanceState) {

super.onRestoreInstanceState(savedInstanceState);

//Restore the relevant information

localUserName = savedInstanceState.getString("name");

localFloatArray = savedInstanceState.getFloatArray("array");

}

注意,onCreate()方法同样包含Bundle savedInstanceState对象。当Activity关闭后又被重新初始化时,在 onSaveInstanceState()中被保存的 bundle 也会被传递到onCreate()方法中。在任何情况下,被保存的bundle都要传递给onSaveInstanceState()函数,所以用它来恢复状态更为自然。

技巧8:使用Fragment

Fragment是新近被加入Android基本构建模块行列的新鲜事物,它们是Activity下属的小部件,用于为视图与功能分组。对Fragment的一个形象类比是把它们想成小的积木块,可以堆在一起从而对更大的积木块进行填充。对小组块的需求源于平板电脑和电视屏幕的引入。

Fragment使得视图可以被捆绑在一起,并按需要被混合并匹配到一个或两个(甚至更多) Activity中去。对Fragment的经典应用是将屏幕从带有一个列表及一个详细视图的横向模式切换到带有一个单列表及一个详细视图的纵向模式。事实上,该模式已变得如此流行,以至于现在可以通过Create New Project对话框直接创建这一模式的应用骨架。

创建的步骤与前面技巧里描述过的类似。

(1)在Eclipse下,选择File→New→Android Application Project。

(2)填写项目名称,比如SimpleFragmentExample。

(3)填写应用程序的名称,比如Example of Basic Fragments。

(4)填写包名称,例如com.cookbook.simplefragments。

(5)将SDK的最低要求选为API Level 11或Android Honeycomb。只有机器上安装了额外的支持库的情况下,Fragment才能在更低的API版本下使用。

(6)在Create Activity界面中选择MasterDetailFlow作为起始点。

(7)为用于演示的项起名,例如叫做fruits。

(8)点击Finfish按钮完成创建。

进一步探索这一范例功用的工作就留给读者完成。在此我们强调与 Fragment 有关的几点重要事项。

Fragment有它们自己的生命周期,该周期依赖于宿主Activity。由于Fragment可以在Activity生命周期的任意时间点被添加、显示、隐藏和移除,它们比其他组件要更短命一些。与Activity类似,Fragment拥有onPause()、onResume()、onDestroy()和onCreate()方法。

但需要注意的是,对于Fragment,onCreate(Bundle)方法是第二个被调用的方法,第一个调用是 onAttach(Activity),它产生信号,表明已存在与宿主 Activity 的连接。可以在onAttach中调用Activity上的方法,但此时并不能保证Activity已经将自己初始化完毕。只有当调用了onActivityCreated()方法之后,Activity才算是通过了它自己的onCreate()方法。

鉴于Fragment可以在很晚时才被实例化和添加,我们不应依赖Activity在onAttach()中的状态。用于初始化视图并开始大部分工作的乃是onCreateView(LayoutInflater, ViewGroup, Bundle)方法。如果Fragment被是重建的,那么其中的Bundle类为事先保存的实例状态。

Fragment还使用bundle来序列化参数。Fragment所需的每一种属于可打包类型的外部信息,都可以通过调用 setArguments()方法从宿主 Activity 获取。并且总能在 Fragment 中调用getArguments()方法读取它们。这使得从Activity的起始Intent来的信息能够被直接传递到Fragment中显示。

2.3 多个Activity

就算是最简单的应用程序也会拥有不止一项功能,因此我们经常要应对多个 Activity。例如,一款游戏可能含有两个Activity,其一为高分排行榜,另一为游戏画面。一个记事本可以有三个Activity:浏览笔记列表、阅读选定笔记、编辑选定的或新建的笔记。

AndroidManifest.xml文件中定义的主Activity会随应用程序启动而启动。该Activity可以开启另外的Activity,通常由触发事件引起。这第二个Activity被激活时,主Activity会暂停。当第二个Activity结束时,主Activity会被重新调回前台恢复运行。

要想激活应用中的某个特定组件,可以用显式命名该组件的Intent来实现。而应用程序的所需可以通过Intent过滤器指定,这时可采用隐式Intent。系统可随即确定最合适的某个或某组组件,不管它是属于另外的应用还是系统自带的。注意,与其他 Activity 不同,位于其他应用程序中的隐式Intent不需要在当前应用的AndroidManifest.xml文件中声明。

Android尽可能选用隐式Intent,它能为模块化功能提供强大的框架。当一个符合所需的隐式Intent过滤器要求的新组件开发完成,它就可以替代Android内部的Intent。举个例子,假如一个用来显示电话联系人的新应用被装入到Android设备,当用户选择联系人时,Android系统会找出符合浏览联系人这一Intent过滤器要求的所有可用的Activity,并询问用户想使用哪一个。

技巧9:使用按钮和文本视图

触发器事件有助于全面展示多Activity特性。为此我们引入一个按钮(button)按下动作,为给定的布局添加按钮并为其指派动作的步骤如下。

(1)为指定的布局XML文件添加一个按钮控件:

<Button android:id="@+id/trigger"

android:layout_width="100dip" android:layout_height="100dip"

android:text="Press this button" />

(2)声明一个指向布局文件中的按钮ID的按钮:

Button startButton = (Button) findViewById(R.id.trigger);

(3)为按钮点击事件指定一个监听器(listener):

//Set up button listener

startButton.setOnClickListener(new View.OnClickListener() {

//Insert onClick here

});

(4)重写监听器的onClick函数以执行要求的动作:

public void onClick(View view) {

// Do something here

}

为展示动作的效果,改变屏幕上的文字不失为一招。定义文本域并通过编程对其进行改动的步骤如下:

(1)用一个 ID 为指定的布局 XML 文件添加文本域,该文本域可以有初始值(此处可用strings.xml中定义的hello字符串初始化它)。

<TextView android:id="@+id/hello_text"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="@string/hello"

/>

(2)声明一个指向布局文件中TextView ID的TextView(文本视图):

private TextView tv = (TextView) findViewById(R.id.hello_text);

(3)如果需要变更文本,可使用setText函数:

tv.setText("new text string");

以上两项UI技术会在本章后续的一些技巧中用到。对于UI技术更详细的讲解请参见第5章。

技巧10:通过事件启动另外一个Activity

本技巧中MenuScreen是主Activity,如代码清单2-9所示,它会开启名为PlayGame的Activity。此处,触发器事件是作为按钮点击、用Button微件实现的。

当用户点击按钮,startGame()函数会运行,并启动 PlayGame Activity。当用户点击PlayGame Activity中的按钮时,它会调用finish()函数将控制权交还给调用它的Activity。下面是启动Activity的步骤。

(1)声明一个指向要启动的Activity的Intent。

(2)在该Intent上调用startActivity方法。

(3)在AndroidManifest.xml中对这一额外的Activity加以声明。

代码清单2-9 src/com/cookbook/launch_activity/MenuScreen.java

package com.cookbook.launch_activity;

import android.app.Activity;

import android.content.Intent;

import android.os.Bundle;

import android.view.View;

import android.widget.Button;

public class MenuScreen extends Activity {

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

//Set up button listener

Button startButton = (Button) findViewById(R.id.play_game);

startButton.setOnClickListener(new View.OnClickListener() {

public void onClick(View view) {

startGame();

}

});

}

private void startGame() {

Intent launchGame = new Intent(this, PlayGame.class);

startActivity(launchGame);

}

}

在匿名内部类里提供当前上下文环境

注意,通过点击按钮启动Activity时,还有一些东西需要考虑,如代码清单2-9显示的那样,Intent需要一个上下文环境。然而,在onClick函数里使用this引用并不是个稳妥的解决办法。下面给出通过匿名内部类来提供当前上下文环境的几种不同方法。

使用Context.this代替this。

使用getApplicationContext()代替this。

显式地使用类名MenuScreen.this。

调用一个在合适的上下文级别中声明的函数。在代码清单2-8的startGame()中使用的就是这个方法。

这些方法通常是可以互换的,可依照具体情况选择最好的方法。

代码清单2-10中给出的PlayGame Activity只不过是一个按钮,带有一个会调用finish()函数把控制权交还给主Activity的onClick监听器。可以按需给该Activity添加更多的功能,各个代码分支可以导致各自不同的finish()调用。

代码清单2-10 src/com/cookbook/launch_activity/PlayGame.java

package com.cookbook.launch_activity;

import android.app.Activity;

import android.os.Bundle;

import android.view.View;

import android.widget.Button;

public class PlayGame extends Activity {

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.game);

//Set up button listener

Button startButton = (Button) findViewById(R.id.end_game);

startButton.setOnClickListener(new View.OnClickListener() {

public void onClick(View view) {

finish();

}

});

}

}

按钮必须像代码清单2-11所示的那样添加到main布局中,其ID应为play_game,以与代码清单 2-9 中的设定匹配。此处,按钮的大小也以设备独立/无关像素(dip)[5]的方式声明,该方式会在第5章中进行更多讨论。

代码清单2-11 res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent"

>

<TextView

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="@string/hello"

/>

<Button android:id="@+id/play_game"

android:layout_width="100dip" android:layout_height="100dip"

android:text="@string/play_game"

/>

</LinearLayout>

PlayGame Activity引用它自己的按钮ID——end_game,它位于布局资源R.layout.game中,R.layout.game又对应名为game.xml的XML文件,如代码清单2-12所示。

代码清单2-12 res/layout/game.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent"

>

<Button android:id="@+id/end_game"

android:layout_width="100dip" android:layout_height="100dip"

android:text="@string/end_game" android:layout_gravity="center"

/>

</LinearLayout>

尽管在各种情况下文本都可以显式地写在代码中,但更好的编码习惯是为每个字符串定义相应变量。本技巧里,名为play_game和end_game的两个字符串需要在字符串资源文件中分别定义,如代码清单2-13所示。

代码清单2-13 res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>

<resources>

<string name="hello">This is the Main Menu</string>

<string name="app_name">LaunchActivity</string>

<string name="play_game">Play game?</string>

<string name="end_game">Done?</string>

</resources>

最终,在AndroidManifest.xml文件里需要为PlayGame这个新类注册一个默认动作,如代码清单2-14所示。

代码清单2-14 AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

android:versionCode="1"

android:versionName="1.0" package="com.cookbook.launch_activity">

<application android:icon="@drawable/icon"

android:label="@string/app_name">

<activity android:name=".MenuScreen"

android:label="@string/app_name">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

<activity android:name=".PlayGame"

android:label="@string/app_name">

<intent-filter>

<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />

</intent-filter>

</activity>

</application>

<uses-sdk android:minSdkVersion="3" />

</manifest>

技巧11:通过使用语音转文本功能启动一个Activity

本技巧演示了如何调用一个 Activity 以获取其返回值,还演示了如何使用 Google 的RecognizerIntent 中的语音转文本功能,并将转换结果输出到屏幕上。这里采用按钮点击作为触发事件,它会启动RecognizerIntent Activity,后者对来自麦克风的声音进行语音识别,并将其转换为文本。转换结束时,文本会被传递回调用RecognizerIntent的Activity。

返回时,首先会基于返回的数据调用 onActivityResult()函数,然后会调用onResume()函数使Activity正常继续。调用的Activity可能会出现问题而不能正确返回,因此,在解析返回的数据之前,应当始终检查resultCode确保返回值为RESULT_OK。

注意,一般来讲启动任何会返回数据的Activity都将导致同一个onActivityResult()函数被调用。因此,要使用一个请求代号来辨别是哪个Activity在返回数据。当被启动的Activity结束时,它会将控制权交还给调用它的Activity,并使用相同的请求代码调用onActivityResult()。

调用Activity获取返回值的步骤如下:

(1)用一个Intent调用startActivityForResult()函数,定义被启动的Activity及一个起识别作用的requestCode变量。

(2)重写 onActivityResult()函数,检查返回结果的状况,检查所期望的requestCode,并解析返回的数据。

下面是使用RecognizerIntent的步骤:

(1)声明一个动作为ACTION_RECOGNIZE_SPEECH的Intent。

(2)为该Intent传递附加内容,至少EXTRA_LANGUAGE_MODEL是必需的,它可以被设置成LANGUAGE_MODEL_FREE_FORM 或者LANGUAGE_MODEL_WEB_SEARCH。

(3)返回的数据包中包含可能与原始文本匹配的字符串的列表。使用 data. getStringArrayListExtra检索这一数据,它将在稍后以ArrayList的形式传送给用户。

返回的文本用一个TextView显示。主Activity在代码清单2-15中给出。

所需的支持文件还有 main.xml 和 strings.xml,其中需要定义一个按钮以及用于存放结果的TextView,这可以借助技巧10 中的代码清单2-11 和2-13来实现。AndroidManifest.xml文件中只需要声明主Activity,这与前面的技巧1相同。RecognizerIntent Activity是Android系统原生的Activity,不需要显式声明即可使用。

代码清单2-15 src/com/cookbook/launch_for_result/RecognizerIntent Example.java

package com.cookbook.launch_for_result;

import java.util.ArrayList;

import android.app.Activity;

import android.content.Intent;

import android.os.Bundle;

import android.speech.RecognizerIntent;

import android.view.View;

import android.widget.Button;

import android.widget.TextView;

public class RecognizerIntentExample extends Activity {

private static final int RECOGNIZER_EXAMPLE = 1001;

private TextView tv;

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

tv = (TextView) findViewById(R.id.text_result);

//Set up button listener

Button startButton = (Button) findViewById(R.id.trigger);

startButton.setOnClickListener(new View.OnClickListener() {

public void onClick(View view) {

// RecognizerIntent prompts for speech and returns text

Intent intent =

new Intent(RecognizerIntent. ACTION_RECOGNIZE_SPEECH);

intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,

RecognizerIntent. LANGUAGE_MODEL_FREE_FORM);

intent.putExtra(RecognizerIntent.EXTRA_PROMPT,

"Say a word or phrase\nand it will show as text");

startActivityForResult(intent, RECOGNIZER_EXAMPLE);

}

});

}

@Override

protected void onActivityResult(int requestCode,

int resultCode, Intent data) {

//Use a switch statement for more than one request code check

if (requestCode==RECOGNIZER_EXAMPLE && resultCode==RESULT_OK) {

// Returned data is a list of matches to the speech input

ArrayList<String> result =

data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);

//Display on screen

tv.setText(result.toString());

}

super.onActivityResult(requestCode, resultCode, data);

}

}

技巧12:实现选择列表

应用程序中常常需要提供给用户一个选择列表,供用户点选。这一功能利用ListActivity可以轻松地实现。ListActivity是Activity的一个子类,它会根据用户选择触发事件。

下面是创建选择列表的步骤。

(1)创建一个扩展ListActivity而不是Activity的类。

public class ActivityExample extends ListActivity {

//content here

}

(2)创建一个存储各个选项名称的字符串数组:

static final String[] ACTIVITY_CHOICES = new String[] {

"Action 1",

"Action 2",

"Action 3"

};

(3)以ArrayAdapter为参数调用setListAdapter(),为其指定选择列表及一个布局:

setListAdapter(new ArrayAdapter<String>(this,

android.R.layout.simple_list_item_1, ACTIVITY_CHOICES));

getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);

getListView().setTextFilterEnabled(true);

(4)启动OnItemClickListener以确定选中了哪个选项,并做出对应的动作:

getListView().setOnItemClickListener(new OnItemClickListener()

{

@Override

public void onItemClick(AdapterView<?> arg0, View arg1,

int arg2, long arg3) {

switch(arg2) {//Extend switch to as many as needed

case 0:

//code for action 1

break;

case 1:

//code for action 2

break;

case 2:

//code for action 3

break;

default: break;

}

}

});

这一技术在下一个技巧中也会用到。

技巧13:使用隐式Intent创建Activity

隐式Intent不需要指定要使用哪个组件。相反,它们通过过滤器指定所需的功能,而Android系统必须决定使用哪个组件是最佳选择。Intent过滤器可以是动作(action)、数据(data)或者分类(category)。

最常用的 Intent 过滤器是动作,而其中最常用的要属 ACTION_VIEW。该模式需要指定一个统一资源标识符(URI),从而将数据显示给用户。它为给定的URI执行最合理的动作。比如,在下面的例子中,case 0、case 1、case 2中的隐式Intent拥有相同的语法,却产生不同的结果。

下面是使用隐式Intent启动Activity的具体步骤。

(1)声明Intent,同时指定合适的过滤器(如ACTION_VIEW、ACTION_WEB_SEARCH等)。

(2)为运行Activity所需的该Intent附加额外的信息。

(3)将该Intent传递给startActivity()方法。

代码清单2-16 src/com/cookbook/implicit_intents/ListActivityExample.java

package com.cookbook.implicit_intents;

import android.app.ListActivity;

import android.app.SearchManager;

import android.content.Intent;

import android.net.Uri;

import android.os.Bundle;

import android.view.View;

import android.widget.AdapterView;

import android.widget.ArrayAdapter;

import android.widget.ListView;

import android.widget.AdapterView.OnItemClickListener;

public class ListActivityExample extends ListActivity {

static final String[] ACTIVITY_CHOICES = new String[] {

"Open Website Example",

"Open Contacts",

"Open Phone Dialer Example",

"Search Google Example",

"Start Voice Command"

};

final String searchTerms = "superman";

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setListAdapter(new ArrayAdapter<String>(this,

android.R.layout.simple_list_item_1,

ACTIVITY_CHOICES));

getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);

getListView().setTextFilterEnabled(true);

getListView().setOnItemClickListener(new OnItemClickListener()

{

@Override

public void onItemClick(AdapterView<?> arg0, View arg1,

int arg2, long arg3) {

switch(arg2) {

case 0: //opens web browser and navigates to given website

startActivity(new Intent(Intent.ACTION_VIEW,

Uri.parse("http://www.android.com/")));

break;

case 1: //opens contacts application to browse contacts

startActivity(new Intent(Intent.ACTION_VIEW,

Uri.parse("content://contacts/people/")));

break;

case 2: //opens phone dialer and fills in the given number

startActivity(new Intent(Intent.ACTION_VIEW,

Uri.parse("tel:12125551212")));

break;

case 3: //searches Google for the string

Intent intent= new Intent(Intent.ACTION_WEB_SEARCH);

intent.putExtra(SearchManager.QUERY, searchTerms);

startActivity(intent);

break;

case 4: //starts the voice command

startActivity(new

Intent(Intent.ACTION_VOICE_COMMAND));

break;

default: break;

}

}

});

}

}

技巧14:在Activity间传递基本数据类型

有时需要向某个启动的Activity 传递数据,有时启动的 Activity 需要把其创建的数据传回给调用它的Activity。例如,需要把游戏的最终得分返回给高分排行榜界面。以下是在Activity之间传递信息的几种不同方式。

在发起调用的Activity中声明相关变量(如public int finalScore),并在启动的Activity中为其赋值(例如:CallingActivity finalScore=score)。

给bundle附加额外数据(在本技巧中有所体现)。

使用Preference属性存储数据,以备后面检索(将在第6章中介绍)。

使用SQLite数据库储存数据,以备后面检索(将在第11章中介绍)。

Bundle是从字符串值到各种可打包(parcelable)类型的映射,可以通过向Intent添加额外数据创建它。本技巧显示了将数据从主Activity传递给启动的Activity,在其中修改后再传递回来的全过程。

变量(本例中一个为integer型,另一个为String型)在StartScreen Activity中定义。在创建Intent调用PlayGame类时,通过putExtra方法把这两个变量附加给Intent。当结果从启动的Activity中返回时,可借助getExtras方法读取变量值。以上调用过程如代码清单2-17所示。

代码清单2-17 src/com/cookbook/passing_data_activities/StartScreen.java

package com.cookbook.passing_data_activities;

import android.app.Activity;

import android.content.Intent;

import android.os.Bundle;

import android.view.View;

import android.widget.Button;

import android.widget.TextView;

public class StartScreen extends Activity {

private static final int PLAY_GAME = 1010;

private TextView tv;

private int meaningOfLife = 42;

private String userName = "Douglas Adams";

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

tv = (TextView) findViewById(R.id.startscreen_text);

//Display initial values

tv.setText(userName + ":" + meaningOfLife);

//Set up button listener

Button startButton = (Button) findViewById(R.id.play_game);

startButton.setOnClickListener(new View.OnClickListener() {

public void onClick(View view) {

startGame();

}

});

}

@Override

protected void onActivityResult(int requestCode,

int resultCode, Intent data) {

if (requestCode == PLAY_GAME && resultCode == RESULT_OK) {

meaningOfLife = data.getExtras().getInt("returnInt");

userName = data.getExtras().getString("returnStr");

//Show it has changed

tv.setText(userName + ":" + meaningOfLife);

}

super.onActivityResult(requestCode, resultCode, data);

}

private void startGame() {

Intent launchGame = new Intent(this, PlayGame.class);

//passing information to launched activity

launchGame.putExtra("meaningOfLife", meaningOfLife);

launchGame.putExtra("userName", userName);

startActivityForResult(launchGame, PLAY_GAME);

}

}

传入PlayGame Activity的变量可以用getIntExtra和getStringExtra读取。当该Activity结束并准备通过一个Intent返回时,可以用putExtra方法将数据传回给发起调用的Activity。上述调用如清单2-18所示。

代码清单2-18 src/com/cookbook/passing_data_activities/PlayGame.java

package com.cookbook.passing_data_activities;

import android.app.Activity;

import android.content.Intent;

import android.os.Bundle;

import android.view.View;

import android.widget.Button;

import android.widget.TextView;

public class PlayGame extends Activity {

private TextView tv2;

int answer;

String author;

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.game);

tv2 = (TextView) findViewById(R.id.game_text);

//reading information passed to this activity

//Get the intent that started this activity

Intent i = getIntent();

//returns -1 if not initialized by calling activity

answer = i.getIntExtra("meaningOfLife", -1);

//returns [] if not initialized by calling activity

author = i.getStringExtra("userName");

tv2.setText(author + ":" + answer);

//Change values for an example of return

answer = answer - 41;

author = author + " Jr.";

//Set up button listener

Button startButton = (Button) findViewById(R.id.end_game);

startButton.setOnClickListener(new View.OnClickListener() {

public void onClick(View view) {

//将信息返回给发起调用的activity

Intent i = getIntent();

i.putExtra("returnInt", answer);

i.putExtra("returnStr", author);

setResult(RESULT_OK, i);

finish();

}

});

}

}

[1].Honeycomb为Android 3.0~3.2版的别名,这里特指对应API Level 11的3.0版本。——译者注

[2].Ice Cream Sandwich为Android 4.0版的别名,对应API Level 14。——译者注

[3].所谓white-label是这样一类产品和服务,它们由一家公司(制造商)生产,但其他公司(市场商)可以对其进行重塑,让它们以自己的面貌面世。——译者注

[4].在显示方向的英文术语中,纵向被称为 portrait(意为肖像),而横向被称为 landscape(意为风景),这大概是肖像图多呈纵向(高大于宽),而风景图多呈横向(宽大于高)的缘故。——译者注

[5].设备独立/无关像素(device-independent pixels),简写为dip或dp,是Android为方便跨不同屏幕类型的设备的编程而推出的一种虚拟像素单位,用于定义应用的UI,以密度无关的方式表达布局尺寸或位置。在运行时,Android根据使用中的屏幕的实际密度,透明地处理任何所需dip单位的缩放。在第5章的表5.1中也有涉及。——译者注

相关图书

Android App开发入门与实战
Android App开发入门与实战
Kotlin入门与实战
Kotlin入门与实战
Android 并发开发
Android 并发开发
Android APP开发实战——从规划到上线全程详解
Android APP开发实战——从规划到上线全程详解
Android应用案例开发大全( 第4版)
Android应用案例开发大全( 第4版)
深入理解Android内核设计思想(第2版)(上下册)
深入理解Android内核设计思想(第2版)(上下册)

相关文章

相关课程