SONY 数码录音棒 ICD-PX820 简单测评

ICD-PX820
ICD-PX820

因为学习的需要要买一支录音笔,经过考虑后选择了索尼的ICD-PX820

从定位上来说,PX系列在索尼录音笔产品线中属于低端,只提供基本的录音及相关功能,不过这样也正好符合我的需求。

经过一段时间的使用,简单谈谈这支录音笔的使用感受。

首先是外观和做工方面。继承了索尼一贯高质量的工业设计,PX820的外观简洁大方,丝毫没有国内某些厂商录音笔的山寨感。最主要的两个按键,即录音和播放键使用了大尺寸设计,不易误操作。同时播放键和音量+键都有突起方便手指定位,相当人性化。至于做工,尽管外壳采用的是塑料,但精细的做工避免了玩具感。

接下来是录音方面。首先不得不提到是录音效果。PX820的麦克风灵敏度相当高,可以完整记录下周围的各种声音细节。在室内也可以录下窗台外的轻微的滴水声以及远处道路上车辆驶过的呼啸声。至于室内的各种声音细节更是可以清晰的录制。从某种意义上来说,把PX820比作是助听器也不为过。

另外PX820支持VOR功能,可以自动识别环境声音并开始录音,在环境无声时自动中断录音直到下次出现声音时新建录音。在某些情况下是很实用的功能。PX820支持环境降噪功能,开启后可以初步消除部分环境低音(比如室内空调的噪音)以增强语音清晰度。PX820的录音格式是MP3,这样就使共享和在其他设备上播放变得十分方便。

PX820机体内置了一个单声道麦克风,同时还随机附赠了一个外接式立体声麦克风。外接式麦克风附有一个衬衣夹,可以像手机的耳机线那样夹在衣服上。我认为后者会是更加经常使用的录音方式,不但可以得到更好的录音效果,还可以把录音笔本身放在口袋或是包中,方便携带。

放音方面。PX820内置了单声道扬声器,实际效果一般,没有特别的感觉。音量也没有一些山寨手机那么夸张。只能说中规中距。不过通常也是使用耳机收听回放的了,因此并没有什么影响。PX820也可以作为音频播放器,支持播放MP3格式的音频,不过实际试听了一下音质惨不忍睹,完全无法演绎出高音和低音,无法替代音乐播放器使用。不过虽说是惨不忍睹,那也是和专业的MP3相比的。用于回放录制的录音效果还是可以接受的。另外由于PX820支持A-B循环和0.5-2.0倍速播放,这些对于语言学习是一个必要的便利功能。

存储和功能方面。PX820搭载了2G的存储空间,实际可用空间约1.9G。根据录音品质的不同,录音可存储时间总长可以从22小时到500小时以上。不过我自己来说应该会始终使用最高音质录音。通过随机附送的软件Digital Voice Editor 3可以轻松传输管理和编辑录音文件,因此20多个小时的录音时间足够了。PX820本身提供了简单的覆盖重录、音频分割和删除等功能。同时提供了5个文件夹(每个文件夹最多可存放99段音频片段)供分别保存不同类别的录音内容。

PX820使用两节7号电池供电。标称使用时间为30小时以上。同时PX820也支持AC外接电源供电以满足长时间录音的需要。不过外接电源需要另购,可能真的会去买的人也就很少了。不过由于PX820使用了MiniUSB接口,因此也许手机充电器+MiniUSB线的组合就可以实现AC供电也说不定?不过因为自己用不到,也就没有做这方面的测试。

总结来说,PX820虽然功能比较单一,价格相比国产录音笔也要昂贵不少,但出色的性能和索尼的品质保证使其有着与其他录音笔相竞争的资本。要说不足的话,容量偏小和需要较为经常地更换电池算是一些美中不足。

最后稍微介绍一下更加高端的录音笔和PX820相比有哪些改进供参考:点阵式的显示屏(PX820非点阵式,因而对文字的显示效果很差,尽管这并不影响使用)、智能降噪、优质的音乐播放效果、无压缩的音频格式、内置立体声麦克风、指向性麦克风、FM收音等等。这些都是PX820所不具备的。

Android中调用资源中的字符串

代码:getResources().getString(R.string.资源名)

范例:

说明:调用app_name字符串并绘图。这样就可以使用定义在string.xml文件中的字符串了。

Android中截取当前屏幕画面

代码及范例:

说明:将屏幕画面截取并保存至Bitmap的getpage中。
范例首先声明了getpage并设置分辨率,这个范例中创建了一个800×380像素的Bitmap。之后声明一个新的Canvas。接下来在该Canvas中(范例中为canvas)进行绘图,这里仅仅填充了浅灰色。最后的两条语句则是将画面截取保存在了getpage中。

避免活动切换动画

Android中切换活动时默认会有简单的动画效果,然而有的时候,特别是比如游戏中,可能这种切换效果是希望被避免的。因此在这里提供我个人认为的一种简单可行的办法。

概括地说,就是整个程序只有一个大的类,这个类负责控制模块,比如,对触摸的识别。在这个类中,再包含多个继承于View类的子类,以供在接收到相应的屏幕或键盘指令后执行。这样就可以避免活动之间的动画切换效果。

这个方法的局限性是对于比较复杂的程序会造成一定的麻烦,同时也失去了多个活动的优点。优点是可以省略传递Intent的步骤。适用于较为简单的程序。

关于翻译的几点说明

从博客建立至今也有半个月了,因为自己也有各种各样的事情要忙,所以仅能维持缓慢的翻译进度了。在这里对翻译作几点说明。

首先是翻译的准确性问题。虽然尽可能地做了检查工作,但是仍然可能还会有一些错误。我会定期回顾检查已发布的文章,但是如果发现有问题还请告诉我。直接在文章下留言即可。

第二,是翻译的通顺性问题。一直对不少引进教材的翻译水平很不满意,真的自己要翻译英文文档时才发现这是一个很难处理好的问题。英文的语言习惯和汉语相差很大,一字一句的翻译会导致行文流畅性的丧失。我决定从现在起尽可能地使用意译。这样可能会增加错误的概率,但我会尽力避免的。之前的文章也会找时间修正。

第三,关于同时翻译的篇目数量。打算从现在起同时翻译两到三篇文章。作为一种尝试。

不过最近比较忙碌是无可避免的了,博客的Favicon也没有时间设计。等到11月中旬一些事情告一段落之后应该会有更多的空闲了吧。

P.S. 补充说明一下关于类名翻译中所遵守的规则。原则上,如果原文中是首字母小写单词,例如,activity,将被翻译为“活动”,如果是首字母大写的Activity,将被认为是类名而直接使用。类似的还有View和view(视图)等。

用户界面

Android程序通过View和ViewGroup对象来构建用户界面。有许多种类的View和ViewGroup,他们都继承于View类。

View对象是Android平台的基本用户界面表现单元。View类是其子类“widgets”(提供诸如文本区域和按钮等全功能UI部件)的基础。ViewGroup类是其子类“layouts”(提供诸如线性、列表和相对等不同类型的布局结构)的基础。

View对象是一种数据结构,塔储存了布局参数和一块特定屏幕矩形区中的内容。View对象处理其自身所属屏幕矩形区域的测量、布局、绘图、焦点改变、滚动、及按键/手势交互。作为用户界面中的一个对象,一个View也是交互事件中用户和接收者的一个交互点。

视图层级(View Hierarchy)

在Android平台上,可以通过View和ViewGroup结点的层级结构来定义Android的UI,就像下图那样。这棵层级树可以随你所需或简单或复杂,你可以使用Android预定义的widget和layout来搭建,或是使用自己创建的自定义View。

为了让视图层级树可以在屏幕上被渲染,一个活动必须调用setContentView()方法并把根节点对象传递给它。Android收到这个引用后用它来擦除、测量、绘制这棵树。树的根节点要求其子节点绘制出自身——这点对于子结点也相同,子节点会要求其子结点自绘。这些子节点会向父结点请求尺寸和位置,不过父结点对子结点的大小位置有最终决定权。Android依次处理布局元素(从层级树的顶部开始),初始化View并将其加入其父结点。正因如此,当有元素重叠时,较后绘制的会覆盖先前绘制的内容。

更多关于视图是如何层级化并被测量绘制的,请阅读“Android如何绘制视图”。

布局(Layout)

使用XML布局文件是最常用的定义布局并表达视图层级的方法。XML为布局提供了一种可读性很高的结构,和HTML相似。XML中的每一个元素都是一个View或者ViewGroup对象(或其子嗣)。View对象是树的叶结点,ViewGroup对象是树的枝结点(如上图所示)。

一个XML元素的名称与其在所显示的Java类相对应。因此一个<TextView>元素会在UI中创建一个TextView,一个<LinearLayout>元素会在视图组中创建一个线性布局。当载入布局资源时,Android系统会根据布局中的元素来初始化这些运行对象。

举例来说,一个有着一个文本视图和一个按钮的垂直视图(vertical layout)是这样的:

注意LinearLayout元素中同时包含有文本视图和按钮。你可以在这里嵌套另一个LinearLayout(或是其他类型的视图组(view group))来加长视图层级,创建一个更为复杂的布局。

关于构建用户界面布局的更多情报请阅读“声明布局”。

Tip:还可以通过Java代码绘制View和ViewGroup对象。使用addView(View)方法来动态地插入新的View和ViewGroup对象。

可以通过多种途径来设计视图的布局。通过使用更多不同类型的视图组可以构造出无数的子视图和视图组。Android提供了一些预定义的视图组(被称为布局)。包括LinearLayout,RelativeLayout,TableLayout,GridLayout等。它们都提供了一些特定的参数用以定义其子视图的位置和布局的结构。

想要获知关于用于布局的不同视图组的更多情报,请阅读“常用Layout对象”。

部件(Widget)

部件是一种View对象,是用户交互的一种接口。Android提供了一套完整可用的部件,比如按钮、勾选框、文本输入框,因此能够快速地构建UI。Android还提供一些更为复杂的部件,比如日期采集器(date picker)、闹钟、缩放控制。不过能使用的部件并不受限于Android平台所提供的这些。通过声明自有的View对象或是继承整合已有的部件,可以创建出更为定制化的部件来实现自定义的功能。

更多内容请阅读“构建自定义组件”。

关于Android所提供的部件列表,参阅android.widget包。

UI事件

在向UI添加了视图/部件之后,需要了解它们如何进行用户交互以实现功能。为了让UI收到事件通知,需要完成以下两件事中的一件:

  • 定义一个事件侦听器并将其在View中注册。通常会通过它来侦听事件。View类包含一系列的嵌套接口,以On<XXXX>Listener命名,并分别拥有一个On<XXXX>()的回调方法。比如,View.OnClickListener(处理View的“点击”),View.OnTouchListener(处理View中的触屏事件),以及View.OnKeyListener(处理View中的设备按键)。如果希望View在“点击”时(比如选中一个按钮)收到通知,就使用OnClickListener并定义其onClick()回调方法(当点击时要执行的动作),之后通过setOnClickListener()将其注册给View。
  • 为View覆写一个已有的回调方法。当使用自建的View类并希望其侦听特殊的事件时需要这样做。可以处理的事件有比如屏幕触摸(onTouchEvent()),轨迹球移动(onTrackballEvent()),或设备键盘操作(onKeyDown())。允许在自定义View中定义每一个事件的默认行为以及事件是否应被传递给其他子视图。再次说明,这些是View类的回调方法,因此只有在构建自定义组件时才能定义它们。

可以在“处理UI事件”一文中继续阅读处理用户界面交互的更多内容。

菜单(Menu)

程序菜单是程序用户界面的另一重要组成部分。菜单提供了一种可靠的接口来显示程序的功能与设置。通常按下设备的菜单键可以调出程序菜单。然而,也可以添加当用户长按某一项目时调出的上下文菜单(Context Menu)。

菜单也是通过视图层级构建的,但是不需要亲自定义其结构。只要为程序定义onCreateOptionsMenu()或onCreateContextMenu()回调方法,之后声明想要包含于菜单内的项目即可。在合适的时刻,Android会自动为菜单创建必要的View等级,并在其中绘制每一个菜单项目。

菜单也会处理其自身的事件,因此不必为菜单中的项目注册事件侦听器。当菜单中的项目被选择时,onOptionItemSelected()或是onContextItemSelected()方法会被框架调用。

如同程序布局,可以在XML文件中声明菜单项目。

阅读“创建菜单”以了解更多。

高级主题

在掌握了创建用户界面的基本内容之后,可以试着用一些高级特性来创建更为复杂的程序接口。

适配器(Adapter)

有时需要把一些无法硬解码的信息装入一个视图组,这时要将视图与外部数据源绑定。为此,需要将AdapterView作为视图组,并将其子视图通过适配器的数据初始化后装入。

AdapterView对象是ViewGroup的一种实现,它说明其子视图是基于Adapter对象的。Adapter充当数据源(比如一列外部字符串)与AdapterView之间的信使角色来显示数据。Adapter类对于专门的任务有这多种不同的实现,比如从Cursor读取数据库数据的CursorAdapter,或是任意读取数组的ArrayAdapter。

欲了解更多关于使用Adapter装入视图的信息,参阅“使用AdapterView绑定数据”。

风格(Style)和主题(Theme)

如果不满意标准部件的外观,可以自己建立风格和主题来修改它们。

  • 风格是一组若干个格式属性,可以将其作为一个单位使用于布局的单个元素中。比如,可以定义一个具有特定文本尺寸和颜色的风格,然后将其应用到特定的View元素中。
  • 主题是一组若干个格式属性,可以将其作为一个单位使用于一个程序的所有活动,或是单个活动中。比如,可以定义一个具有特定窗口框架颜色、面板背景颜色、文本尺寸和菜单颜色的主题。该主题可以被应用于特定活动或是整个程序。

风格和主题是资源。Android提供了一些默认的风格和主题供使用。也可以声明自定义的风格与主题资源。

在“应用风格与主题”一文中可以了解更多关于使用风格和主题的信息。

本页部分内容根据Android Open Source Project创作并共享的内容修改,并在知识共享 署名2.5许可协议中所述条款的限制下使用。

Android开发入门

Android无疑是当前最为热门的移动设备操作系统之一,越来越多的开发者开始为Android开发应用。进行Android开发的目的大致可以分为商业盈利目的和业余爱好。对于后者,不少开发者在刚接触Android开发时总会有各种各样的问题,我想在这篇文章里谈谈如何入门Android。

进行Android开发所必需的素质:

做任何一件事,最重要的恐怕就是兴趣了。甚至可以说,只要有兴趣,做很多事都会顺利很多。因为有兴趣的话,就能够坚持下去,就会进行自主学习。其实这个主题不需要多说,因为要做好任何一件事,所必需的素质都是大同小异的,不是么。

进行Android开发所必需的理论知识:

Android的语法是基于Java的。因此如果对于Java有一定的掌握,开始Android的学习将是一件轻松的事。但是Java并不是学习Android前所必需的,甚至对于Java的了解有时反而会成为学习Android的障碍。毕竟,Android有许多独有的特性,以Java的思路去思考Android有时反而会产生限制。不过基础的OOP编程思想是必要的,如果仅仅学习过C语言这样的面向过程语言,恐怕需要先补充一些知识才能对Android比较顺利地上手。

此外一些基本的计算机相关知识自然也是需要的。对eclipse以及命令行的熟悉将是一种帮助。在开发某些类型的应用程序时OpenGL ES以及SQLite等的相关知识也是必需的。

进行Android开发所必需的软硬件准备:

  • 软件方面
    1. 操作系统:操作系统方面并没有太多的限制,Windows、Max OS和Linux都有对应的SDK。
    2. Java运行环境:开发Android之前需要在计算机上配置好JRE(Java运行环境,Java Runtime Environment),安装好JDK(Java开发套件,Java Development Kit )。具体的方法在开发指南中会有介绍。
    3. IDE(集成开发环境,Integrated development environment):Android的话推荐使用eclipse,这也是Android官方所推荐的。目前的最新版是3.7的Indigo。
  • 硬件方面
    1. 分辨率较高的显示器很重要。笔记本电脑的话或许没有办法,如果是台式机的话,推荐使用分辨率至少为1920×1080的显示器。
    2. 硬盘性能也对开发有着比较大的影响。有条件的话还是使用比较高速的硬盘比较好,能节省不少时间。
    3. 处理器的性能主要影响模拟器的速度。如果有实机进行开发的话,这一点就并不那么重要了。

初学Android开发的一点建议:

要开始学习Android的话,可以选择买一本好书,然后边读边实践。也可以从网络上找资料,自己摸索尝试。前者的优点是翻书比较方便,而且内容经过了作者的整理,缺点则是书的内容毕竟有限。后者的优点是免费,内容多,缺点是比较杂乱,花费时间可能会比较多。

另外自己深有体会的是,网络上的中文内容并不是很齐全,相较英语甚至是日语而言,整体水平低了不少。因此在遇到问题时,尝试通过英语搜索会是一个好主意。自己也有多次“通过中文很久无法找到答案,而通过英语/日语很快解决了问题”的经历。另外在例如Stack Overflow或是Quora这样的问答网站里几乎总是能找到高质量的答案(如果没有,就自己提问吧,得到满意回答的可能性是很高的),特别是Stack Overflow,在编程中遇到问题时应该想到它。总而言之,一定的英语能力可以大大提高自己的学习效率。

有了这些准备之后,就开始正式的Android开发之旅吧。首先可以跟着别人的代码摸索一下Android开发的大致流程,之后了解一下Android提供的各种控件。有了一定的熟悉之后,就可以开始考虑试着编写自己的程序了。

推荐书籍:

可以参见我的一些简单的笔记

下一次将具体介绍一下Android开发环境的搭建。

创建程序启动屏幕

代码及范例:

说明:通过新建一个线程来创建一个splash screen,效果就如同电脑上Office、Photoshop、Media Go等软件启动时有一个画面显示进度那样。在此期间程序可以进行初始化工作,减少用户的等待感。

Android中将整型变量转换为字符串变量

代码:Integer.toString(int num)

范例:

说明:将括号内的整型变量转换为字符串变量以供使用,比如drawText等。

应用程序基础

Android应用程序以Java编程语言写成。Android SDK工具将代码连同数据和资源文件一起编译进一个以.apk为后缀的档案文件——Android包(Android package)中。一个.apk文件中的所有代码被认为是一个程序,它也是使用Android的设备的程序安装文件。

一旦被安装,每一个Android程序就活在其自己的安全沙盒中:

  • 每一个程序默认都运行于其独立的Linux进程中。当任何一段代码需要被执行时Android会开始一个进程,而当它不再被需要或当其它程序请求系统资源时进程将被关闭。
  • 每一个进程有其自己的虚拟机,因此程序代码的运行和所有其他程序是孤立开的。
  • 每一个程序默认被分配一个专属的Linux用户ID。由于设立的权限,因此程序的文件仅对该用户和程序可见。同时也有很多方法将程序文件输出给外部程序。

两个不同的程序可以共享同一个用户ID,这样它们的文件将会相互可见。为了节省系统资源,两个程序还可以运行于同一个进程中,并使用同一个虚拟机。

应用程序组件

Android的一大核心特性是一个程序可以使用其他程序的部件(如果那些程序允许的话)。例如,当你的程序需要显示一个滚动式列表时,如果有另外的某个程序拥有一个合适的滚动式列表程序并且向其他程序开放使用权限,那你就可以让这个已经存在的滚动列表来完成你的工作,而不必自己写一个新的滚动列表。并且,它只会在你需要的时候启动你所需要的那一部分。 为了实现这个工作,这个系统必须在当任何一个部分被需要时都能创建一个新的程序进程,并为这个部分实例化一个Java对象。因此,和其他系统上的程序不同,Android程序没有一个单独的入口指针以进入整个程序(例如,没有main()函数),并且这些程序有一些必需的组件使得系统可以在需要时初始化并运行它们。组件有以下四种:

活动(Activity)

为了让用户可以集中处理一个操作,一个活动呈现为一个视觉用户界面。例如,一个活动可以呈现为一个项目列表以供用户进行选择,或者显示照片及其注释。一个短消息程序可能会有一个活动来列出可用的收件人,一个活动可以给选定的收件人撰写消息,以及另外一个活动用来查看旧消息或改变设定。尽管它们一起工作以形成一个完整的用户界面,但每一个活动都是独立于其他活动的。每一个活动都是Activity类的一个子类。

一个程序可以仅由一个活动组成,或者像之前提到的短信息程序那样由多个活动组成。用怎样的活动和多少个活动取决于程序和它的设计。通常其中的一个活动会被标识为当程序启动时第一个被呈现在用户面前的那个。让当前活动启动另一个活动就可以从一个活动切换到下一个。每一个活动被分配了一个默认的窗口来绘图。一般来说这个窗口将填满屏幕,但它也可以小于屏幕而浮在其他窗口的上方。一个活动还可以使用更多的窗口——比如,一个在活动中出现的弹出式的对话框,提示用户进行操作,或者当用户选择了屏幕上一个特定项目后出现一个窗口向用户显示重要的信息。

窗口的视觉内容由视图(View)的一个继承提供,这些视图是从基本的View类衍生出的对象。每一个视图控制窗口中的一块特定的矩形区域。父视图包含并组织它们的子视图的布局(Layout)。叶视图(那些位于继承底部的视图)在它们控制的矩形区中绘图并对指向该区域的用户操作进行回应。因此,视图是一个活动与用户的交互之处。比如,一个视图可以显示一个小的图片并在用户轻触这张图片时启动一个动作。Android中有很多你能马上使用的视图——包括按钮、输入框、滚动条、菜单项目、可选框等等。

视图的继承由Activity.setContentView()方法来设置到一个活动的窗口中。一个内容视图是这个继承的根视图。(参阅另外的用户界面文档来获得关于视图和继承的更多信息)

服务(Service)

服务没有视觉用户界面,它在后台运行不确定的一段时间。比如,一个服务可以在用户处理别的事的同时在后台播放音乐,或从网络接收数据,或计算并给活动提供所需的结果。每一个服务都继承于Service类。

一个基本的例子就是音乐播放器播放一个播放列表中的音乐。这个播放程序可能有一个或多个活动来允许用户选择歌曲并开始播放。但是,播放本身不应该由一个活动来操作,因为用户希望他们离开播放器进行别的工作时音乐仍然继续播放。为了让音乐继续,播放器活动应该在后台开启一个服务。系统会保持音乐播放服务一直运行即使启动它的活动已经不在屏幕上。

关联一个正在运行的服务(并且要是它没在运行的话启动这个服务)是可能的。当关联后,你可以通过这个服务的一个接口和它通信。对于音乐服务来说,这个接口将允许用户暂停,停止和重新播放。

如同活动和其他组件一样,服务运行于程序进程的主线程中。因此它们不会阻碍其他组件或是用户界面。它们常产生一个另外的线程来执行消耗时间的任务(比如音乐播放)。之后请看进程和线程。

如同活动和其他组件一样,服务运行于程序进程的主线程中。因此它们不会阻碍其他组件或是用户界面。它们常产生一个另外的线程来执行消耗时间的任务(比如音乐播放)。之后请看进程和线程。

广播接收者(Broadcast Receivers)

作为一种组建,广播接收者仅仅接收并回应广播通知。很多广播是在系统代码中创建的——比如,通知时区已被改变,通知电量低下,通知拍摄了一张照片,或通知语言偏好被用户改变。程序也可以初始化各种广播——比如,让别的程序知道设备完成了某些数据的下载并已经能够被使用。

一个程序能够有任意多的广播接收者来对任何它认为重要的通知进行回应。所有的广播接收者都继承与BroadcastReceiver基类。

广播接收者不会显示出一个用户界面。然而,它们可能会启动一个活动来响应接收到的信息,或者是使用通知管理器(NotificationManager)来提示用户。通知可以通过各种途径来引起用户的注意——背光的闪烁,设备的震动,声音的播放等。它们通常会在状态栏显示一个图标,用户可以打开它来获得更多的信息。

内容提供者(Content Providers)

内容提供者使程序中特定的一组数据可被其他程序使用。这些数据能够被储存在文件系统中,在SQLite数据库中,或其他任何适合的地方。内容提供者继承于ContentProvider基类,它运用一系列标准的方法来使得其他程序能够接受并储存它所控制的这部分数据。然而,那些程序不会直接使用这些方法。通常他们会使用一个ContentResolver对象并使用它的方法。ContentResolver能够和任何内容提供者交流信息,它与它们合作管理任何类型的进程间交流。

请参阅另外的内容提供者文档来获取更多关于如何使用内容提供者的信息。

当存在一个请求需要被某一特定的组件处理时,Android会确保这个组件的程序进程处于运行状态,并在必要时启动它。如果该组件的一个正确的实例是可用的,系统会在必要时创建这个实例。

激活组件:意图(Intent)

内容提供者会在有一个来自ContentResolver的请求时被激活。其他三种组件——活动,服务和广播接收者——是由被称为意图的异步信息来激活的。一个意图是一个Intent对象,这个对象包含了激活信息的内容。对于活动和服务来说,它命名了被请求的活动并分配了要执行操作的数据的URI等。比如,一个意图可以传达一个请求,让一个活动向用户显示一张图像或是允许用户编辑文字内容。对于广播接收者来说,Intent对象命名了被通知的操作内容。比如,它能通知相关联的部分照相机的拍摄键被按下的这一消息。 有几种不同的方法来激活每一种组件:

  • 可以通过向Context.startActivity()或是Activity.startActivitiForResult()传递一个Intent对象来启动一个活动(或让这个活动做一些新的工作)。接受响应的活动会调用它的getIntent()方法来查找最初启动它的那个意图。Android调用该活动的onNewIntent()方法来传递之后其他的意图。
  • 一个活动常常会启动另一个活动。如果希望从启动的活动中获得一个返回的结果,就要改用startAcitivityForResult()方法而不是startAcitity()。比如说,如果一个活动启动了另一个活动来让用户选择一张照片,那么应该要能收到返回的那张被选择的照片。其结果会被返回到一个Intent对象,然后被传递到前一个活动的onActivityResult()方法。

  • 通过向Context.startService()传递一个Intent对象可以启动一个服务(或给正在运行的服务一个新的指令)。Andoird会调用这个服务的onStart()方法然后将它传递给Intent对象。
  • 同样的,可以传递一个意图给Context.bindService()来给正在调用的组件和目标服务间建立联系。该目标服务会在onBind()中收到一个Intent对象(如果这个服务没有在运行,bindService()还可以选择启动它)。比如说。一个活动可以和前面提到过的音乐播放服务相连接,这样它能给用户提供一种途径(或者说一个用户界面)来控制播放。这个活动将调用bindService()来建立这个连接,然后调用在服务内定义的一些方法来控制播放。 在之后会更详细说明远程步骤调用(Remote procedure call)如何与服务建立连接。

  • 通过传递一个Intent对象给比如Context.sendBroadcast()、Context.sendOrderedBroadcast()、Context.sendStickyBroadcast()或是其他任何它们的变体,一个程序能够初始化一个广播。Android会调用任何可能接收的广播接收者的onReceive()方法来把这个广播递送给它们。

关于意图的更多信息,请参阅另外的“意图与意图过滤器”条目。

关闭组件

内容提供者只有在它对来自ContentResolver的请求进行响应的时候才处于活动状态。类似地,广播接收者只有在响应一个广播通知的时候才是活动的。因此,没有必要显式地关闭这两种组件。 另一方面来说,活动(Activity)负责提供用户界面。它们要与用户进行长期的交互,因此只要这个交互仍在继续,一个活动即使处于空闲时也要保持活动(Active)状态。同样地,服务也可能要保持长时间的运行状态。因此Android中提供了关闭活动和服务的方法:

  • 可以通过调用finish()方法关闭一个活动。而通过调用finishActivity()则可以关闭另一个(由startActivityForResult()方法启动的)活动。
  • 调用一个服务自身的stopSelf()方法可以关闭这个服务,或者也可以调用Context.stopService()来实现这个操作。

当组件不再被使用时或剩余内存不足以启动其他更多的组件时,系统可能会自动关闭这些组件。在稍后的组件生命周期板块中会更加具体地讨论这一问题及其后果。

manifest文件

Android在开始一个应用程序组件之前必须先确认其存在。因此,程序必须在manifest文件中声明这些组件,并把这个文件与Android包相绑定。此外,.apk文件中则是包含了程序的代码、文件和资源。 manifest文件是一种结构化的XML文件,并且对于任何程序来说都只能被命名为AndroidManifest.xml。除了声明应用程序组件,它还有一系列的功能,比如对程序要关联的(Android默认库以外的)库进行命名,或者对程序希望取得的操作权限进行认证。 不过manifest的主要任务还是告诉Android一个程序有哪些组件。比如,一个活动将会被这样声明:

<activity>元素的name属性命名了这个活动(它是一个Activity类的子类)。而iconlabel属性则指向了包含了图标和标签的资源文件,它们会被显示以表示这个活动。 其他的组件也被以相似的方式来声明——<service>元素声明服务,<receiver>元素声明广播接收者,<provider>元素声明内容提供者。没有在manifest中被声明的活动、服务和内容提供者对于系统来说是不可见的,所以也就无法被运行。然而,广播接收者既可以在manifest中声明,也能够在代码中作为一个BroadcastReceiver对象声明后通过调用Context.registerReceiver()来向系统注册这个组件。 关于如何构建一个manifest文件的更多信息,请参见”AndroidManifest.xml文件“。

意图过滤器(Intent filters)

一个Intent对象可以命名一个目标组件。当这么做时,Android会通过manifest文件中的声明来找到这个组件并激活它。但如果这个目标组件没有被显式地命名,Android会比较可能的目标组件的意图过滤器和这个Intent对象,之后选择一个最合适的组件来响应这个意图。一个组件的意图过滤器告诉Android这个组件可以处理哪些类型的意图。和别的一个组件所必需的信息一样,意图过滤器需要在manifest文件中被声明。下面是前面的例子的一个扩充,它向这个活动添加了两个意图过滤器:

例子中的第一个过滤器——行动(action)“android.intent.action.MAIN和类别(category)“android.intent.category.LAUNCHER”的组合——一种常见的情况。它标明了这个活动会被显示在应用程序启动器中(也就是设备中那个列出了所用用户可以启动的程序列表的画面)。换句话说,这个活动将作为这个程序的入口,当用户从启动器中选择了这个程序后,该活动会被第一个启动。 第二个过滤器声明了这个活动可以执行某种数据类型的操作。 一个组件可以有任何数量的意图过滤器,每一个声明一种不同的能力。如果一个组件不含有任何过滤器,它将只能被那些明确以其为目标的意图启动。 对于在代码中创建并注册的广播接收者来说,意图过滤器会被直接实例化为一个IntentFilter对象。其他所有的过滤器则是在manifest中被建立的。 关于意图过滤器的更多信息,参见另外的“意图和意图过滤器”篇目。

活动和任务(Task)

正如前面提到过的,一个活动可以启动另一个活动,甚至是在其他的应用程序中的活动。想象一下,比如,你希望向用户显示某地的一张街道地图,而现在已经有了一个活动可以做到这个功能,那么你的活动要做的就只是把需要的信息放入一个Intent对象然后通过startActivity()传递这个意图。那个活动将会显示地图,当用户按下返回键时,你的活动将会重现屏幕。 对于用户来说,地图显示似乎是你的程序的一部分,尽管它其实是在另外一个程序中定义并在另一个程序的进程中运行的。Android通过在同一个任务(task)中保持这两个活动来呈现这样的用户体验。简单说,一个任务是用户所感受到的“一个程序”。它是一组相关的活动,被分配在一个栈中。栈底部活动是开始整个任务的那个活动。通常它也是用户从应用程序启动器中选择的那个活动。栈顶部的活动是正在运行的活动,它是用户操作的接收者。当一个活动启动了另一个活动,新的活动被推入栈中,成为正在运行的那个活动。前一个活动则保持在栈中。当用户按下返回键,当前活动会从栈中弹出,而前一个活动成为当前正在运行的活动。 栈中包含有对象,因此如果一个任务中有同一个活动子类的多个实例处于开启状态,比如说多个地图显示程序,栈对于每个实例会有单独的入口。栈中的活动不会被重新排序,仅仅是被推入和弹出而已。 一个任务是一个包含多个活动的栈,不是manifest文件中的一个类或是元素。因此无法单独为一个任务中的活动设定值。在栈底的活动中,整个任务的值会被设定。比如,下一节中将讨论“任务的关系(affinity)”,通过读取一个任务的底部活动的“关系”可以获得这个任务的值。 一个任务中所有的活动作为一个整体一起移动。整个任务(即整个活动栈)可以被带至前台或是送往后台。设想一下,比如说当前任务有四个活动在栈中,其中三个活动在当前活动之下。用户按下了HOME键,前往了程序启动器并选择了一个新的程序(事实上是一个新的任务)。当前的任务被送往了后台,同时新的任务的底部活动被显示。一段时间以后,用户回到主界面并再次选择了之前的那个程序(或者说任务)。那个有四个活动在栈的任务将返回前台。当用户按下返回键,用户刚刚离开的那个活动不会被显示(即前一个任务的底部活动),而是显示同一个栈中的上一个活动(同时该栈的顶部活动被移除)。 上一段所描述的是活动和任务的默认行为。但是可以通过一些方法来完全地改变它们的行为。活动和任务间的关联,以及一个任务中某一个活动的行为,是由交互旗标(interaction flag)和属性(attribute)所控制的,前者存在于启动了活动的Intent对象中,后者存在于manifest中活动的<activity>元素中。两者都能改变活动和任务间的行为。

主要的意图旗标有:
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
FLAG_ACTIVITY_SINGLE_TOP

主要的<activity>属性有:
taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch

下面将描述其中一些旗标和属性的作用,以及它们如何进行交互工作和使用上的注意事项。

关系(affinity)和新任务

一个程序中的所有活动默认和其他活动有一种“关系”——即,它们默认属于同一个任务。然而,可以用<activity>元素中的taskAffinity属性来为每一个活动设置单独的关系。不同程序中的活动可以被定义为共享同一个关系,同一个程序中的活动也可以被分配不同的关系。在两种情况下关系将发挥作用:当Intent对象启动了一个包含有FLAG_ACTIVITY_NEW_TASK旗标的活动时,或者这个活动的allowTaskReparenting属性值为true

FLAG_ACTIVITY_NEW_TASK旗标

正如前面所说的,一个新的活动默认会加入调用了startActivity()的那个活动的任务中。它会被推入相同的一个栈中。然而,如果被传递给startActivity()的Intent对象里包含有FLAG_ACTIVITY_NEW_TASK旗标的话,系统会给新的活动分配一个不同的任务。如旗标的名称,通常那会是一个新的任务。不过也可以不是新任务。如果存在一个任务和这个新的活动有同样的关系,这个活动就会加入这个活动。不然则是创建一个新的任务。

allowTaskReparenting属性

如果一个活动的allowTaskReparenting属性值为“true”,这个活动能在另一个和它有关系的任务来到前台时从启动它的任务转移到入这个任务中。例如,在一个旅行用程序中有一个活动能报告所选地点的天气状况。这个活动和该程序中的其他活动一样有着相同的关系(也就是默认关系),并且允许重新指定父级。当你自己的一个活动启动了天气报告活动,这两个活动是属于同一个任务的。然而,当那个旅行用程序再次来到前台,天气报告活动将被重新分配给该任务并被显示。

如果,从用户的角度来看,一个.apk文件包含了不止一个的程序,将有必要对与这些程序相关的活动分配不同的关系。

启动模式

有四种不同启动模式可供指定给一个<activity>元素的launchMode属性:

standard” (默认模式)
singleTop
singleTask
singleInstance

这些模式在以下四个方面有所不同:

  • 对意图进行响应的活动的所属任务的不同。对于“standard”和“singleTop”模式,活动属于创建了意图(并调用了startActivity())的任务——除非Intent对象包含了FLAG_ACTIVITY_NEW_TASK旗标。因为这种情况下会像之前在“关系和新任务”一节中说的那样选择一个不同的任务。 与此相对应的,“singleTask”和“singleInstance”模式将活动标记为始终处于任务底部。它们这些活动定义了一个任务并将不会在其他任务中运行。
  • 活动是否存在多个不同的实例。一个“standard”或是“singleTop”的活动可以被多次实例化。它们可以属于多个任务,并且一个任务里可以有该活动的多个实例。 相对应的,“singleTask”和“singleInstance”的活动被限定于只能有一个实例。这些活动处于任务的底部,因此这个限制意味着在设备上该任务不可能同时存在一个以上的实例。
  • 活动的实例是否在它所属的任务中有其他活动。一个“singleInstance”活动作为它所属的任务中唯一的一个活动而存在。如果它启动了另一个活动,无论那个活动的启动模式是什么都必定会在另一个不同的任务中被创建,就好象意图中含有FLAG_ACTIVITY_NEW_TASK。在其他的方面,“singleInstance”模式等同于”singleTask“。另外的三种模式允许多个活动属于一个任务。“singleTask”活动将始终是任务的根活动,这个活动可以启动其他将被分配到该任务的活动。“standard”和“singleTop”活动的实例可以存在于栈的任何位置。
  • 一个类的新的实例是否会被启动以处理一个新意图。对于默认的“standard”模式,在响应每一个新意图时都会创建一个新的实例。每一个实例只能处理一个意图。对“singleTop”模式来说,一个已经存在的类实例在当它处于目标任务的活动栈的顶部时,它可以被重新使用以处理一个新的意图。如果这个活动不在顶部,则无法被重用。这时,会有一个新的实例被创建并被推入栈中来处理这个新意图。
    例如,假设一个任务的活动有一个根活动A,在其上按顺序有活动B,C,D。所以这个栈就是A-B-C-D。这时传来了一个意图启动类型D的活动,如果D是“standard”的启动模式,这个类就启动一个新的实例,同时栈变为A-B-C-D-D。然而要是D的启动模式是“singleTop”,那么现有的这个实例会去处理那个新的意图(因为它已经在栈顶了),这个栈会保持为A-B-C-D。
    另一方面来说,如果这个意图是启动B类型的,无论B的模式是“standard”还是“singleTop”,都会启动一个新的实例(因为B不再栈顶),使这个栈变为A-B-C-D-B。
    如前面所说到的,“singleTask”或“singleInstance”类型的活动只能有一个实例,所以该实例会处理所有的新意图。“singleInstance”活动永远保持在栈的顶部(因为它是这个栈里唯一的活动),所以它会始终处理新的意图。然而,“singleTask”活动可能会存在一些在栈中位于其上方的活动,那样它就不处于处理意图的位置了,这个意图回被舍弃。(即使意图被舍弃了,它也会使任务按照本应该的那样来到前台。)

当一个存在的活动被要求处理一个新的意图时,Intent对象通过onNewIntent()被传递至这个活动。(最初启动这个活动的意图对象可以通过调用getIntent()来检索)
需要注意,当一个活动的新实例被创建以处理新的意图时,用户可以按下返回键来回到前一个状态(或者说前一个活动)。但是当一个已经存在的活动处理一个新的意图时,用户无法通过返回键来回到这个实例在接受意图之前的状态。

关于启动模式的更多内容,参见<activity>元素的描述。

栈的清除

如果用户长时间不运行一个任务,系统会清除除了根活动外其他所有的活动。当用户再次返回到这个任务时,这个任务会和离开之前一样,不过只有最初的活动会被呈现。这一思想的实质是认为用户在一段时间后返回之前的任务时会希望放弃之前行进的工作而开始新的。

这是默认的情况。可以通过一些活动属性来控制并修改这一行为:

alwaysRetainTaskState属性

如果这个属性在一个任务的根活动里被设置为“true”,那么之前描述的默认行为就不会发生。任务会长时间保持所有的活动于栈中。

clearTaskOnLaunch属性

如果这个属性在一个任务的根活动里被设置为“true”,那无论用户是离开任务还是回到任务,该栈都会被清除到只剩根活动。换言之,这是alwaysRetainTaskState的相反极。即使只经过了短暂的离开,用户也会回到任务的最初状态。

finishOnTaskLaunch属性

这个属性类似于clearTaskOnLaunch属性,不过它只对单个活动有效而不是整个任务。而且它能清除包括根活动在内的任何活动。当它的值为“true”时,该活动仅仅在当前状态下才是任务的一部分。一旦用户离开后,即使再次返回,这个活动也不会被呈现。

还有一种方法可以强制活动从栈中清除。如果一个Intent对象含有FLAG_ACTIVITY_CLEAR_TOP旗标,同时目标任务已经存在有一个应当处理此意图的活动类型的实例,那么这个实例之上的所有活动将被清除从而该实例能处于栈顶来响应这个意图。如果这个活动的启动模式是“standard”,它同样也会被移除出栈,会有一个新的实例被启动来处理那个意图。之所以会这样的原因是“standard”启动模式下会为每一个新意图创建一个新的实例。

FLAG_ACTIVITY_CLEAR_TOP通常都会和FLAG_ACTIVITY_NEW_TASK一起使用。此时,这两个旗标的作用是将一个已存在的活动置于另一个任务中的某个合适的位置使其可以响应某个意图。

任务的开始

通过给一个活动设置“android.intent.action.MAIN”和“android.intent.category.LAUNCHER”这两个意图过滤器来设定其动作和目录,可以将这个活动设为一个任务的入口。(在之前的意图过滤器部分有这样的一个例子。)这种类型的过滤器会在应用启动器中显示该活动的图标和标签,使用户可以启动这个任务,或在启动后的任何时刻返回到该任务。

这里的第二个能力很重要:用户必须可以在离开一个任务之后再回到这个任务。为此,“singleTask”和“singleInstance”这两个会使活动始终初始化任务的启动模式应当仅被用于有MAINLAUNCHER过滤器的活动。想象一下,假如没有过滤器会发生什么:一个意图启动了“singleTask”的活动,初始化了一个新任务,然后用户在这个任务中进行了操作。之后用户按下HOME键。任务被置于后台,被主界面遮挡住。但是,由于它没有被显示于应用启动器中,因此用户将无法返回到这个任务。

对于FLAG_ACTIVITY_NEW_TASK旗标来说也有类似的问题。在这个旗标使一个活动开始一个新的任务后,如果用户按了HOME键离开的话,必须提供某种方式使用户可以重返这个任务。有些实体(比如通知管理器)始终会在一个外部而非其自身的任务中启动活动,所以它们始终会在传递至startActivity()的意图中包含FLAG_ACTIVITY_NEW_TASK。如果你的活动涉及某个会使用该旗标的外部实体时,注意要提供用户一个返回到任务的途径。

如果你不希望用户可以返回到原来的活动的话,把<activity>元素的finishOnTaskLaunch属性设置为“true”即可。具体的可以参见之前的栈的清除。

进程和线程

当一个应用程序组件的第一个部分要运行时,Android会启动为它启动一个Linux进程及一个单独的执行线程。默认来说,所有的组件会在这个线程和进程里运行。

不过,你也可以让组件运行于其他进程,或者是在任意其他进程中生成新的线程。

进程

一个组件将运行于哪一个进程是由manifest文件控制的。组件元素——<activity><service><receiver><provider>——每一个都含有一个process属性,指定了该组件应当运行于哪一个进程。可以设置这些属性使每一个组件运行于其自身的进程,或者可以让一部分的组件共用同一个进程。甚至可以设定让不同程序中的组件运行于同一个进程——假设这些程序共享同样的Linux用户ID并被相同的权限标记。<application>元素也有<process>属性,用来给所有的组件设定默认值。

所有组件都是在制定进程的主线程中被实例化的,系统对组件的调用也是从这个线程中分发的。系统不会对每一个实例创建单独的线程。因此,那些响应调用的方法——比如View.onKeyDown()这类的报告用户操作的方法,或者是在之后的组件生命周期一节中会讨论的生命周期通知(lifecycle notification)——总是会运行于进程的主线程中。这意味着组件在被系统调用时不应该长时间或阻断性地运行(比如网络工作或是循环计算),因为这样会阻断在进程中的其他组件。你可以为长期操作创建单独的线程,如同接下来会讨论的那样。

Android在某些时候(比如内存不足而用户又马上需要更多的内存来运行其他的进程)会做出关闭某个进程的决定。运行于该进程的应用程序组件也就相应地会被销毁。当再次需要它们时将会重启一个进程。

在决定终止哪一个进程时,Android会权衡它们对于用户的重要性。比如,系统更有可能关闭一个活动在屏幕上不可见的进程而不是一个活动仍然可见的进程。因此,是否终止一个进程取决于进程中组件的运行状态。而这则是之后的章节,组件生命周期,的内容了。

线程

即使你可能会限制你的程序运行于单独的进程,有时你也可能需要创建一个线程用于后台工作。由于用户界面必须快速响应用户操作,含有活动的线程不应当也含有消耗时间的操作(比如网络下载)。任何可能无法迅速完成的工作都应该被分配给不同的线程。

线程在代码中通过Java的Thread对象创建。Android提供了一系列类来便捷地管理线程——在线程中进行消息循环的Looper,执行消息的Handler,以及设立含有消息循环的线程的HandlerThread。

远程过程调用

Android有一种轻量级的远程过程调用机制(remote procedure call — RPC)——一种方法会被本地调用,之后(在另一个进程中)远程执行,最后结果会被传回调用者。这样势必会在一定程度上分解方法调用以及所有相关数据使系统得以能够理解它们,将其从本地进程传送出去,在远程进程中指定空间,重组和重新调用。返回值必须被反向传输。Android提供了完成这些工作的所有代码,因此你只需要集中注意力于定义和实现RPC接口自身就好。

RPC接口只包含方法。默认地,即使没有返回值,所有方法也都是同步执行的(本地方法在远程方法完成前将被阻塞)。

简单来说,这个机制将这样运作:由通过一个简单的IDL(接口定义语言)声明你想要执行的RPC接口开始。在这个声明中,
aidl工具生成一个必须在本地和远程进程中都可用的Java接口定义。它如下图所示含有两个类:

内部类含有所有需要的用于管理通过IDL声明的接口远程过程调用的代码。两个内部类都有IBinder接口。其中一个在系统内部被本地使用,你写的代码可以忽略它。另一个,Stub,继承于Binder类。另外,为使内部代码有效化IPC调用,它包含了你所声明的RPC接口的方法的声明。如图所示,你需要Stub的子类来使用这些方法。

一般来说,远程进程由一个服务来管理(因为服务可以告知系统这个进程与其他进程的联系)。该进程含有由aidl工具生成的接口文件和执行RPC方法的Stub子类。服务的客户端则只含有aidl工具生成的接口文件。

这里将说明服务和它的客户端之间的连接是如何被建立的:

  • 服务的(本地端的)客户端会执行onServiceConnected()和onServiceDisconnected()方法以获知何时与远程服务的连接被成功建立记忆何时中断。它们之后将调用bindService()来建立连接。
  • 服务的onBind()方法可以被用来接收或是拒绝连接,这取决于它所收到的意图(即传递到bindService()的意图)。如果一个连接被接受,它将返回一个Stub子类的实例。
  • 如果服务接受了连接,Android会调用客户端的onServiceConnected()方法并传给它一个IBinder对象。这是一个由服务管理的Stub子类的代理。通过这个代理,客户端能够在远程服务中进行调用。

以上的描述中省略了RPC机制的细节。更多信息请参见“使用AIDL设计远程接口”和IBinder类的描述。

线程安全(thread-safe)方法

在一些context中,你使用的方法可能会被不止一个线程调用,因此必须使这段代码线程安全。

对于会被远程调用的方法来说,这是非常正确的——就像在前一节的RPC机制中所提到的那样。当IBinder对象内的一个方法的调用在与IBinder相同的进程中生成时,这个方法会在调用者的线程中被执行。然而,当这个调用在另外的进程中生成时,Android会在IBinder所在进程中的线程里选择一个来执行这个方法;它不在进程的主线程中被执行。比如说,一个服务的onBind()方法会被服务所在进程的主线程调用,而onBind()所返回的对象(比如使用RPC方法的一个Stub子类)中使用的方法会被池线程调用。由于服务可以有不止一个客户端,多个池线程可以同时对同一个IBinder方法操作。因此IBinder方法必须线程安全。

同样地,内容提供者可以接收能创建其他进程的数据请求。尽管ContentResolver和ContentProvider类隐藏了多进程通信管理的细节,响应了query(),insert(),update()和getType()方法的ContentProvider方法是被内容提供者所在进程的池线程而非主线程调用的。由于这些方法可能会同时被多个线程调用,因此它们也必须确保线程安全。

组件生命周期

应用程序组件具有生命周期——从Android响应意图而实例化组件开始到该实例被销毁为止。在这之间,它们有时处于活动状态,有时处于非活动状态(对于活动来说,则是可见与不可见)。这一节会讨论活动、服务、广播接收者的生命周期——包括在它们的存在期间内的各种状态,提示状态转换的方法,以及这些状态对组件所属进程是否被终止及实例是否被销毁等的可能性的影响。

活动的生命周期

一个活动有三种必要的状态:

  • 当它在屏幕前台(处于当前任务的活动栈的顶部)时,它是活动的(active)或者说是正在运行的。该活动处于用户操作的焦点之下。
  • 当它失去焦点而仍然可见时,它是暂停的(paused)。也就是说,有另一个活动在其上方,但这个活动是透明的或没有覆盖整个屏幕,因此一些暂停的活动依旧是可见的。一个暂停的活动仍然是存在的(alive),它保持着所有的状态和成员信息并和窗口管理器(window manager)相关联,不过在内存过低的情况下系统会杀死这个活动。
  • 如果它完全被另一个活动遮盖,它是停止的(stoped)。它仍保持着所有的状态和成员信息。不过,它已经不再对用户可见了,因此它的窗口会被隐藏,通常会被系统在需要内存时关闭。

当一个活动是暂停或者停止的时候,系统可以通过要求其终止(调用其finish()方法)或者直接杀死它的进程来将它从内存中清除。当它再次呈现给用户时,它将是完全重启启动的,并被还原为它先前的状态。

随着一个活动在不同状态之间的切换,以下protected方法的调用能指示出这些变化:

void onCreate(Bundle savedInstanceState)
void onStart()
void onRestart()
void onResume()
void onPause()
void onStop()
void onDestroy()

所有这些方法都能被override以使在状态转换时正常工作。所有的活动都必须在对象被初次初始化时使用onCreate()来初始,不少活动还会使用onPause()来确认数据变化或准备停止用户交互操作。

调用父类的方法

使用任何活动生命周期的方法前都应先调用其父类的版本,比如
protected void onPause() {
super.onPause();
. . .
}

总的来说,这七种方法定义了一个活动的整个生命周期。通过使用这些方法,可以监控三种嵌套循环:

  • 一个活动的完整的生命周期处于第一次对onCreate()的调用与对onDestory()的调用之间。一个活动在onCreate()中完成左右的“全局(global)”状态设置,并在onDestroy()中将所有的资源释放。比如,对于一个从网络上下载数据的背景线程,活动将在onCreate()中创建该线程,之后在onDerstroy()中停止它。
  • 一个活动的可见的生命周期始于对onStart()的调用而止于对onStop()的调用。在此期间,用户可以在屏幕上看到活动,尽管它可能没有处于最前端和用户发生交互操作。在这两种方法之间,可以保留需要的资源以向用户显示该活动。比如,可以在onStart()中注册一个广播接收者来监视对UI会有影响的变化,之后当用户不会再看到屏幕显示内容时在onStop()中注销该接收者。onStart()onStop()方法可以多次被调用,活动也会在可见和不可见之间转换。
  • 一个活动的前台生命周期始于对onResume()的调用而止于对onPause()的调用。在此期间,该活动处于所有其他活动的上方并与用户发生交互。一个活动可能会在resumed和paused状态间频繁切换——比如,当设备进入睡眠状态或一个新的活动启动时,onPause()会被调用,而当一个活动得出结果或是有新的意图被传递时,onResume()会被调用。因此,在这两种方法间的代码应当是非常轻量级的。

下面的图描绘出了以上这些循环以及一个活动在状态间可能的转换路径。彩色的椭圆是一个活动的主要状态。线框中代表当活动转换状态时你可以用来执行操作的反馈方法。

下面的表格更加详细地介绍了这些方法,并说明了它们在整个生命周期中的位置:

方法 描述 可杀? 后继
onCreate() 当活动被创建时被调用。所有静态设定应当在此进行,包括创建view、对列表的数据绑定等等。会有一个Bundle对象传递给该方法,它包含了活动前一个状态的信息(如果该状态被记录的话)(参见之后的 保存活动状态)。

始终后续 onStart() 。

不可 onStart()
onRestart() 当活动停止时被调用,仅发生于被再次启动之前

始终后续 onStart() 。

不可 onStart()
onStart() 仅在活动可见之前被调用。

当活动来到前台时,后续 onResume() ,当活动退出前台隐藏时后续 onStop()

不可 onResume()

onStop()
onResume() 仅在活动开始用户交互前被调用。这时该活动处于活动栈的顶部,可以接受用户输入。

始终后续 onPause().

不可 onPause()
onPause() 当系统将要恢复另一个活动时被调用。该方法主要用于保存尚未保存的修改、停止动画,或者其他需要消耗处理器资源的工作。该方法应当迅速完成,不然下一个活动将一直等待至其完成才恢复。

当活动回到前台时后续 onResume() ,当活动变为不可见时后续 onStop()

可以 onResume()

onStop()
onStop() 当活动不再对用户可见时被调用。当该活动被销毁或有其他活动(已存在的或是新的活动)被恢复来覆盖此活动时会使用该方法。

当活动回到前台与用户发生交互时后续 onRestart() ,当活动被舍弃时后续onDestroy()

可以 onRestart()

onDestroy()
onDestroy() 当活动被销毁时被调用。这时一个活动能收到的最后一次调用。当活动完成(调用了finish() )或是系统为节省空间而销毁了该活动的实例时会调用该方法。可以通过 isFinishing() 来区别这两种情况。 可以

注意上表中的可杀一栏。它说明了系统是否可以在方法返回后的任一时刻杀死运行该活动的进程而不必执行更多的活动代码。三种方法(onPause()onStop()onDestroy())被标记为“可以”。由于onPause()是三者中的第一个,因此它是唯一可以保证在进程被杀之前被调用的——onStop()onDestroy()就不一定了。因此,应该使用onPause()来执行保存长期数据(比如用户编辑的内容)的工作。

可杀一栏中标记为“不可”的方法在它们被调用时会保护运行活动的进程不被杀除。因此一个活动在,比如说,onPause()返回直到onResume()被调用之间,是可杀的。在onPause()再次返回之前,它不会再次变为可杀的。

之后的“进程和生命周期”一节中将会提到,一个在定义上“技术性不可杀”的活动仍有可能被系统杀除——不过这只会发生在资源缺乏极端严重的情况下。

保存活动状态

当系统而不是用户关闭一个活动来获得内存时,用户会希望能再次回到活动并处于之前的状态。

为获取活动被杀之前的状态,可以在活动中使用onSaveInstanceState()状态。Android会在杀死活动之前调用这个方法——也就是在onPause()被调用前。会有一个Bundle对象传递给该方法,其中以名称-值对(name-value pair)的形式记录了活动的动态状态。当活动重新启动时,Bundle会被传递给onCreate()以及在onStart()之后调用的一个方法,即onRestoreInstanceState(),这样这两个方法都可以重创建被记录的状态。

onPause()及其他之前提到的方法不同,onSaveInstance()onRestoreInstanceState()不是生命周期中的方法。它们并非总是会被调用。比如,Android会在活动可能会被系统销毁前调用onSaveInstanceState(),但不会在用户销毁该实例时(比如按下返回键)调用它。这种情况下,用户不希望返回该活动,因此不必保存其状态。

由于onSaveInstanceState()并不是总是会被调用,因此应该只用它记录活动的瞬间状态,而非长期数据。应当使用onPause()来保存长期数据。

协调活动

当一个活动启动了另一个时,它们都会经历生命周期的转换。当其中一个暂停或是停止时,另一个会启动。有时,必须协调这些活动之间的关系。

当同一个进程中仅两个活动时,其生命周期的回馈顺序被清楚地定义如下:

  1. 当前活动的onPause()方法被调用。
  2. 接下来,被启动的活动的onCreate()onStart()onResume()方法依次被调用。
  3. 之后,如果启动的活动不再屏幕可见,它的onStop()方法会被调用。

服务生命周期

一个服务可以通过两种方法使用:

  • 一个服务可以被启动并允许运行直至有人停止它或是它自动停止。在这种模式下,它通过调用Context.startService()启动,调用Context.stopService()停止。通过调用Service.stopSelf()或Service.stopSelfResult()它可以自动停止。无论调用了多少次startService(),只需一次stopService()调用就可以停止服务。
  • 一个服务可以通过它定义输出的一个接口来被预编程式地操作。客户端建立一个与Service对象的连接,然后以此来调用服务。通过调用Context.bindService()来建立这个连接,调用Context.unbindService()来断开。多个客户端可以与同一个服务绑定。如果服务还没有被启动,bindService()可以选择启动它。

这两种模式不完全是不相关的。你可以绑定一个通过startService()启动的服务。比如,一个背景音乐服务以一个识别音乐播放操作的Intent对象调用startService()来启动。不久,比如当用户想对播放器进行某些操作来获取当前音乐的信息时,一个活动会通过调用bindService()与服务绑定连接。在这种情况下,stopService()在最后一次的绑定被断开前无法真正停止服务。

像活动一样,对于服务也有生命周期方法,可以用它们来监视状态变化。不过方法的数量较活动要少——只有三种——并且它们是public而非protected的:

void onCreate()
void onStart(Intent intent)
void onDestroy()

使用这些方法,可以监视服务的生命周期的两种嵌套循环:

  • 一个服务整个的生命期在onCreate()的调用和onDestroy()的调用之间。和活动一样,一个服务在onCreate()中进行初始化,在onDestroy()中释放所有的资源。比如,一个音乐播放服务在onCreate()中创建播放音乐的线程,之后在onDestroy()中终止这一线程。
  • 一个服务的有效的生命周期始于onStart()的调用。这个方法包含将被传递给startService()的Intent对象。该音乐服务会打开Intent来寻找播放哪一首音乐并播放。没有相对应于服务停止的回馈——即没有onStop()方法。

onCreate()onDestroy()方法被所有类型的服务调用,不管它们是由Context.startService()启动还是由Context.bindService()启动。不过,onStart()只被由startService()启动的服务所调用。

如果一个服务允许被绑定,那么它会有更多可用的回馈方法:

IBinder onBind(Intent intent)
boolean onUnbind(Intent intent)
void onRebind(Intent intent)

onBind()回馈会被传递一个传递给bindService的Intent对象,而对于onUnbind()则是一个被传递给unbindService()的意图。如果一个服务允许绑定,onBind()会返回客户端与其交互的通讯频道。当新的客户端与服务连接时onUnbind()方法可以要求调用onRebind()。

下图说明了一个服务的回馈方法。尽管它区分了由startService()创建的和由bindService()创建的服务,不过注意任何服务不论它是如何被创建的,都可以被客户端绑定,也就是说任何服务都可以接收onBind()onUnbind()调用。

广播接收者生命周期

广播接收者只有一种回馈方法:

void onReceive(Context curContext, Intent broadcastMsg)

当一条广播消息传递至接收者时,Android调用接收者的onReceive()方法并将包含该消息的Intent对象传给它。广播接收者仅有在执行该方法时才被认为是活动的(active)。当onReceive()返回时,它就变为非活动状态。

包含活动的广播接收者的进程是不会被杀的。只包含非活动组件的进程则可能在任何其他进程需要内存的时候被系统杀除。

当对一条广播消息的响应需要消耗时间时这就会有问题,因此,必须在另外的线程中完成这些工作,而不是在还有其他UI相关组件运行着的主线程。如果onReceive()生成线程后返回,这个进程包括新的线程会被认为是非活动的(除非进程中有其他活动的应用程序组件),而处于被杀除的危险之中。该问题的解决方案是为onReceive()启动一个服务,让服务来执行工作,这样系统就能知道进程中还有活动着的工作正在被处理。

下一节会更深入地讨论将被杀除的进程的脆弱性(vulnerability)。

进程和生命周期

Android系统试图尽可能长地维持一个应用程序进程,但在内存不足时它最终不得不移除旧的进程。Android将每一个进程放入一个由组件状态所决定的“重要性层级(importance hierarchy)”中以决定哪些进程要保持而哪些要杀除。最低重要性的进程会被首先杀除,之后是次重要的。共有五种层级。下面依重要性列出了他们:

1. 前台进程是用户当前需要操作的进程。当下列条件之一满足时一个进程被认为是前台的:

  • 它正在运行一个与用户发生交互的活动(Acitvity对象的onResume()方法被调用)。
  • 它有一个与用户正在操作的活动相绑定的服务。
  • 它有一个正在执行其生命周期回馈方法之一(onCreate(),onStart()或onDestroy())的Service对象。
  • 它有一个正在执行其onReceive()方法的广播接收者。

在任何时候,只会存在少数的前台进程。它们将被最后杀除——在内存极端不足的情况下。通常此时设备处于内存分页状态,需要杀除前台进程来保持用户界面响应。

2. 可见进程没有任何前台组件,但是可以影响用户在屏幕上看到的内容。当以下任一条件满足是一个进程为被认为是可见进程:

  • 它有一个不在前台但是被用户可见的活动(其onPause()方法被调用)。比如,当前台活动是一个对话框,而前一个活动仍可以被看到在其后方的情况。
  • 它有个一个与可见活动相绑定的服务。

可见进程被认为是很重要的,除非急需内存确保所有的前台进程,不然不会被杀除。

3. 服务进程是一个运行着由startService()方法启动的服务的进程,它不属于前两种进程。尽管服务进程不与任何用户直接看到的内容绑定,它们确实在执行用户希望的工作(比如背景播放mp3或是从网络下载数据),因此系统只有在亟需内存维持所有的前台和可见进程时才会杀除他们。

4. 后台进程有一个当前不对用户可见的活动(Activity对象的onStop()方法被调用)。这些进程对用户体验没有直接影响,当前台、可见或服务进程需要内存时它们会被随时杀除。通常有着大量的后台进程在运行,它们被保存在一个LRU表(Least recently used)中以确保最后看到的活动所在的进程会被最后杀除。如果一个活动正确地执行其生命周期方法并保存其状态,杀死它的进程不会影响用户体验。

5. 空进程是没有任何活动的(active)应用程序组件的进程。保持这样一个进程的唯一理由是作为缓存来提高下一次有组件要在其内运行时的启动速度。系统为了在进程缓存和底层核心缓存间平衡整体系统资源而常常杀除这些空进程。

Android根据进程中当前活动着的组件的重要性来尽可能高地给一个进程定级别。比如,如果一个进程包含一个服务和一个可见活动,那么进程会被分级为可见进程而不是服务进程。

另外,进程的层级可能会因为其他进程依赖于它而被提升。服务于另一个进程的进程不可能比那个进程的层级低。比如,进程A中的一个内容提供者服务于进程B中的一个客户端,又或是进程A中的一个服务与进程B中的一个组件绑定,那么进程A被认为至少要和进程B同样重要。

由于运行服务的进程比运行后台活动的进程层级高,一个执行长时间操作的活动应该开启一个服务来处理该工作而不是简单地创建一个线程——特别是该操作可能比活动本身持续更久时。这样的例子有个后台播放音乐或是上传一张拍摄的照片到网上。使用服务至少可以确保该操作有服务进程的优先级,而不必在意活动本身怎样。正如之前在广播接收者生命周期一节中提到的,这也是为什么广播接收者应当用服务而不是用线程来执行花费时间的工作的原因。

本页部分内容根据Android Open Source Project创作并共享的内容修改,并在知识共享 署名2.5许可协议中所述条款的限制下使用。