210615.《Learning Android中文版》学习笔记
《Learning Android中文版》学习笔记03
基本构件
本章介绍Android应用的基本组成部分:构件(Building Blocks)。
(译者注:本书在翻译中,对构件的名称一律保留原文,比如Activity、Intent、Service等等。)
到本章结束,你将对不同构件的应用场景、以及它们在实际应用中的关系有个概念性的认识。
基本构件
构件(Building Blocks)即构建Android应用所需要用到的组件。就像砖块之于盖房子,有构件的堆叠组合才可以构建出完整的应用。刚开始设计时,不妨参照自顶向下的设计方法:先想想,你的应用的外观是什么样子,有什么功能,大体怎样实现。然后就可以打草稿了,画下设计图,记录下设计思路。这对把握全局——怎样组合构件才能让它们正常工作——是大有好处的。
真实世界的例子
我们要做的是一个Twitter应用。用户的需求很清楚,那就是发新消息、查看朋友的新消息,这也正是它的基本功能。除此之外,我们还需要事先获知用户的用户名和密码才能登录Twitter。由此可知,我们需要三个界面。
其次,我们希望应用程序能够流畅地运行,而回避开网络连接情况的影响。应用程序应该只在设备联网时抓取Twitter消息,并缓存到本地。为此,我们需要用到一个后台运行的Service,也需要用到数据库。
我们也希望这个后台Service最好在设备开机时就能运行。这样用户只要打开应用,就能立即查看到朋友们的最新消息。
如上,这都是些很直白的需求。通过Android提供的构件,你可以轻松将以上的任务分离开来,独立完成。到最后只消简单地将它们打包,一个完整的应用也就成型了。
Activity/界面
Activity 即设备在某一时刻所显示的界面。一个程序通常含有多个界面,用户可在其间切换。对用户而言,这就是程序的外观部分。
我通常喜欢拿网页来类比Activity:就像一个网站含有多个网页,Android程序也含有多个界面(Activity);网站有主页,Android程序有主界面(main Activity)——也就是启动程序时显示的第一个界面;为了方便用户在多个页面之间跳转,网站会提供导航功能,Android也一样。
在网站里,你可以从某页面跳转到另一个网站的页面。类似地在Android中,你也可以在某程序的界面里启动或访问其它程序的界面。比如在联系人应用中,你可以选择某个朋友,直接进入短信应用的界面为他写消息。
Activity生命周期
因为涉及到新建Linux进程、为UI对象申请内存、解析XML文件以及最终的图像渲染,初始化Activity的成本是比较高的。既然在初始化上面花了这么大功夫,那么每次离开Activity就将其销毁就未免太浪费了。在这方面,Android引入了Activity Manager机制,用以自动管理Activity的生命周期。
Activity的创建、销毁、管理皆由Activity Manager全权负责。如果用户首次启动了某程序,Activity Manager会负责程序界面的初始化与显示;如果用户要切换界面,Activity Manager将原先的界面隐藏而不是销毁,这样可以加速下次访问;Activity Manager也负责销毁长时间未访问的Activity,为当前活跃的Activity腾出更多内存空间。引入Activity Manager机制的动机就是提升用户界面的速度,这对用户体验很重要。
![](https://img.haomeiwen.com/i22406019/bc02f8a05aab0fc5.png)
Android的编程环境有些独特的地方,那就是在Android开发中,你会发现自己更多是对程序状态的迁移做出响应,而很少需要主动控制它的状态。在托管的环境中添加自己的业务逻辑,这点倒是与Java的applet与servlet有些相似。因此谈及Activity的生命周期,我们很少关心它的当前状态,而对它的状态迁移更感兴趣。图4.1 Activity的生命周期 展示了Activity各个状态之间的迁移关系。
Starting状态
Activity在刚刚启动时还未进入内存,这就是Starting状态。在这时,Activity会调用一些我们提供的回调函数,随后进入Running状态。
留意,Starting状态到Running状态的切换是最为耗时的操作之一。耗时也就耗电,对电池的续航能力也有影响。因此不再显示的Activity不会被轻易销毁,留它驻留在内存中,到用户下次访问的时候可以节省资源。
Running状态
Activity显示在屏幕上允许用户交互,这是Running状态。我们有时称这一状态的Activity为“获得焦点”(in focus),也就是在这时用户交互的相关操作比如打字、触摸屏幕、点击按钮等等,都由这个Activity掌握。可知,同一时刻只能有一个Activity处于Running状态。
处于Running状态的Activity可以优先获取内存或者其它资源,这样有助于提高用户界面的响应速度。
Paused状态
当Activity没有获得焦点(没有用户交互)但仍可以在屏幕上看到时,它就是Paused状态。这种状态出现不多,因为屏幕通常不大,而Activity都是要么占据整个屏幕,要么不显示。那么进入Paused状态,往往就是因为有对话框显示在前面了。另外,Activity会在停止前进入Paused状态。
Paused状态的Activity依然拥有获取内存或者其它资源的优先权,因为它们依然显示在屏幕上,应该避免让用户感到不自然。
Stopped状态
当Activity不再显示却依然驻留在内存中的时候,就是Stopped状态。Stopped状态的Activity可能会被重复利用,重新进入Running状态。也有可能被销毁而从内存中移除。
系统将Stopped状态的Activity驻留在内存中,用以加速用户下次访问。毕竟重启一个Activity要比重新初始化一个Activity轻量的多。一切都还在内存里,需要做的只是把它显示出来。
Stopped状态的Activity可能会在任意时刻从内存中移除。
Destroyed状态
Destroyed状态的Activity就不再驻于内存了。当Activity Manager认为某个Activity已经不再需要的时候,就会销毁它,并把它视作Destroyed。在销毁之前,它会调用回调函数onDestroy(),允许开发者在这里执行一些清理工作。不过Destroyed状态之前不一定是Stopped状态,Paused状态的Activity也有被销毁的可能,因此最好将一些重要的工作(比如保存数据)置于onPause()而不是onDestroy()中。
Note:
实际上,Activity在Running状态并不一定意味着它的运算量大,它也有可能在干等着用户输入;同样,在Stopped状态也并不一定意味着它的运算量小。状态多是针对用户输入的响应(比如是否显示、是否拥有焦点)而言,而并不能代表它所做的工作内容。
Intent/意图
Intent是构件之间传递消息的一种机制。通过Intent可以显示一个Activity、启动或停止一个Service,也可以当作简单的广播。Intent是异步的,也就是说在发送时不需要阻塞等待它们响应完成。
Intent分为显式(explicit)和隐式(implicit)两种。显式的Intent要求发送者明确地指出接收者是谁;而隐式的Intent只要求发送者指明接收者属于哪一类别。比如某Activity可以发送一个“我要打开网页”的Intent,这时能够“打开网页”的某个应用程序即可收到这个Intent。
当多个应用程序对同一个Intent发生争抢的时候,系统会询问用户来决定选择哪个程序。用户也可以针对某Intent设置一个默认程序,就像你在桌面系统中安装了Firefox或者Chrome,即可选择把它设置为默认浏览器,从而替换掉原先的Internet Explorer或者Safari一样。
这样的消息机制允许用户自定义系统中的任何响应程序。比如你想换一个短信程序或者浏览器,只要把它们设成默认即可。
![](https://img.haomeiwen.com/i22406019/278d319749dbe3bd.png)
Service/服务
Service运行于后台,没有任何用户界面。它们就像是没有界面的Activity,适合于那些需要执行一段时间、但是不需要考虑如何显示的操作。比如,我们希望在移出音乐播放器的窗口之后,播放器依然可以播放歌曲。
Note:
不要将Android的Service与Linux的原生服务、服务器进程或者守护进程相混淆。它们都是操作系统的部件,要底层的多。
![](https://img.haomeiwen.com/i22406019/ed4e27a681b94886.png)
Service的生命周期要比Activity简单不少,要么启动,要么停止,如 图4.3 Service的生命周期 。同时,Service的生命周期受开发者影响较多,而受系统控制相对较少。因此身为开发者,我们需要小心对待Service,避免它无谓地浪费资源(比如CPU和电池)。
Note:
虽说Service运行于后台,但这并不是说它默认执行于另一个独立的线程中。如果一个Service需要执行一段耗时的操作(比如网络调用),依然需要程序员将它放在单独的线程中处理。否则,你的用户界面将响应不灵。一言以蔽之,Service默认与Activity在同一个线程中执行,而这个线程也被称作UI线程。
Content Providers/内容供应商
![](https://img.haomeiwen.com/i22406019/d5d4825dc5214a4e.png)
Content Provider是应用程序之间共享数据的接口。Android默认使每个应用程序都运行在沙盒(sandbox)里面,自己的数据与外面的程序完全隔离,要传递数据则必须依赖一些特定的接口。少量数据通过Intent传递即可,要传递大量的持久化数据就需要Content Provider了。为此,Content Provider提供了一套很好的按照CRUD原则设计的接口,以供不同程序之间共享数据,如 上图 Content Provider。
Android将这种机制应用到了方方面面。比如Contacts Provider专为不同的应用程序提供联系人数据,Settings Provider专为不同的应用程序提供系统的配置信息,Media Store为不同的应用程序提供多媒体信息(比如图片、音乐)的存储与分享,等等。
以联系人应用来说,应用程序本身并没有联系人数据,Contacts Provider则没有用户界面。如 上图从Contacts Provider获取数据的联系人应用,可见联系人应用通过Contacts Provider,从另一个应用程序中获取联系人数据的方法。
![](https://img.haomeiwen.com/i22406019/4ee5d1de46a88d9b.png)
把数据的存储与用户界面分离开来,这使得系统中各个部分之间的联系更加紧密,拆换起来也更加灵活。比如用户可以安装一个新的地址薄应用(Address Book App)替换掉原先的默认的联系人应用(Contacts App),同时仍希望使用原先的数据;或在主屏幕上面安装一个小部件,方便做些开关WiFi/蓝牙/GPS之类的系统设置。也有许多手机制造商(比如 HTC Sense)基于Content Provider机制,设计一套自己的软件集来提升用户体验。
Content Provider的接口很简单,那就是insert()、update()、delete()与query()。这与一个数据库的接口很相似,因此针对某数据库实现一个Content Provider当作代理也十分简单。话虽这么说,不过你该更喜欢使用现成的Content Provider,而不是自己造轮子。
Broadcast Receiver/广播接收器
![](https://img.haomeiwen.com/i22406019/1af04fa4f5cda25e.png)
Broadcast Receiver是Android整个系统中通用的发布/订阅机制(更确切地说,Observer模式)的实现。意思是接收者(Receiver)订阅一些事件,在事件发生时做出一定响应。
系统自身时时刻刻广播着一些事件。比如收到短信、来了一个电话、电量不足或者系统启动等事件发生的时候,它们都是通过广播传递给每个接收者。
对我们的Twitter应用而言,我们希望在系统启动时就启动UpdaterService来更新消息。为此,我可以订阅"系统已启动"的广播事件。
你也可以发送自己的广播。接收者可以是程序自身的其它部分,也可以是其它的程序。
Broadcast Receriver并无任何可见的界面,也并非常驻于内存中执行。它只会在事件发生时执行一段代码,做些启动一个Activity/Service之类的操作。
Application Context
到这里,我们已经对Activity、Service、Content Provider以及Broadcast Receiver有了大致的了解。可以说它们一起构成了整个应用程序,也可以说它们共同存在于同一个应用程序的上下文(Application Context)里面。
Application Context即当前应用程序所在的进程以及其运行环境,它为不同的构件所共享,因此我们可以通过它实现在不同的构件中共享数据和资源。
不管应用程序中首先启动的是哪个构件(Activity,Service还是其它),都会首先初始化Application Context。从此它的生存周期也就与整个应用程序保持一致,而与Activity或者某构件无关。我们可以通过Context.getApplicationContext()或者Activity.getApplication()获得它的引用。留意Activity与Service都是Context的子类,因此也就继承了它所有的方法。