原文地址:http://developer.android.com/guide/topics/fundamentals.html
Android应用程序使用Java语言编写。Android SDK工具将代码以及数据、资源文件编译并打包成Android包——一种以.apk为后缀名的存档文件。单个.apk文件中的所有代码被认为是一个应用程序,Android设备用这个.apk文件来安装该应用程序。
一旦被安装在某个Android设备中,每个Android应用都在它自己的安全沙箱环境中运行:
- Android操作系统是一个多用户的Linux操作系统,每个应用程序都是一个不同的用户。
- 默认状态下,系统分配给每个应用一个唯一的Linux用户ID(这个ID只能由系统使用,对应用来说是未知的)。系统为应用中的所有文件设置权限以便只有分配给应用的用户ID能够访问它们。
- 每个进程都有自己的虚拟机(VM),因此应用的代码能够相互隔离地运行。
- 默认状态下,每个应用都在自己的Linux进程中运行。当应用的任何一个组件需要被执行的时候,Android启动该进程;当它不再被需要或者系统必须为别的应用回收内存的时候,进程被关闭。
Android系统就照这样的方式实现“最小特权原则”。它意味着,在默认状态下,每个应用程序可以且仅可以访问它需要用来完成它的工作的组件。这就创建了一个非常安全的环境,在这之中应用程序不能访问系统没有授予它权限的部分。
不过,同样有方法能使应用能够与别的应用共享数据或者使应用能够访问系统的服务:
- 让两个应用程序共享相同的Linux用户ID是可能的,在这种情况下它们能够访问彼此的文件。为了节省系统资源,拥有相同用户ID的应用同样可以在相同的Linux进程中运行并且共享相同的VM(应用也必须署上相同的证书)。
- 应用程序可以请求访问诸如用户的通讯录、短信、外部存储器(SD卡)、照相机、蓝牙等等移动设备数据的权限。所有应用程序的权限必须由用户在安装时授予。
- 以上囊括了关于Android应用程序是如何在系统中存在的基础信息。接下来将为你介绍的是:
- 核心框架组件,用于定义应用程序。
- 清单文件,用于为应用程序声明组件和请求设备功能。
- 资源,独立于程序代码,允许应用程序为一系列的设备配置极大地优化它的表现。
应用的组件
应用组件是Android应用程序重要的构建模块。每个组件都是一个不同的节点,通过它系统可以进入到应用程序中。不是所有的组件对于用户来说都是实际的程序入口,其中一些组件彼此依赖,但是每个组件都作为一个实体而存在,扮演着特定的角色——每个组件都是定义应用程序整体表现的一个唯一的构建模块。
组件有四个不用的类别。每种类别为一个不同的目的服务,并且拥有一种不同的决定组件创建和销毁的生命周期。
以下是这四种应用组件:
Activity
Activity代表的是单个拥有用户界面的屏幕。例如,一个邮件程序可能有一个显示新的邮件列表的Activity,另一个编写邮件的Activity,以及一个读取邮件的Activity。虽然在邮件程序中Activity一起工作来形成一种紧密联系的用户体验,但是每个Activity都是独立于彼此的。因此,一个不同的应用能够启动这些Activity中的任何一个(如果邮件程序允许的话)。例如,相机程序能够启动邮件程序中编写新邮件的Activity来为用户分享照片。
Activity被作为Activity类的子类来实现。获取更多信息请参阅开发者指南Activities。
Service
Service是运行在后台的组件,它的作用是执行长期运行的操作或者调用远程进程。Service不提供用户界面。例如,用户在运行别的应用程序的同时,Service可以在后台播放音乐;或者,它可以通过网络获取数据而并不妨碍用户与别的Activity间的交互。另的组件,诸如Activity,可以启动Service使之运行或者将它绑定以便与它交互。
Service被作为Service类的子类来实现。获取更多信息请参阅开发者指南Services。
Content provider
Content provider管理一套共享的应用数据。数据可以被存储在文件系统、SQLite数据库、网络上或者其它任何应用程序能够访问到的持久存储区域中。通过Content provider,别的应用可以查询甚至修改数据(如果Content provider允许的话)。例如,Android系统提供了一个Content provider来管理用户的通讯录信息。这种情况下,任何具有适当权限的应用都能查询Content provider中的部分(如ContactsContract.Data)来读写某个特定人物的信息。
Content provider也可以用来读写应用的私有、不会共享的信息。例如,示例NotePad中就使用了一个Content provider来保存笔记。
Content provider被作为ContentProvider类的子类来实现,并且必须实现一套标准的API来使别的应用能够完成交互。获取更多信息请参阅开发者指南ContentProvider。
Broadcast receiver
Broadcast receiver是一个对系统中的广播通知做出响应的组件。许多广播来源于系统——例如,通知屏幕被关闭、电池电量不足或者图像被捕获。应用程序也可以发出广播——例如,通知其它的应用程序数据已经被下载到本地设备上并且可以使用了。虽然Broadcast receiver没有用户界面,但是它可以创建一个状态栏通知来提示用户广播事件发生了。不过,通常Broadcastreceiver只是别的组件的“出入口”,并且只被计划用在非常微量的工作上。例如,它可以初始化一个服务(Service)来处理基于某个事件的一些工作。
Broadcast receiver被作为BroadcastReceiver类的子类来实现,并且每个每个广播被作为一个Intent对象来传递。获取更多信息请参阅BroadcastReceiver类。
Android系统设计的一个独特的方面是任何一个应用程序都可以启动另一个程序的组件。例如,如果你想要用户使用照相机拍照,很可能会有另一个应用可以完成拍照这件事而你的应用程序能够直接使用它,而不是自己开发一个拍照的Activity。你并不需要包含或者甚至要链接相机应用程序的代码,而是可以仅仅启动相机程序中负责拍照的Activity。一旦拍照完成,照片将返回给你的程序供你使用。对于用户来说,这就像相机实际上就是你程序的一部分一样。
当系统启动一个组件时,它会为应用程序启动一个进程(如果应用当前没有运行),并且实例化组件所需要的所有的类。例如,如果你的应用程序启动了相机程序中负责拍照的Activity,该Activity在属于相机程序的进程中运行,而不是你的程序的进程中。所以,与别的大多数系统中运行的应用程序不同的是,Android应用没有单一的程序入口(例如,没有main()函数)。
由于通常的系统将每个应用程序运行在相互隔离的进程中,这种进程具有限制其它应用程序访问的文件权限,因此在这种系统中你的程序不能直接激活别的应用程序的组件。然而,Android系统能够做到。要激活别的程序的一个组件,你必须发送一条信息给系统,系统会区分你发送的Intent来启动特定的组件。之后系统会为你激活它。
激活组件
四种组件类型中的三种——Activity、Service以及Broadcastreceiver——可以被叫做Intent的异步消息激活。运行时,Intent把单个的组件彼此绑定在一起(你可以把Intent想成是向别的组件请求动作的信使),不论这个组件属不属于你。
Intent通过Intent对象创建,它定义了激活某个特定组件或某种特定组件类型的消息——Intent可以是显式的,个别情况下也可以是隐式的。
对于Activity和Service来说,Intent定义了需要执行的动作(例如,“查看”或者“发送”某物),还可能指定了数据运作的地址的URI(除了其它事情外,正在启动的组件是需要被获知的)。例如,Intent可以给某个Activity传达显示图像或者打开网页的请求。在某种情况下,你需要启动一个Activity来获取一个结果,这时,Activity也可以通过Intent来返回该结果(例如,你发送了一个Intent来让用户选择一条个人通讯录,然后返回给你——返回的Intent包含了一个指向被选中的通讯录的URI)。
对于Broadcastreceiver来说,Intent仅仅定义了广播的公告内容(例如,表示电池电量不足的广播只包含了一条表示“电量不足”的已知动作字符串)。
另外一个组件类型Contentprovider不能被Intent激活,它会被来自ContentResolver的请求激活。Contentresolver处理所有直接与Content provider进行的交互以便与provider进行交互的组件都调用ContentResolver对象的方法。这就在Content provider和需要请求信息的组件间留下了一个抽象层(出于安全考虑)。
激活每种类型的组件都有各自的方法:
- 你可以通过传递一个Intent给startActivity()或startActivityForResult()(当你想让Activity返回一个结果的时候)方法来启动一个Activity(或者给它一些新的事情做)。
- 你可以通过传递一个Intent给startService()方法来启动一个Service(或者给正在运行的Service新的指令)。或者,你可以通过传递一个Intent给bindService()来绑定到一个Service。
- 你可以通过传递一个Intent给像sendBroadcast()、sendOrderedBroadcast()或者sendStickyBroadcast()这样的方法来初始化一个广播。
- 你可以通过调用ContentResolver的query()方法来对contentprovider执行一次查询。
获取更多关于如何使用Intent的信息,参阅《Intent and Intent Filters》文档。更过关于激活特定组件的信息也可以在如下的文档中找到:Activities、Services、BroadcastReceiver以及ContentProvider。
清单文件
在Android系统能够启动某个程序组件之前,系统必须通过读取应用的AndroidManifest.xml文件(“清单”文件)来得知该组件存在。你的应用程序必须在该文件中声明所有的组件,而且该文件必须放在程序项目的根路径下面。
除了声明应用的组件外,清单还要做许多事情,诸如:
- 确定应用需要的任何用户权限,比如互联网的访问权限或者用户通讯录的读取访问权限。
- 声明应用需要的最低API级别,该级别基于应用所使用的API。
- 声明应用使用或者需要的软硬件功能特性,诸如相机、蓝牙或者触屏。
- 应用需要链入的API库(非Android框架API),诸如GoogleMaps library。
- 以及其它更多内容。
声明组件
清单的首要任务是提醒系统应用所需要的组件。例如,清单文件可以像下面这样声明一个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>
在<application>元素中,android:icon属性指出了标识该应用的图标资源。
在<activity>元素中,android:name属性指定了Activity子类的全称,android:label属性指定了用来作为该Activity的用户可见的标签的字符串。
你必须按照如下方式声明应用的所有组件:
- <activity>元素表示Activity
- <service>元素表示Service
- <receiver>元素表示Broadcastreceiver
- <provider>元素表示Contentprovider
你包含在你的源文件中但是没在清单中声明的Activity、Service以及Content provider对系统来说是不可见的,而且通常来说不能运行。不过,Broadcast receiver既可以在清单中声明,也可以在代码中动态地创建(以BroadcReceiver对象的形式)然后通过调用registerReceiver()方法来向系统注册。
获取更过关于如何组织应用程序清单文件的信息,参阅TheAndroidManifest.xml File文档。
声明组件性能
如前所述,激活组件的时候你可以使用Intent来启动Activity、Service以及Broadcast receiver。你可以通过在Intent中显式地对目标组件命名(使用组件的类名)来办到这一点。不过,Intent的真正力量存在于Intent行为的概念中。运用Intent行为,你可以简单地描述你想要执行的动作的类型(以及你想要在此之上执行动作的数据),而且可以允许系统发现设备上能够执行该动作的组件并且启动它。如果Intent中描述了多个能够执行该行为的组件,那么用户要选择去使用哪个。
系统通过比较由本设备上的其它应用的清单文件所提供的Intent拦截器所捕获的Intent来确定响应Intent的组件。
当你在应用的清单中声明一个组件的时候,你可以选择包含声明该组件性能的Intent拦截器使之可以响应从别的应用来的Intent。你可以通过给组件的声明元素添加<intent-filter>元素作为子元素来给你的组件声明Intent拦截器。
例如,具有编写新邮件Activity功能的邮件程序可以在它的清单入口中声明一个Intent拦截器来响应“发送”Intent(发送邮件)。其后应用程序中的Activity创建一个包含“发送”行为(ACTION_SEND)的Intent,系统会把它匹配给邮件程序的“发送”Activity并且当你在startActivity()方法中调用Intent时启用它。
获取更多关于创建Intent拦截器的信息,参阅《Intents and Intent Filters》文档
声明应用的需求
Android能够驱动一系列的设备,而且它们不都拥有相同的功能或者性能。为了防止应用在安装到设备上的时候缺失所需要的某些功能,通过在清单文件中声明软硬件需求来为应用所支持的设备的种类清楚地做出一个概括是非常重要的。这些声明中的大多数只是单纯的信息,而且系统也不会去读取它们,但是外部的服务,诸如安卓应用商店,会读取它们以便用户在搜索适合他们的设备的应用程序的时候提供相应的筛选结果。
例如,如果你的应用需要用到相机并且使用了Android 2.1版本的API(APILevel 7),你应该在清单文件中把这些作为需求来声明。那样的话,没有相机以及Android系统低于2.1版本的设备将不能从安卓应用商店中安装你的程序。
不过,你也可以声明你的应用使用相机但并不需要它。这种情况下,应用程序必须在运行时检查设备是否具有相机,若没有则务必禁用要用到相机的相关功能。
当设计、开发应用程序的时候请好好考虑以下的一些重要的设备特征:
屏幕尺寸和分辨率
为了能根据屏幕的类型进行分类,Android给每部设备定义了两个特征:屏幕尺寸(屏幕的物理大小)和屏幕分辨率(屏幕的物理像素密度,或者dpi——每英寸的点数)。为了简化屏幕配置的所有不同类型,Android系统将它们编入可选的组中以使它们更容易被定位。
屏幕尺寸包括:小、普通、大以及特大。
屏幕分辨率包括:低分辨率、中分辨率、高分辨率和极高分辨率。
默认状态下,应用程序能够兼容所有的屏幕尺寸和分辨率,这是因为Android系统会对UI布局和图像资源做出适当的调整。不过,你应该使用可替代的布局资源,给某种特定的屏幕尺寸制定专门的布局,给某种特定的分辨率提供专门的图像,并且在清单文件中用<support-screens>元素声明你的应用程序究竟支持那些屏幕类型。
获取更多信息,参阅《支持多种屏幕》文档。
输入配置
许多设备都提供了不同的用户输入机制,诸如硬件键盘、轨迹球或者五方向导航键。如果你的应用需要一种特定的输入硬件,那么你应该用<uses-configuration>元素在清单中声明它。不过,应用需要某种特定的输入配置是很罕见的。
设备功能
在某个Android设备中会有许多存在的或者不存在的软硬件功能,诸如相机、光传感器、蓝牙、特定版本的OpenGL或者触屏的保真度。你永远不能假定某种功能存在于所有的Android设备上(标准Android库除外),因此你需要用<uses-feature>元素来声明任何需要用到应用程序中去的功能特性。
平台版本
不同的Android设备常常运行在不同版本的Android平台之上,诸如Android1.6或者Android 2.3。每个后续的版本通常包含之前版本中所不具备的新增的API。为了表明哪一套API可用,每个平台版本指定了一个APILevel(例如,Android 1.0是API Level 1,Android 2.3是API Level 9)。如果你要使用任何在1.0版本之后添加的API,你需要用<uses-sdk>元素来声明引入所需API的最低API Level。
给应用程序声明所有这些需求是非常重要的,这是因为,当你在安卓应用商店中描述应用的时候,商店使用这些声明来给每部设备筛选可用的应用程序。因此,你的应用可以只对那么满足应用需求的设备开放。
获取更多关于安卓应用商店如何基于需求筛选应用的信息,参阅《Market Filters》文档。
应用的资源
Android应用程序不仅仅由代码组成——它还需要独立于源代码的资源,诸如图像、音频文件以及与应用的视觉表现相关的事物。例如,你需要用XML文件定义动画、菜单、风格、颜色以及Activity中用户界面的布局。使用资源使更新应用的各种特征更加简单而不用去修改代码,并且——通过提供一套可替代的资源——使开发者能够优化应用中的一系列设备配置(诸如不同的语言、屏幕尺寸)。
对于每个你包含中Android项目中的资源,SDK编连工具都给它定义了一个唯一的整数ID,你可以在应用的代码或者XML文件的其它资源中使用它来引用该资源。例如,如果应用程序中包含了一个名叫logo.png的图像文件(保存在res/drawable/目录下),SDK工具就会为其生成一个名叫R.drawable.logo的资源ID,你可以用它来引用图像并且把图像插入到你的用户界面中。
将提供的资源和源代码分隔开的其中一个很重要的方面就是赋予你给不同的设备配置提供可替代的资源的能力。例如,通过在XML文件中定义UI的字符串,你可以将这些字符串翻译成别的语言并保存在另一个文件中。之后,根据你给资源路径名(诸如表示法语字符串的res/values-fr/)以及用户语言设置追加的语言修饰符,Android系统就可以为UI采用适当的语言字符串了。
对于可替代的资源,Android支持许多不同的修饰符。修饰符是一种包含在资源路径名中用来定义资源会被哪些配置使用的短字符串。另一个例子是,根据设备屏幕的朝向和尺寸,常常要为Activity建立不同的布局。例如,当屏幕处于垂直朝向(纵向)的时候,你可能想要一个按钮垂直对齐的布局;但是当屏幕处于水平朝向(横向)的时候,按钮应该水平对齐。要根据朝向改变布局,你可以定义两种不同的布局文件并且采用合适的修饰符来改写每个布局文件的路径名称。之后,系统就可以根据当前的设备朝向自动地采用恰当的布局了。
获取更多关于可以在应用中包含的不用种类的资源以及如何为不同的设备配置创建可替代的资源的信息,参阅开发者指南《应用的资源》。