QuickView
Android软件是由一个或多个应用程序组件组成的,这些组件有activities、services、content provider和broadcast receivers。
每一个组件在整个程序的behavior中扮演着重要角色,而且每一个组件都可以被单独的激活(activated),甚至可以由其他的程序激活。
在manifest文件中必须要声明程序中所有的组件和必要的条件,比如需要的最低Android平台版本或者硬件配置。
不需要编码的程序资源(图片、字符串、布局文件等)必须要为不同的设备配置(比如为不同的语言准备不同的字符串,为不同的屏幕尺寸准备不同的布局文件)准备好。
Android软件是用Java编程语言编写的。Android SDK tools对代码和资源与数据文件一起编译成一个以.apk为后缀的Android包。在一个单独的.apk文件中的所有代码被认为一个程序,并且是一个Android系统的设备可以安装的程序。
一旦程序在一个设备上被安装,每一个Android软件都存在于自己的安全沙箱中:
Android系统是一个多用户的Linux系统,系统中的每一个程序都是一个不同的用户。
默认情况下,系统会为每一个程序分配一个唯一的Linux用户ID(这个ID只是为系统所使用,并不为程序所知)。系统为程序中的文件设定一定的权限,这样只有相同用户ID的程序可以访问那些文件。
Android系统中,每一个进程都有自己的虚拟机(VM),这样每一个程序的代码都单独的运行。
默认情况下,每一个程序都运行在自己的Linux进程中。当程序中的任意一个组件被执行的时候,Android系统会启动组件所在的程序的进程,当这个系统不再需要这个进程,且为了为其他程序节省内存的时候,便会关闭这个进程。
通过这种方式,Android系统便实现了least privilege准则。那就是,在默认情况下,每一个程序只能访问有工作要做的组件。这就创建了一种非常安全的环境,在这种环境中,程序不能访问没有授予权限访问的系统的其他部分。
然而,在Android系统中还设有办法去和其他的程序共享数据和去访问系统服务:
只要程序有相同的Linux用户ID,则他们之间就是可以相互访问对方的文件的,而让两个程序具有相同的Linux用户ID是可能的。为了保存系统资源,具有相同用户ID的程序甚至可以在相同的Linux继承中运行,并共享同一个VM(当然还有个条件就是这两个程序必须有相同的证书(cetificate))。
一个程序可以要求一定的权限来访问设备的数据,比如用户的联系人、短信息、SD卡(the mountable storage)、照相机、蓝牙等等。这些所有的权限必须在用户安装的时候被用户所承认。
上面讲了关于Android软件是如何在系统中存在的基本内容。剩下的内容将介绍的内容包括:定义你程序的核心框架组件;在manifest文件中为你的程序声明你的组件和设备必须具备的特点;独立于程序代码的资源,并为不同的设备最优化你的代码。
程序组件(Application Component)
程序组件是构成Android程序的基本元素。每一个组件都是系统可以进入你的程序的入口点。对用户来说并不是所有的组件都是入口点,但是每一个组件都已自己的实体存在,并扮演一个特定的角色,那就是每一个都是可以帮助你定义你的程序特征的元素。
在Android系统中,共有四种 不同的组件。每一个组件都有自己独特的作用和不同的生命周期,这个生命周期就是定义了组件是如何创建 和被销毁的一个过程。
Activities
一个Activity就是一个拥有用户界面的屏幕。比如,一个email程序可能包含一个用于显示新邮件的activity,一个用于编写邮件的activity,一个用于读取邮件的activity。虽然这些activities work together,让用户形成了一个连续的用户体验,但是每一个activity都是独立于其他activity的。 这样,只要这个email程序允许,一个不同的程序就可以启动这些activities中的一个activity。比如,为了让用户分享一张图片,一个camera程序就可以启动一个编写新邮件的activity。
一个activity扩展子Activity类,你可以通过Activities developer guide了解更多。
Services
一个service是一个为了执行一个长时间的操作或者为远程进程共多的而在后台运行的组件。一个service并没有用户界面。比如,当用户执行不同程序的时候,一个用于播放音乐的service可以在后台一直运行,或者为了不堵塞用户与activity继续交互时,一个service可以用于从互联网上下载数据。一个其他的组件,比如activity,可以启动这个组件,或者为了与这个组件进行交互来绑定这个service。
一个service应该扩展子Service类,并且你可以通过Services developer guide来学习更多的关于service的知识。
Content providers
一个content provider主要用于共享数据。你可以把数据存在文件中、一个SQLite数据库中、互联网上、或者其他的你的程序可以访问的persistent storage location。通过一个content provider,其他的程序可以查询甚至修改数据(只要这个content provider允许)。比如,Android系统就为了处理用户的联系人信息而提供了一个content provider。这样的话,拥有适当权限的任何程序都可以查询content provider共享的数据(比如ContactsContract.Data),来读和写 关于一个特定联系人的信息。
Content provider对于你的程序私有的数据的读写也是很有用的。比如,那个Note Pad例子就是使用content provider来保存笔记。
Broadcast receiver
一个broadcast receiver是一个可以对在整个系统中传播的广播做出响应的组件。许多像屏幕关闭、电池电量太少、拍了一张照片等这种广播是有系统发出的。一个程序一个可以发送广播,比如为了让其他的程序知道一些数据已经被下载完成并可以被使用时,就可以发送一个广播。虽然broadcast receiver并没有一个用户界面,但是它们会创建一个status bar notification来告知用户一个广播时间发生了。更常见的情形是,一个broadcast receiver只是用与其他组件进行通信,并只做很少的工作。比如,一个broadcast receiver可能只是启动一个service。
Android系统设计的一个独特方面是任何的一个程序都可以启动另一程序的组件。比如,你想让你的程序可以使用照相机拍照,如果已经有了实现这种功能的程序并且你你的程序能使用它(有权限),那么你就没有再要再写一个新的Activity来实现这个功能。你的程序不需要包含或者链接这个拍照程序。相反,你只需要在你的程序中打开这个拍照程序中的实现拍照功能的Activity。当拍完之后,拍好的照片甚至会自动返回给你的程序。者对于用户来说,就好像是想拍照功能的程序就是你的这个程序的一部分一样。
当系统启动一个组件之后,如果这个组件所在的程序之前没有运行的话,系统会自动开始这个程序的进程,并初始化这个组件所需要的相关类。比如,你的程序开启了一个拍照功能程序的Activity,这时系统会启动这个Activity所在的程序,所以这个Activity运行在拍照功能的程序当中,而不是在你的程序中。所以,不像其他操作系统的中的程序一样,Android程序没有一个单独的入口点(比如没有我们常见的main()函数)。
因为系统中的程序运行在自己的独立进程中,并且程序中的文件都有自己的限制其他程序访问的权限,所以,你的程序不能直接激活其他程序中的组件。但是Android系统就可以。具体是这样的实现的,为了激活(activate)其他程序中的组件,你必须向系统发送一个消息来详细说明你要启动其他组件的意图,这样系统才会为你激活这个组件。
激活组件(Activating Components)
四大组件中的三个组件——activities、services和broadcast receiver——是由一种叫intent的异步消息来激活的。这些intents在运行时(runtime)将这些属于你的程序或不同程序的单独的组件绑定在一起(bind),你可以把这些intents看作是需要其他组件的action的messengers。
一个intent就是一个Intent对象,这个intent定义了一种可以激活(activate)某个特定组件或者某种特定类型的组件,这两种情况分别对应两种intent的定义方式或者显示的或者隐式的。
对于activities和services,一个intent定义了要执行的操作(action)(比如,要“view”或者“send”什么)和要操作的数据的URI。比如,一个intent可能会为一个activity传递一个请求来展示一张图片或者打开一个网页。有时,你可以启动一个activity来得到返回的结果,在这个例子中这个activity的返回的结果也是一个Intent(比如,你可以发送一个intent让用户选择一个personal contact并返回给你——这个返回的intent就包含了一个指向用户选择的联系人的URI)。(关于activity和service的启动方式,下面将介绍。)
对于广播接收者来说,intent只是简单的定义了要广播的内容(比如,一个用以表明电池电量很低的广播仅包含了一个表明电池电量很低的字符串)。
最后一种组件类型content provider并不是由intent来激活的(activate)。而是由接收到ContentResolver的请求时激活的。。。。
它们都各自有自己的方法来激活相应的组件:
你可以通过传递一个Intent给startActivity()或startActivityForResult()启动一个activity(或者给他一些新的要做的内容)。使用startActivityForResult()你将得到一个返回结果。
你可以通过传递一个Intent给startService()来start一个service(或者给一个正在运行的service一些新的指令(instructions))。或者你可以通过把一个Intent传递给bindService()来绑定一个service。
你可以通过传递一个Intent给诸如sendBroadcast()、sendOrderedBroadcast()或者sendStickyBroadcast()等方法来初始化一个广播。
你可以通过调用ContentResolver的query()方法来执行一次content provider的查询操作。
更多的关于intent的内容,可以参看文档中的Intents and Intent Filters。更多的关于激活特定组件的内容可以参看文档中的:Activities、Services、BroadcastReceiver、Content Providers。
关于Manifest文件
在Android系统可以启动一个应用程序组件之前,Android系统必须通过读取这个程序的AndroidManifest.xml(即manifest文件)文件来确定要启动的组件存在。你的程序必须在这个manifest文件声明用到的所有的组件,并且这个manifest文件必须在项目的根目录下。
另外,这个manifest文件还声明一些其他的东西,比如:
(1)确定这个程序需要的所有权限,比如Internet访问权限或者读取用户联系人权限。
(2)声明这个运行这个程序所需要的最低API版本,这个可以根据开发该程序所使用的API版本。
(3)声明该程序所需要的硬件或软件特征(features),比如照相机、蓝牙服务或者多点触屏。
(4)声明该程序需要链接(link against)的API库(不是Andorid的framework APIs),比如Google Maps library。
(5)等等。
组件声明(Declaring components)
Manifest文件的首要任务就是通知系统关于程序中要使用的组件。比如,一个manifest文件可以用如下的方式来声明一个activity:
<?xml version="1.0" encoding="utf-8"?> <manifest ... > <application android:icon="@drawable/app_icon.png" ... > <activity android:name="com.example.project.ExampleActivity" android:label="@string/example_label" ... > </activity> ... </application> </manifest>
在<activity>元素中,android:name属性用于确定这个扩展自Activity的子类的全路径名,android:label属性用于标示这个activity的对于用户可见的label。
你必须要用以下方式来声明你的程序组件:
activities:<activity>标签
services:<service>标签
broadcast receiver:<receiver>标签
content providers:<provider>标签
如果程序中用到activities、services和content providers,你没有在manifest文件中声明,那么这些组件将不会被系统知道,结果就是你的程序不能运行。然而,broadcast receiver既可以在manifest文件中声明也可以在代码中动态创建(BroadcastReceiver),并通过调用registerReceiver()在系统中注册。
更多关于怎样为你的程序构建manifest文件,请参看文档The AndroidManifes.xml文件。
声明组件的能力(Declaring component capabilities)
正如在上面Activating Components中讨论的那样,你可以使用一个Intent来启动activities、services和broadcast receiver。你可以通过在intent中注明目标组件的名字(使用的是组件的类名)来显示的启动组件。然而,intents真正强大的地方在与关于intent的actions的概念。通过intent的actions,你可以简单的描述你要执行的操作的类型(并且可以有选择的描述你要处理的数据),可以允许系统在device中找到这个组件并启动它。如果有多个组件可以执行intent中描述的action,这时用户就可以选择一个来执行。
系统可以识别能对某intent做出反应的方式是通过将接收到的intent和设备中其他程序的manifest文件的intent filters进行比较实现的。
当你在程序的manifest文件中声明一个组件之后,你可以有选择包含intent filters,这些intent filters表明了组件对接收自其他程序的intent做出反应的能力(capabilities)。你可以通过添加一个<intent-filter>元素作为a child of the component's declaration element来为你的程序声明一个intent filter。
比如,在一个邮件程序中的一个activity可以编写新的邮件,这样的话你就需要在manifest文件中来声明一个intent filter来对“发送”intent响应(为了发送邮件)。这样,在你的程序中,一个activity就可以创建一个发送intent(ACTION_SEND),这样当你调用startaActivity()时,系统就会匹配邮件程序中的发送activity并启动它。
更多关于创建intent filters的内容,可以参看Intents and Intent Filter文档。
声明运行程序所需的条件(Declaring application requirements)
Andorid系统可以支持很多不同的设备,并且这些设备的性能特征并不相同。为了防止你的程序被安装在不能正常运行你的程序的较低android系统版本上,通过在manifest文件中声明你的程序支持的设备和软件,便变得尤其重要起来。大多数的这些声明仅是一些信息,而系统并不会读取它们,但是其他的服务比如Android Market却会阅读这些声明来帮助通过通过自己的设备搜索软件的用户过滤软件。
比如,你的程序需要照相机,并且使用的Android2.1的APIs,那么你就必须在你的manifest文件中声明这些需要。这样的话,在Android Market上,没有照相机或者Android系统版本低于2.1将不能安装你的程序。
然而,如果你的程序不需要照相机,你仍可以声明你需要照相机。这种情况下,你的程序必须在运行时做一下检查,来检查这个设备是否含有照相机,如果没有照相机可用,则系统将会使使用照相机的相关程序不能用。
下面是一些你在设计和开发你的程序时,必须要考虑的关于设备的一些重要方面:
屏幕大小和分辨率(Screen size and density)
为了根据屏幕的类型进行分类,Android定义了两个特征:屏幕大小和分辨率。
屏幕尺寸有: small, normal, large, and extra large
屏幕分辨率类型有:low density, medium density, high density, and extra high density
默认情况下,你的程序可以兼容所有的屏幕尺寸和分辨率,因为Android系统对你的程序的UI布局和image资源做了适当的调整。然而,you should create specialized layouts for certain screen sizes and provide specialized images for certain densities, using alternative layout resources, and by declaring in your manifest exactly which screen sizes your application supports with the <supports-screens> element.
输入方式(Input configurations)
很多设备有不同类型的输入方式,比如a hardware keyboard, a trackball, or a five-way navigation pad。如果你的程序需要某特定形式的输入方式,则你必须在manifes文件中使用<uses-configuration>标签来声明。不过这种情况是比较少的。
设备配置(Device features):有许多硬件或软件并不全在Android系统的设备上,比如,一个照相机、光线传感器、特定版本的OpenGL,或者屏幕的保真度(fidelity)。你在任何条件下都不能假定Android设备具备某种特性(feature)(当然得除掉Android标准库的情况),所以如果你的程序使用了某feature,则你必须使用<uses-feature>标签来声明。
平台版本(Device features):
程序资源文件(Application Resources)
一个Android程序不只是由代码组成的,它还需要独立于源码的资源,比如图片、音频文件、和程序相关的可使用的任何资源。比如,你应该定义动画(animations)、菜单(menus)、样式(styles)、颜色(colors)、布局文件。使用这些资源可以在不需要改动代码的情况下,通过提供各种可用的资源(by providing sets of alternative resources),来更容易的更新你的程序(update characteristics of your application),让你为不同配置(比如不同语言或者屏幕尺寸)的Android设备来最优化你的程序。
对于你在程序中包含的每一个资源,SDK build tools都会定义一个唯一的整形ID,你可以使用这个ID来引用你的代码中或在XML中定义的资源。比如,你的程序包含一张名为logo.png图片资源(保存在res/drawable目录下),这是SDK tools便会生成一个叫R.drawable.logo的资源ID,这样你就可以在用户接口(user interface)中通过使用这个ID来引用这张图片。
Android系统提供这种独立于代码的资源的一个重要方面就是可以让你为不同配置的设配都提供可用的资源(provide alternative resources)。比如,通过在XML文件中定义UI字符串,你就可以把这些字符串翻译成其他语言,并把这些字符串保存在单独的文件中。这样,根据你添加的资源目录的名字(比如使用res/values-fr表示法语字符串值)和用户的语言设置,Android系统便会向你的UI中提供适合用户的语言。
对于你提供的各种可用资源,Android系统支持多种不同的限定词(qualifiers)。这个限定词是你根据这些资源可以被哪些设备配置(configuration)而以你的资源目录的名义定义的(in the name of your resource directories)一个比较短的字符串。另一例子是,你应该根据设备的平米方向和尺寸来为你的activities创建不同的布局文件。比如,如果设备的屏幕是竖屏的(in portrait orientation),这是你可能需要一个buttons是vertical的布局,如果屏幕是宽屏时(in landscape orientation),布局中的buttons就应该水平放齐。为了根据设备屏幕方向二改变布局,你可以定义两种不同的布局,并根据布局文件的目录名来使用合适的限定词。这样系统便会根据当期设备的方向来自动的提供合适的布局文件。
更多的关于你的程序中可以包含的资源和如何为不同配置的设备提供可用的资源,可以参看Appication Resources developer guide。