Application Fundamentals
英文原文:http://developer.android.com/guide/components/fundamentals.html
采集(更新)日期:2015-05-11
搬迁自原博客:http://blog.sina.com.cn/s/blog_48d491300101h41p.html
Android 应用程序是用 Java 语言编写的。Android SDK 工具软件会把代码—连同数据、资源文件—一起编译并放入 APK 中:
Android 包中,这是个后缀为.apk
的打包文件。
APK 文件包含了 Android 应用程序所需的全部内容,同时也是在 Android 设备上安装该应用所用到的安装文件。
安装完成后,每个 Android 应用程序就独立运行于各自的安全沙盒(sandbox)中:
- Android 操作系统是一个多用户的 Linux 系统,其中的每个应用程序都是一个独立的用户。
- 默认情况下,系统将给每个应用程序赋予一个唯一的 Linux 用户 ID(该 ID 仅供系统使用,对于应用程序是不可见的)。 系统将对应用程序中的所有文件分配权限,只有赋予该应用程序的用户 ID 才能访问这些文件。
- 每个进程都拥有自己的虚拟机(VM),因此不同应用程序的代码是相互独立运行的。
- 默认情况下,每个应用程序运行于自己的 Linux 进程中。 每当需要运行应用程序的某个组件时,Android 就会启动进程,并在不再需要时或必须为其他应用程序腾出内存时关闭进程。
这样,Android 系统实现了最小权限原则。也就是说,每个应用程序默认只访问必要的组件。 这就建立了一种非常安全的环境,使得应用程序无法访问未授权的系统内容。
不过,应用程序还是有很多方法与其他应用程序共享数据或访问系统服务的:
- 可以让两个应用程序共享同一个 Linux 用户 ID,这样它们就可以相互访问文件了。 为了节省系统开销,用户 ID 相同的应用程序还可以运行于同一个 Linux 进程中,并且共享同一个 VM(这些应用程序必须由同一个证书签名)。
- 应用程序可以申请访问设备数据的权限,诸如用户通讯录、短信、可挂载存储(SD卡)、摄像头、蓝牙等等。 应用程序的全部权限都必须在安装时经过用户授权。
上面简述了 Android 应用程序是如何在系统中运行的。本文剩余部分将介绍:
- 建立应用程序所需的主要组件。
- 用于声明组件和所需硬件的 Manifest 文件。
- 与代码分离的资源,以便应用程序可以根据硬件配置优化运行方式。
应用程序组件
组件(Component)是搭建 Android 应用程序的基础模块。 每个组件都是一个独立的入口,系统可以通过它们进入应用程序。 不是每个组件都是可供用户使用的入口,有些组件是相互依存的,但每个组件都运行为一个实体并发挥各自的作用—每个组件都是唯一的应用程序模块,为应用程序的整体表现出力。
应用程序组件分为四种类型。每类组件实现不同的目标,拥有各自从创建到消亡的生命周期。
下面介绍这四种类型的应用程序组件:
- Activity
- Activity代表一个包含了用户界面的屏幕(窗口)。 比如,一个 Email 应用程序可能会包含一个显示未读邮件列表的 Activity、一个撰写邮件的 Activity 及一个阅读邮件的 Activity。 虽然该 Email 应用程序由多个 Activity 一起构成了完整的用户体验,但是每个 Activity 都是相互独立的。 因此,另一个应用程序可以启动其中任意一个 Activity(如果该 Email 应用程序允许的话)。 例如,为了能让用户分享照片,某个拍照应用可以启动该 Email 应用程序的 Activity 来撰写一封新邮件。
- 服务
- Service 是一种后台运行的组件,用于执行运行时间较长的操作,或是为远程进程提供服务。服务不提供用户界面。 例如:当用户使用其他应用程序时,服务可以在后台播放音乐,或者可以通过网络下载数据,这些都不需要用 Activity 与用户进行交互。 Activity 之类的其他组件可以启动并运行服务,也可以与之绑定以便交互。
- Content Provider
- Content Provider 管理着某个可共享的应用程序数据集。
数据可以存放在文件系统、SQLite 数据库、Web,或其他任何应用程序可访问的持久性存储中。
其他应用程序可以通过 Content Provider 查询甚至修改数据(如果 Content Provider 允许的话)。
例如,Android 系统就提供了一个管理用户通讯录信息的 Content Provider。
因此,任何拥有相应权限的应用程序都可以查询该 Content Provider 里的数据(比如
ContactsContract.Data
), 以便读写某个联系人的信息。对于读写应用程序的私有数据而言,Content Provider 也很有用。 比如,例程 Note Pad 就使用了一个 Content Provider 来保存笔记。
Content Provider 实例化为
ContentProvider
的子类,且必须实现一组标准的 API,以便其他应用程序能提交事务。 详情请参阅开发指南的 Content Providers 部分。 - 广播接收器
- 广播接收器 是一种响应系统级广播的组件。
很多广播都来源于系统 — 比如屏幕已关闭、电池电量低、拍了一张照片等。
应用程序也可以发起广播 — 比如通知另一个程序所需数据已经下载到本地设备并已可用。
尽管广播接收器不显示用户界面,但在广播事件发生时它可以
创建一条状态栏通知来通知用户。
不过,更多时候广播接收器仅仅是充当其他组件的“入口”,只完成很少量的工作。
比如,它可以初始化一个服务以便完成某个事件对应的任务。
广播接收器实例化为
BroadcastReceiver
对象的子类,每条被分发的广播都是一个Intent
对象。详见BroadcastReceiver
类。
Android 系统有一条独特的设计,就是任何应用程序都能启动其他应用程序的组件。 例如,假设需要使用摄像头拍照,而其他应用程序已经实现了这一功能,则可以直接使用,而不需要再自行开发一个 Activity 来完成拍照功能。 这不需要包含摄像头应用的代码,甚至都不需要链接代码,只要启动摄像头应用中的拍照 Activity 即可。 拍摄完成后,照片甚至都会直接返回给调用方应用以便使用。 对于用户而言,摄像头应用就像是调用方应用中的一部分一样。
当启动一个组件时,系统会启动相关应用程序的进程(如果该程序还未运行的话),并且实例化组件所需的类。
例如,当启动了摄像头应用中的拍照 Activity 时,该 Activity 运行于摄像头应用的进程中,而不是在调用方应用的进程中。
因此,与其他大多数系统不同,Android 应用程序不存在一个单一的入口点(比如没有main()
函数)。
因为系统把每个应用程序都放入相互独立的进程中运行,文件权限就限制了对其他应用的访问,所以不可能直接启动其他应用的组件。 但是 Android 系统可以启动其他应用程序的组件。因此,如果要激活其他应用的组件,必须向系统发送一条消息,其中指定了要激活某个组件的 Intent。 然后,系统就会启动该组件。
启动组件
在四种类型的组件中,有三种 — Activity、服务和广播接收器 — 都是由名为 Intent 的异步消息启动的。 在运行时, Intent 与某个组件相互绑定(可以将它们视为其他组件发出的执行某个操作的请求消息),这与组件属于哪个应用没有关系。
Intent 是用 Intent
对象创建的,定义了一个激活某个或某类组件(分别对应显式或隐式的 Intent )的消息
。
对于 Activity 和服务而言,Intent 定义了所要执行的操作(比如:“查看”或“发送”),并可以指定数据的 URI(被启动的组件可能会需要知道)
例如,某 Intent 可能会发送一个请求,要让某个 Activity 显示一张图片或者打开一个网页。
有时需要启动一个 Activity 并接收其结果,这时 Activity 还需要在
Intent
中返回结果(例如,发送一个 Intent 让用户选取某个联系人并返回数据 — 返回的 Intent 包含了一个指向选中联系人数据的 URI)。
对于广播接收器而言,Intent 只是简单地定义了要广播的消息 (例如,一条表示电量不足的广播可以仅包含一个“电量不足”字符串。
剩下的一类组件 Content Provider,不是由 Intent 启动的。而是由
ContentResolver
发出的请求来激活的。
Content Resolver 负责处理 Content Provider 所有的事务,因此需要执行事务的组件不需要去调用
ContentResolver
对象的方法。这样就在 Content Provider 和请求数据的组件之间建立一个抽象层(出于安全考虑)。
每种组件都有各自的激活方法:
-
通过把
Intent
传入startActivity()
或startActivityForResult()
(需要返回结果时),可以启动某个 Activity (或者让它执行某些新任务)。 -
通过把
Intent
传入startService()
, 你可以启动某个服务(或者给已有的服务发布新的指令)。或者可以把Intent
传入bindService()
, 以绑定某个服务。 -
通过给
sendBroadcast()
、sendOrderedBroadcast()
或sendStickyBroadcast()
之类的方法传入一个Intent
,你可以初始化一条广播消息。 -
通过调用
ContentResolver
的query()
方法,你可以在 Content Provider 上执行一条查询。
关于 Intent 的更多信息,请参阅文档
Intents 和 Intent 过滤器。
关于激活组件的更多信息,在以下文档中也有涉及:
Activity、
服务、
BroadcastReceiver
和
Content Provider。
Manifest 文件
在启动一个应用程序组件之前,Android 系统必须知道组件是否存在,这是通过读取应用程序的 AndroidManifest.xml
文件(“Manifest”文件)来实现的。
该文件必须位于项目的根目录,应用程序必须在该文件中声明所有的组件。
除了声明应用程序组件之外,Manifest 文件还有其他一些用途,比如:
- 标记应用程序所需的用户权限,如访问 Internet 或 读写用户通讯录
- 声明应用程序需要的最低版本 API Level ,应用程序依赖于该版本的 API。
- 声明应用程序的的硬件和软件需求,诸如:摄像头、蓝牙服务和多点触屏。
- 应用程序需要链接的 API 库(Android 系统 API 之外的库),如: Google Maps library。
- 其他
声明组件
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>
在 <application>
元素中,android:icon
属性指向了一个标识该应用程序的图标资源,。
在 <activity>
元素中, android:name
属性设定了 Activity
子类的完全限定(fully qualified)类名, android:label
属性设定了一个用作 Activity 标题的字符串。
必须按照以下规则声明应用程序的全部组件:
- 用于 Activity 的
<activity>
元素 - 用于服务的
<service>
元素 - 用于广播接收器的
<receiver>
元素 - 用于 Content Provider 的
<provider>
元素
如果你的代码中用到了某些 Activity、服务和 Content Provider,但没有在 Manifest 文件中声明,那么系统是无视这些组件的,因此它们也就无法生效。 不过,广播接收器既可以在 Manifest 文件中声明,也可以用代码动态创建( 创建为 BroadcastReceiver
对象)并用 registerReceiver()
进行注册。
关于构建 Manifest 文件的更多信息,请参阅文档 AndroidManifest.xml 文件
声明组件的兼容性
正如 启动组件 部分所述,可以用一个 Intent
来启动Activity、服务和广播接收器。可以在 Intent 中使用显式命名的目标组件(用组件的类名)启动 Intent。 不过,Intent 真正的强大之处在于 隐式 Intent 的理念。 隐式 Intent 只要说明需执行的操作类型即可(还可附带操作所需的数据),系统籍此在设备上查找可执行此操作的组件并启动之。 如果同时有多个组件都可以执行此操作,则由用户选用一个。
通过将接收到的 Intent 和其他应用程序的 Manifest 文件中的 Intent 过滤器 进行比较,系统识别出可以响应该 Intent 的组件。
当在应用程序的 Manifest 文件中声明 Activity 时,可以包含 Intent 过滤器,用于声明该 Activity 可以响应来自其他应用程序的 Intent。 通过在组件声明节点下添加一个子节点 <intent-filter>
,可以为该组件声明一个 Intent 过滤器。
例如,假设建立了一个 Email 应用程序,其中包含一个撰写新邮件的 Activity,这时就可以声明一个响应”send“ 类型 Intent(用于发送新邮件)的 Intent 过滤器如下:
<manifest ... > ... <application ...> <activity android:name="com.example.project.ComposeEmailActivity"> <intent-filter> <action android:name="android.intent.action.SEND" /> <data android:type="*/*" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> </application> </manifest>
这样,如果其他应用程序创建了一个 ACTION_SEND
类型的 Action,并传给 startActivity()
, 系统就可以启动上述 Activity,用户就可以起草并发送一封 Email 了。
关于创建 Intent 过滤器的更多信息,请参阅文档 Intent 和 Intent 过滤器。
声明应用程序需求
Android 支持多种设备,这些设备并不是全都提供相同的硬件配置和功能。 为了防止在不提供必需配置的设备上安装应用程序,明确定义一个配置文件是非常重要的,以便列出 App 支持的设备类型,这通过在 Manifest 文件中声明软硬件需求即可。 绝大部分声明仅仅是通告性质的,系统不会去读取它们。 但为了能让用户在搜索应用时根据设备型号进行过滤,类似 Google Play 之类的外部服务将会读取这些信息。
例如,如果应用程序需要用到摄像头和 Android 2.1 以上的 API( API Level 7), 就可以在 Manifest 文件中进行如下声明:
<manifest ... > <uses-feature android:name="android.hardware.camera.any" android:required="true" /> <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" /> ... </manifest>
这样,不具备摄像头的设备和低于2.1版本的 Android 就无法通过 Google Play 安装该应用了。
当然,还可以声明为:应用程序会用到摄像头但不是必需的。这时,应用程序必须将 required
设为 "false"
,并且在运行时自行检测是否存在摄像头,以便适时禁用与之有关的功能。
关于如何让应用程序保持与各种设备的兼容性,文档 设备兼容性 中给出了更为详细的信息。
应用程序资源
Android 应用程序并不仅仅由源代码构成 — 还需要用到资源文件,如图片、音频文件和其他所有的可视化内容,这些都是与源代码相分离的。 例如,用 XML 文件定义动画、菜单、颜色、Activity 用户界面的 Layout。 通过资源文件,不必修改源代码就可以方便地改变应用程序的各种配置 — 通过替代资源 — 以便根据各种设备参数(比如不同的语言和屏幕尺寸)优化应用程序。
SDK 编译工具会为 Android 项目中的每个资源都定义一个唯一的整数型 ID,以便在源代码或 XML 格式的其他资源中引用该资源。 例如,假设应用程序中包含一个名为 logo.png
的图片文件(保存在 res/drawable/
目录中), SDK 编译工具将会创建一个名为 R.drawable.logo
的唯一 ID,通过此 ID 可以引用该图片并把它放用户界面中去。
把资源文件从源代码中分离出来的重点之一,就是为不同的设备参数分别给出替代资源。 例如,通过定义 XML 格式的 UI 字符串,可以把这些文字翻译为其他国家的语言并保存到单独的文件中。 然后,根据资源目录名后面跟随的语言标识符(如法语字符串就是 res/values-fr/
),以及用户的语言设置, Android 系统将会把相应语言的字符串应用到用户界面中。
Android 为替代资源提供了很多不同种类的的 标识符。 这些标识符也就是一个短字符串,各种设备配置对应的资源目录的名称中包含了这个字符串。 再举个例子,根据设备的屏幕方向和尺寸,通常需要为 Activity 创建多种不同的布局文件(Layout)。 比如,当设备屏幕是纵向放置时(高度较大),可能需要让按钮纵向排列。 而当屏幕横向放置时(宽度较大),按钮就应该是横向放置了。 为了能根据屏幕方向来改变布局,可以定义两个 Layout,并用适当的标识符为 Layout 目录命名。 这样,系统就能根据当前设备方向自动选用合适的 Layout。
关于应用程序可用的各种资源,以及如何为各种设备配置创建替代资源,更多信息请参阅 提供资源。