Android 应用可以使用 Kotlin、Java 编程语言和 C++ 语言编写。Android SDK 工具会将您的代码以及所有数据和资源文件编译成 APK 或 Android App Bundle。
Android 软件包是一个带有 .apk
后缀的归档文件,其中包含 Android 应用在运行时所需的内容,也是 Android 设备用于安装应用的文件。
Android App Bundle 是一个带有 .aab
后缀的归档文件,其中包含 Android 应用项目的内容,包括一些运行时不需要的额外元数据。AAB 是一种发布格式,无法安装在 Android 设备上。它将 APK 的生成和签名推迟到稍后阶段。
例如,当您通过 Google Play 分发应用时,Google Play 的服务器会生成优化的 APK,其中仅包含特定设备请求安装应用所需的资源和代码。
每个 Android 应用都位于其自己的安全沙盒中,受以下 Android 安全功能的保护:
- Android 操作系统是多用户 Linux 系统,其中每个应用都是一个不同的用户。
- 默认情况下,系统会为每个应用分配一个唯一的 Linux 用户 ID,该 ID 仅供系统使用,应用本身并不知道。系统会为应用中的所有文件设置权限,以便只有分配给该应用的用户 ID 才能访问它们。
- 每个进程都有自己的虚拟机 (VM),因此应用代码与其他应用隔离运行。
- 默认情况下,每个应用都在其自己的 Linux 进程中运行。当应用的任何组件需要执行时,Android 系统会启动该进程;当不再需要该进程或系统必须为其他应用回收内存时,系统会关闭该进程。
Android 系统实现最小权限原则。也就是说,每个应用默认情况下只能访问其完成工作所需的组件,而不能访问更多。这创建了一个非常安全的环境,应用无法访问其未被授予权限的系统部分。
但是,应用可以通过以下方式与其他应用共享数据以及访问系统服务:
- 可以安排两个应用共享相同的 Linux 用户 ID,在这种情况下它们能够相互访问文件。为了节省系统资源,具有相同用户 ID 的应用也可以安排在相同的 Linux 进程中运行并共享相同的 VM。这些应用还必须使用相同的证书进行签名。
- 应用可以请求权限来访问设备数据,例如设备的位置、相机和蓝牙连接。用户必须明确授予这些权限。有关权限的更多信息,请参阅Android 上的权限。
本文档的其余部分介绍了以下概念:
- 定义您应用的核心框架组件。
- 您在其中声明组件和应用所需设备功能的清单文件。
- 与应用代码分离的资源,可让您的应用针对各种设备配置优雅地优化其行为。
应用组件
应用组件是 Android 应用的基本构建块。每个组件都是系统或用户可以进入您应用的入口点。有些组件依赖于其他组件。
应用组件有四种类型:
- Activity
- Service
- 广播接收器
- 内容提供程序
每种类型都有不同的用途,并具有定义组件如何创建和销毁的不同生命周期。以下各部分介绍了四种类型的应用组件。
- Activity
- Activity 是与用户交互的入口点。它代表一个带有用户界面的屏幕。例如,一个电子邮件应用可能有一个 Activity 用于显示新电子邮件列表,另一个 Activity 用于编写电子邮件,还有一个 Activity 用于阅读电子邮件。尽管这些 Activity 协同工作以在电子邮件应用中形成连贯的用户体验,但它们彼此独立。
如果电子邮件应用允许,其他应用可以启动这些 Activity 中的任何一个。例如,相机应用可以启动电子邮件应用中用于编写新电子邮件的 Activity,以让用户共享图片。
Activity 促进了系统和应用之间的以下关键交互:
- 跟踪用户当前关注的内容(屏幕上显示的内容),以便系统保持运行承载该 Activity 的进程。
- 了解哪些以前使用的进程包含用户可能返回的已停止 Activity,并优先处理这些进程以使其保持可用。
- 帮助应用处理其进程被终止的情况,以便用户可以返回到已恢复其先前状态的 Activity。
- 为应用提供一种在彼此之间实现用户流的方式,并为系统协调这些流。这方面的主要例子是共享。
您将 Activity 实现为
Activity
类的子类。有关Activity
类的更多信息,请参阅Activity 简介。 - Service
- Service 是一个通用入口点,用于出于各种原因在后台运行应用。它是一个在后台运行以执行长时间运行操作或为远程进程执行工作的组件。Service 不提供用户界面。
例如,Service 可以在用户处于不同应用时在后台播放音乐,或者可以在不阻塞用户与 Activity 交互的情况下通过网络获取数据。其他组件(例如 Activity)可以启动 Service 并让其运行,或绑定到它以与其交互。
有两种类型的 Service 告诉系统如何管理应用:已启动 Service 和绑定 Service。
已启动 Service 告诉系统让它们运行直到其工作完成。这可能是为了在后台同步一些数据,或者即使在用户离开应用后也播放音乐。在后台同步数据或播放音乐代表不同类型的已启动 Service,系统会以不同方式处理它们:
- 音乐播放是用户直接意识到的事情,应用通过指示它希望在前台运行,并显示通知告知用户它正在运行,从而将此信息传达给系统。在这种情况下,系统会优先保持该 Service 的进程运行,因为如果它消失,用户会获得糟糕的体验。
- 常规后台 Service 不是用户直接意识到的事情,因此系统在管理其进程方面有更大的自由度。如果它需要 RAM 来处理用户更直接关心的事情,它可能会让它被终止,稍后重新启动 Service。
绑定 Service 运行是因为其他应用(或系统)表示它想使用该 Service。绑定 Service 为另一个进程提供 API,并且系统知道这些进程之间存在依赖关系。因此,如果进程 A 绑定到进程 B 中的 Service,系统就知道它需要保持进程 B 及其 Service 为 A 运行。此外,如果进程 A 是用户关心的事情,那么它就知道将进程 B 也视为用户关心的事情。
由于其灵活性,Service 是各种更高级别系统概念的有用构建块。动态壁纸、通知监听器、屏幕保护程序、输入法、无障碍服务以及许多其他核心系统功能都是作为应用程序实现的 Service 构建的,并在它们运行时系统会绑定到它们。
Service 被实现为
Service
的子类。有关Service
类的更多信息,请参阅Service 概览。注意:如果您的应用面向 Android 5.0 (API 级别 21) 或更高版本,请使用
JobScheduler
类来安排操作。JobScheduler 的优点是可以通过优化任务调度来减少功耗,并与 Doze API 协同工作,从而节省电量。有关使用此类的更多信息,请参阅JobScheduler
参考文档。 - 广播接收器
- 广播接收器是一个组件,它允许系统在常规用户流程之外向应用传递事件,以便应用可以响应系统范围的广播公告。由于广播接收器是应用中另一个定义明确的入口,因此即使对于当前未运行的应用,系统也可以传递广播。
因此,例如,应用可以安排一个闹钟来发布通知,告知用户即将发生的事件。由于闹钟会传递到应用中的
BroadcastReceiver
,因此应用无需保持运行直到闹钟响起。许多广播都源自系统,例如宣布屏幕关闭、电池电量低或图片已捕获的广播。应用也可以发起广播,例如告知其他应用某些数据已下载到设备并可供它们使用。
尽管广播接收器不显示用户界面,但它们可以创建状态栏通知以在广播事件发生时提醒用户。然而,更常见的情况是,广播接收器只是其他组件的网关,旨在执行非常少的工作。
例如,广播接收器可能会安排一个
JobService
来使用JobScheduler
根据事件执行一些工作。广播接收器通常涉及应用之间的交互,因此在设置它们时了解安全隐患非常重要。广播接收器被实现为
BroadcastReceiver
的子类,每个广播都作为Intent
对象传递。有关更多信息,请参阅BroadcastReceiver
类。 - 内容提供程序
- 内容提供程序管理一组共享的应用数据,您可以将其存储在文件系统、SQLite 数据库、Web 上或应用可以访问的任何其他持久性存储位置。通过内容提供程序,如果内容提供程序允许,其他应用可以查询或修改数据。
例如,Android 系统提供了一个内容提供程序来管理用户的联系人信息。任何具有适当权限的应用都可以查询内容提供程序,例如使用
ContactsContract.Data
来读取和写入特定人员的信息。人们很容易将内容提供程序视为数据库的抽象,因为其中内置了大量 API 和支持以应对这种常见情况。然而,从系统设计的角度来看,它们具有不同的核心目的。
对于系统来说,内容提供程序是应用发布命名数据项的入口点,这些数据项由 URI 方案标识。因此,应用可以决定如何将其包含的数据映射到 URI 命名空间,将这些 URI 分发给其他实体,这些实体反过来可以使用它们来访问数据。这使系统在管理应用方面能够做一些特别的事情:
- 分配 URI 不要求应用保持运行,因此 URI 可以在其所属应用退出后仍然存在。系统只需要确保当它从相应的 URI 检索应用数据时,所属应用仍在运行。
- 这些 URI 还提供了一种重要的细粒度安全模型。例如,应用可以将图像的 URI 放置在剪贴板上,但将其内容提供程序锁定,以便其他应用无法自由访问它。当第二个应用尝试访问剪贴板上的该 URI 时,系统可以允许该应用使用临时URI 权限授予访问数据,以便它只访问该 URI 后面的数据,而不访问第二个应用中的其他任何内容。
内容提供程序对于读取和写入您的应用私有且未共享的数据也很有用。
内容提供程序被实现为
ContentProvider
的子类,并且必须实现一组标准 API,以使其他应用能够执行事务。有关更多信息,请参阅内容提供程序开发者指南。
Android 系统设计的一个独特之处在于,任何应用都可以启动另一个应用的组件。例如,如果您想让用户使用设备相机拍摄照片,可能已经有另一个应用可以实现此功能——您的应用可以使用它,而不是自行开发一个 Activity 来拍摄照片。您无需集成甚至链接相机应用的代码。相反,您可以启动相机应用中用于拍摄照片的 Activity。完成后,照片甚至会返回到您的应用,以便您可以使用它。对于用户来说,相机似乎是您应用的一部分。
当系统启动一个组件时,如果该应用进程尚未运行,它会启动该应用的进程,并实例化组件所需的类。例如,如果您的应用启动相机应用中用于拍摄照片的 Activity,则该 Activity 会在属于相机应用的进程中运行,而不是在您的应用进程中运行。因此,与大多数其他系统上的应用不同,Android 应用没有单一的入口点:没有 main()
函数。
由于系统在单独的进程中运行每个应用,并具有限制访问其他应用的文件权限,因此您的应用无法直接激活其他应用中的组件。但是,Android 系统可以。要激活其他应用中的组件,您需要向系统发送一条消息,指定您启动特定组件的意图。然后系统会为您激活该组件。
激活组件
一种称为Intent 的异步消息会激活四种组件类型中的三种:Activity、Service 和广播接收器。Intent 在运行时将各个组件相互绑定。您可以将它们视为请求其他组件(无论是您应用中的组件还是其他应用中的组件)执行操作的信使。
Intent 是使用 Intent
对象创建的,该对象定义了一条消息,用于激活特定组件(显式 Intent)或特定类型的组件(隐式 Intent)。
对于 Activity 和 Service,Intent 定义要执行的操作(例如“查看”或“发送”某些内容),并且可以指定要操作的数据的 URI,以及启动组件可能需要知道的其他信息。
例如,Intent 可能会传达一个请求,要求 Activity 显示图像或打开网页。在某些情况下,您可以启动一个 Activity 来接收结果,在这种情况下,Activity 也会在 Intent
中返回结果。您还可以发出 Intent,让用户选择个人联系人并将其返回给您。返回的 Intent 包含指向所选联系人的 URI。
对于广播接收器,Intent 定义了广播通知。例如,指示设备电池电量低的广播仅包含一个表示“电池电量低”的已知操作字符串。
与 Activity、Service 和广播接收器不同,内容提供程序在受到 ContentResolver
请求时被激活。内容解析器处理与内容提供程序的所有直接事务,并且与提供程序执行事务的组件会调用 ContentResolver
对象上的方法。这在内容提供程序和请求信息的组件之间留下了一个抽象层,出于安全原因。
有单独的方法来激活每种类型的组件:
- 您可以通过将
Intent
传递给startActivity()
来启动 Activity 或为其分配新任务,或者当您希望 Activity 返回结果时,传递给startActivityForResult()
。 - 在 Android 5.0 (API 级别 21) 及更高版本上,您可以使用
JobScheduler
类来安排操作。对于更早的 Android 版本,您可以通过将Intent
传递给startService()
来启动服务或向正在进行的服务提供新指令。您可以通过将Intent
传递给bindService()
来绑定到服务。 - 您可以通过将
Intent
传递给sendBroadcast()
或sendOrderedBroadcast()
等方法来启动广播。 - 您可以通过调用
ContentResolver
上的query()
来执行对内容提供程序的查询。
有关使用 Intent 的更多信息,请参阅Intent 和 Intent 过滤器文档。以下文档提供了有关激活特定组件的更多信息:Activity 简介、Service 概览、BroadcastReceiver
和内容提供程序。
清单文件
在 Android 系统可以启动应用组件之前,系统必须通过读取应用的清单文件 AndroidManifest.xml
来了解该组件的存在。您的应用在此文件中声明其所有组件,该文件位于应用项目目录的根目录下。
除了声明应用的组件外,清单还执行许多其他操作,例如:
- 识别应用所需的任何用户权限,例如互联网访问权限或用户联系人的读取权限。
- 根据应用使用的 API 声明应用所需的最低 API 级别。
- 声明应用使用或所需的硬件和软件功能,例如相机、蓝牙服务或多点触控屏幕。
- 声明应用需要链接到的 API 库(除了 Android 框架 API),例如 Google 地图库。
声明组件
清单的主要任务是告知系统有关应用的组件。例如,清单文件可以如下声明一个 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>
元素 - 内容提供程序的
<provider>
元素
您包含在源代码中但未在清单中声明的 Activity、Service 和内容提供程序对系统不可见,因此永远无法运行。但是,广播接收器既可以在清单中声明,也可以在代码中动态创建为 BroadcastReceiver
对象并通过调用 registerReceiver()
注册到系统。
有关如何为您的应用构建清单文件的更多信息,请参阅应用清单概览。
声明组件功能
如激活组件部分所述,您可以使用 Intent
启动 Activity、Service 和广播接收器。您可以通过在 Intent 中使用组件类名显式命名目标组件来完成此操作。您还可以使用隐式 Intent,它描述要执行的操作类型,并且可以选择要对其执行操作的数据。隐式 Intent 允许系统在设备上查找可以执行该操作并启动它的组件。如果有多个组件可以执行 Intent 描述的操作,用户会选择使用哪个组件。
注意:如果您使用 Intent 启动 Service
,请务必通过使用显式 Intent 确保您的应用安全。使用隐式 Intent 启动服务存在安全隐患,因为您无法确定哪个服务响应 Intent,并且用户无法看到哪个服务启动。从 Android 5.0 (API 级别 21) 开始,如果您使用隐式 Intent 调用 bindService()
,系统会抛出异常。请勿为您的服务声明 Intent 过滤器。
系统通过将收到的 Intent 与设备上其他应用的清单文件中提供的Intent 过滤器进行比较来识别可以响应 Intent 的组件。
当您在应用的清单中声明 Activity 时,您可以选择性地包含 Intent 过滤器,声明 Activity 的功能,以便它可以响应来自其他应用的 Intent。您可以通过将 <intent-filter>
元素作为组件声明元素的子元素来完成此操作。
例如,如果您构建一个带有用于编写新电子邮件的 Activity 的电子邮件应用,您可以声明一个 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
操作创建一个 Intent 并将其传递给 startActivity()
,系统可能会启动您的 Activity,以便用户可以起草和发送电子邮件。
有关创建 Intent 过滤器的更多信息,请参阅Intent 和 Intent 过滤器文档。
声明应用要求
有各种由 Android 提供支持的设备,并非所有设备都提供相同的功能和特性。为了防止您的应用安装在缺少应用所需功能的设备上,重要的是您通过在清单文件中声明设备和软件要求来明确定义应用支持的设备类型配置文件。
这些声明中的大多数仅供参考。系统不读取它们,但 Google Play 等外部服务会读取它们,以便在用户从其设备搜索应用时提供过滤功能。
例如,假设您的应用需要相机并使用 Android 8.0 (API 级别 26) 中引入的 API。您必须声明这些要求。minSdkVersion
和 targetSdkVersion
的值在您的应用模块的 build.gradle
文件中设置:
android { ... defaultConfig { ... minSdkVersion 26 targetSdkVersion 29 } }
注意:请勿直接在清单文件中设置 minSdkVersion
和 targetSdkVersion
,因为它们会在构建过程中被 Gradle 覆盖。有关更多信息,请参阅指定 API 级别要求。
您在应用的清单文件中声明相机功能:
<manifest ... > <uses-feature android:name="android.hardware.camera.any" android:required="true" /> ... </manifest>
根据这些示例中显示的声明,没有相机或 Android 版本低于 8.0 的设备无法从 Google Play 安装您的应用。但是,您也可以声明您的应用使用相机,但不要求它。为此,您将 required
属性设置为 false
,在运行时检查设备是否有相机,并根据需要禁用任何相机功能。
有关如何管理应用与不同设备兼容性的更多信息,请参阅设备兼容性概览。
应用资源
Android 应用不仅仅包含代码。它需要与源代码分离的资源,例如图像、音频文件以及与应用视觉呈现相关的任何内容。例如,您可以使用 XML 文件定义动画、菜单、样式、颜色和 Activity 用户界面的布局。
使用应用资源可以轻松更新应用的各种特性而无需修改代码。提供替代资源集可让您针对各种设备配置(例如不同的语言和屏幕尺寸)优化您的应用。
对于您在 Android 项目中包含的每个资源,SDK 构建工具都会定义一个唯一的整数 ID,您可以使用该 ID 从应用代码或 XML 中定义的其他资源引用该资源。例如,如果您的应用包含名为 logo.png
的图像文件(保存在 res/drawable/
目录中),SDK 工具会生成一个名为 R.drawable.logo
的资源 ID。此 ID 映射到一个特定于应用的整数,您可以使用该整数引用图像并将其插入到您的用户界面中。
将资源与源代码分离的最重要方面之一是能够为不同的设备配置提供替代资源。
例如,通过在 XML 中定义 UI 字符串,您可以将字符串翻译成其他语言并将其保存在单独的文件中。然后,Android 会根据您添加到资源目录名称的语言限定符(例如用于法语字符串值的 res/values-fr/
)和用户的语言设置,将适当的语言字符串应用于您的 UI。
Android 支持您的替代资源的许多限定符。限定符是一个短字符串,您将其包含在资源目录的名称中,以定义这些资源所使用的设备配置。
例如,您可以根据设备的屏幕方向和尺寸为您的 Activity 创建不同的布局。当设备屏幕处于纵向(高)方向时,您可能希望布局中的按钮垂直排列;但当屏幕处于横向(宽)方向时,您可能希望按钮水平对齐。要根据方向更改布局,您可以定义两个布局,并为每个布局的目录名称应用适当的限定符。然后,系统会根据当前设备方向自动应用适当的布局。
有关您可以包含在应用程序中的不同类型的资源以及如何为不同设备配置创建替代资源的更多信息,请阅读应用资源概览。要了解有关最佳实践以及设计健壮、生产质量应用的更多信息,请参阅应用架构指南。
其他资源
要通过视频和代码教程学习 Android 开发,请参阅 Udacity 课程使用 Kotlin 开发 Android 应用。
继续阅读关于
- Intent 和 Intent 过滤器
- 了解如何使用
Intent
API 激活应用组件(例如 Activity 和 Service),以及如何使您的应用组件可供其他应用使用。 - Activity 简介
- 了解如何创建
Activity
类的实例,该实例在您的应用中提供一个具有用户界面的独立屏幕。 - 应用资源概览
- 了解 Android 应用的结构如何将应用资源与应用代码分离,包括如何为特定设备配置提供替代资源。
还可能感兴趣的
- 设备兼容性概览
- 了解 Android 如何在不同类型的设备上工作,以及如何为每种设备优化您的应用或限制您的应用在不同设备上的可用性。
- Android 上的权限
- 了解 Android 如何通过权限系统限制应用访问某些 API,该系统要求用户同意您的应用才能使用这些 API。