应用基础知识

Android 应用可以使用 Kotlin、Java 编程语言和 C++ 语言编写。Android SDK 工具会将您的代码以及任何数据和资源文件编译成 APK 或 Android 应用包。

Android 包是一个带有 .apk 后缀的存档文件,其中包含 Android 应用在运行时所需的内容,Android 设备使用此文件安装应用。

Android 应用包是一个带有 .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)是与用户交互的入口点。它表示具有用户界面的单个屏幕。例如,一个电子邮件应用程序可能有一个活动显示新邮件列表,另一个活动用于撰写电子邮件,还有一个活动用于阅读电子邮件。尽管这些活动协同工作,在电子邮件应用程序中形成了一个连贯的用户体验,但每个活动都是独立的。

如果电子邮件应用程序允许,不同的应用程序可以启动这些活动中的任何一个。例如,相机应用程序可能会启动电子邮件应用程序中用于撰写新电子邮件的活动,以允许用户分享图片。

活动促进系统和应用程序之间以下关键交互:

  • 跟踪用户当前关注的内容(屏幕上显示的内容),以便系统继续运行托管该活动的进程。
  • 了解以前使用的进程中包含哪些用户可能返回的已停止活动,并优先处理这些进程以保持其可用性。
  • 帮助应用程序处理其进程被终止的情况,以便用户可以返回到其先前状态已恢复的活动。
  • 为应用程序提供一种在彼此之间实现用户流程的方法,并为系统协调这些流程提供一种方法。这方面的主要示例是共享。

您可以将活动实现为Activity类的子类。有关Activity类的更多信息,请参阅活动简介

服务
服务(Service)是用于出于各种原因保持应用程序在后台运行的通用入口点。它是在后台运行的组件,用于执行长时间运行的操作或为远程进程执行工作。服务不提供用户界面。

例如,服务可以在用户使用其他应用程序时在后台播放音乐,或者可以在不阻止用户与活动交互的情况下通过网络获取数据。另一个组件(例如活动)可以启动服务并让其运行,或者绑定到它以与其交互。

有两种类型的服务可以告诉系统如何管理应用程序:启动的服务和绑定服务。

启动的服务告诉系统在完成工作之前保持其运行。这可能是为了在后台同步某些数据,或者即使用户离开应用程序后也播放音乐。在后台同步数据或播放音乐代表不同类型的启动服务,系统对它们的处理方式不同。

  • 音乐播放是用户直接感知到的内容,应用程序通过指示其希望处于前台并显示通知以告知用户其正在运行来向系统传达这一点。在这种情况下,系统会优先保持该服务的进程运行,因为如果它消失,用户体验会很糟糕。
  • 常规后台服务不是用户直接感知到的内容,因此系统在管理其进程方面具有更大的自由度。如果需要内存来处理对用户更紧急的事情,它可能会将其终止,并在稍后重新启动服务。

绑定服务之所以运行,是因为某些其他应用程序(或系统)已声明其想要使用该服务。绑定服务为另一个进程提供 API,系统知道这些进程之间存在依赖关系。因此,如果进程 A 绑定到进程 B 中的服务,则系统知道它需要保持进程 B 及其服务为 A 运行。此外,如果进程 A 是用户关心的内容,则系统知道也将进程 B 视为用户关心的内容。

由于其灵活性,服务对于各种更高级别的系统概念都是有用的构建块。动态壁纸、通知侦听器、屏幕保护程序、输入法、辅助功能服务以及许多其他核心系统功能都是作为应用程序实现的服务构建的,并且系统在运行时会绑定到这些服务。

服务实现为Service的子类。有关Service类的更多信息,请参阅服务概述

注意:如果您的应用程序的目标是 Android 5.0(API 级别 21)或更高版本,请使用JobScheduler类来安排操作。JobScheduler 的优势在于,它可以通过优化作业调度来减少功耗,并与Doze API 协同工作,从而节省电池电量。有关使用此类的更多信息,请参阅JobScheduler参考文档。

广播接收器
广播接收器(Broadcast Receiver)是一个组件,它允许系统在常规用户流程之外向应用程序传递事件,以便应用程序可以响应系统范围的广播公告。由于广播接收器是另一个定义明确的应用程序入口点,因此系统甚至可以向当前未运行的应用程序传递广播。

因此,例如,应用程序可以安排一个闹钟来发布通知,以告知用户即将发生的事件。由于闹钟传递到应用程序中的BroadcastReceiver,因此无需应用程序在闹钟响起之前保持运行。

许多广播源自系统,例如宣布屏幕关闭、电池电量低或拍摄图片的广播。应用程序也可以启动广播,例如让其他应用程序知道某些数据已下载到设备上,并且可供它们使用。

虽然广播接收器不显示用户界面,但它们可以创建状态栏通知以在发生广播事件时提醒用户。但是,更常见的是,广播接收器只是一个网关,用于其他组件,并且旨在执行极少量的操作。

例如,广播接收器可能会使用JobScheduler调度JobService来执行基于事件的一些工作。广播接收器通常涉及应用程序之间的交互,因此在设置它们时务必注意安全隐患。

广播接收器实现为BroadcastReceiver的子类,每个广播都作为Intent对象传递。有关更多信息,请参阅BroadcastReceiver类。

内容提供程序
内容提供程序(Content Provider)管理一组共享的应用程序数据,您可以将其存储在文件系统、SQLite 数据库、Web 或应用程序可以访问的任何其他持久性存储位置中。通过内容提供程序,其他应用程序可以查询或修改数据(如果内容提供程序允许)。

例如,Android 系统提供了一个内容提供程序来管理用户的联系信息。任何具有适当权限的应用程序都可以查询内容提供程序,例如使用ContactsContract.Data来读取和写入有关特定人员的信息。

人们很容易将内容提供程序视为数据库的抽象,因为针对这种情况内置了大量的 API 和支持。但是,从系统设计的角度来看,它们具有与数据库不同的核心目的。

对于系统而言,内容提供程序是应用程序的入口点,用于发布命名的数据项,这些数据项由 URI 方案标识。因此,应用程序可以决定如何将其包含的数据映射到 URI 命名空间,将这些 URI 分发给其他实体,这些实体又可以使用这些 URI 来访问数据。这允许系统执行一些特定操作来管理应用程序:

  • 分配 URI 不需要应用程序保持运行,因此 URI 可以在其所属的应用程序退出后仍然存在。系统只需要确保在从相应的 URI 检索应用程序的数据时,所属的应用程序仍在运行。
  • 这些 URI 还提供了一个重要的细粒度安全模型。例如,应用程序可以将其拥有的图像的 URI 放入剪贴板,但将其内容提供程序锁定,以便其他应用程序无法随意访问它。当第二个应用程序尝试访问剪贴板上的该 URI 时,系统可以允许该应用程序使用临时的URI 权限授予访问数据,以便它仅在该 URI 后面访问数据,而不会访问第二个应用程序中的任何其他内容。

内容提供程序也可用于读取和写入对应用程序私有且未共享的数据。

内容提供程序实现为ContentProvider的子类,并且必须实现一组标准 API,使其他应用程序能够执行事务。有关更多信息,请参阅内容提供程序开发者指南。

Android 系统设计的一个独特方面是,任何应用程序都可以启动另一个应用程序的组件。例如,如果您希望用户使用设备相机拍摄照片,可能会有另一个应用程序可以做到这一点——您的应用程序可以使用它,而不必开发自己拍摄照片的活动。您不需要合并甚至链接来自相机应用程序的代码。相反,您可以启动相机应用程序中拍摄照片的活动。完成后,照片甚至会返回到您的应用程序,以便您可以使用它。对于用户而言,相机似乎实际上是您应用程序的一部分。

当系统启动组件时,如果该应用程序尚未运行,它会启动该应用程序的进程,并实例化组件所需的类。例如,如果您的应用程序启动相机应用程序中拍摄照片的活动,则该活动会在属于相机应用程序的进程中运行,而不是在您的应用程序的进程中运行。因此,与大多数其他系统上的应用程序不同,Android 应用程序没有单个入口点:没有main()函数。

由于系统在单独的进程中运行每个应用,并且文件权限限制了对其他应用的访问,因此您的应用无法直接激活另一个应用中的组件。但是,Android 系统可以。要激活另一个应用中的组件,您可以向系统发送一条消息,其中指定您启动特定组件的 *意图*。然后,系统将为您激活该组件。

激活组件

一条名为 *意图* 的异步消息可以激活四种组件类型中的三种:活动、服务和广播接收器。意图在运行时将各个组件绑定在一起。您可以将它们视为请求其他组件执行操作的信使,无论该组件属于您的应用还是其他应用。

意图是使用 Intent 对象创建的,该对象定义一条消息以激活特定组件(*显式* 意图)或特定类型的组件(*隐式* 意图)。

对于活动和服务,意图定义要执行的操作,例如 *查看* 或 *发送* 某些内容,并且可能指定要对其执行操作的数据的 URI,以及启动的组件可能需要了解的其他内容。

例如,意图可能会传达一个请求,要求活动显示图像或打开网页。在某些情况下,您可以启动一个活动以接收结果,在这种情况下,活动也会在 Intent 中返回结果。您还可以发出意图让用户选择个人联系人并将其返回给您。返回的意图包含指向所选联系人的 URI。

对于广播接收器,意图定义广播公告。例如,指示设备电池电量低的广播只包含一个已知的动作字符串,指示 *电池电量低*。

与活动、服务和广播接收器不同,内容提供程序在 ContentResolver 的请求将其作为目标时被激活。内容解析器处理与内容提供程序的所有直接事务,并且与提供程序执行事务的组件调用 ContentResolver 对象上的方法。出于安全原因,这在内容提供程序和请求信息的组件之间留下一层抽象。

激活每种类型的组件都有单独的方法

  • 您可以启动活动或为其提供新的操作,方法是将 Intent 传递给 startActivity(),或者当您希望活动返回结果时,传递给 startActivityForResult()
  • 在 Android 5.0(API 级别 21)及更高版本中,您可以使用 JobScheduler 类来安排操作。对于早期 Android 版本,您可以通过将 Intent 传递给 startService() 来启动服务或向正在运行的服务提供新指令。您可以通过将 Intent 传递给 bindService() 来绑定到服务。
  • 您可以通过将 Intent 传递给诸如 sendBroadcast()sendOrderedBroadcast() 之类的方法来启动广播。
  • 您可以通过在 ContentResolver 上调用 query() 来对内容提供程序执行查询。

有关使用意图的更多信息,请参阅 意图和意图过滤器 文档。以下文档提供有关激活特定组件的更多信息:活动简介服务概述BroadcastReceiver内容提供程序

清单文件

在 Android 系统启动应用组件之前,系统必须通过读取应用的 *清单文件* AndroidManifest.xml 来知道该组件是否存在。您的应用在此文件中声明其所有组件,该文件位于应用项目目录的根目录。

除了声明应用的组件之外,清单文件还执行许多操作,例如:

  • 标识应用所需的任何用户权限,例如互联网访问或对用户联系人的读取访问权限。
  • 根据应用使用的 API 声明应用所需的最小 API 级别
  • 声明应用使用或需要的硬件和软件功能,例如摄像头、蓝牙服务或多点触控屏幕。
  • 声明应用需要链接到的 API 库(Android 框架 API 除外),例如 Google 地图库

声明组件

清单的主要任务是向系统告知应用的组件。例如,清单文件可以声明活动,如下所示:

<?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 属性指定用作活动的用户可见标签的字符串。

您必须使用以下元素声明所有应用组件:

包含在您的源代码中但在清单中未声明的活动、服务和内容提供程序对系统不可见,因此永远无法运行。但是,广播接收器可以在清单中声明,也可以在代码中动态创建为 BroadcastReceiver 对象,并通过调用 registerReceiver() 向系统注册。

有关如何为您的应用构建清单文件的更多信息,请参阅 应用清单概述

声明组件功能

激活组件 部分所述,您可以使用 Intent 来启动活动、服务和广播接收器。您可以通过在意图中显式命名目标组件(使用组件类名)来实现此目的。您也可以使用隐式意图,它描述要执行的操作类型,以及可选地要对其执行操作的数据。隐式意图允许系统查找设备上可以执行该操作并启动它的组件。如果有多个组件可以执行意图描述的操作,则用户可以选择要使用哪个组件。

**警告:**如果使用意图启动 Service,请确保您的应用安全,方法是使用 显式 意图。使用隐式意图启动服务是一种安全隐患,因为您无法确定哪个服务响应意图,用户也看不到哪个服务启动。从 Android 5.0(API 级别 21)开始,如果您使用隐式意图调用 bindService(),系统将抛出异常。不要为您的服务声明意图过滤器。

系统通过将接收到的意图与设备上其他应用的清单文件中提供的 *意图过滤器* 进行比较来识别可以响应意图的组件。

当您在应用的清单中声明活动时,您可以选择包含意图过滤器来声明活动的功能,以便它可以响应来自其他应用的意图。您可以通过添加 <intent-filter> 元素作为组件声明元素的子元素来实现此目的。

例如,如果您构建了一个带有用于撰写新电子邮件的活动的电子邮件应用,您可以声明一个意图过滤器来响应“发送”意图以发送新电子邮件,如下例所示:

<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 操作的意图并将其传递给 startActivity(),则系统可能会启动您的活动,以便用户可以撰写和发送电子邮件。

有关创建意图过滤器的更多信息,请参阅 意图和意图过滤器 文档。

声明应用需求

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 文件定义动画、菜单、样式、颜色和活动用户界面的布局。

使用应用资源可以轻松更新应用的各种特性,而无需修改代码。提供多套备选资源可以让您针对各种设备配置(例如不同的语言和屏幕尺寸)优化您的应用。

对于您包含在 Android 项目中的每个资源,SDK 构建工具都会定义一个唯一的整数 ID,您可以使用它从应用代码或 XML 中定义的其他资源来引用该资源。例如,如果您的应用包含名为 `logo.png(保存在 `res/drawable/ 目录中)的图像文件,则 SDK 工具会生成名为 `R.drawable.logo 的资源 ID。此 ID 映射到一个特定于应用的整数,您可以使用它来引用图像并将其插入到您的用户界面中。

将资源与源代码分开的最重要方面之一是能够为不同的设备配置提供替代资源。

例如,通过在 XML 中定义 UI 字符串,您可以将字符串翻译成其他语言并将这些字符串保存在单独的文件中。然后,Android 会根据您附加到资源目录名称的语言 *限定符*(例如,法语字符串值为 `res/values-fr/)和用户的语言设置,将相应的语言字符串应用于您的 UI。

Android 支持许多用于替代资源的限定符。限定符是一个简短的字符串,您将其包含在资源目录的名称中,以定义这些资源用于的设备配置。

例如,您可以根据设备的屏幕方向和大小为您的活动创建不同的布局。当设备屏幕处于纵向(高)方向时,您可能需要一个按钮垂直排列的布局,但当屏幕处于横向(宽)方向时,您可能需要将按钮水平对齐。要根据方向更改布局,您可以定义两个布局并将相应的限定符应用于每个布局的目录名称。然后,系统会根据当前设备方向自动应用相应的布局。

有关您可以包含在应用中的不同类型的资源以及如何为不同的设备配置创建替代资源的更多信息,请阅读 应用资源概述。要了解有关最佳实践和设计健壮、生产级应用的更多信息,请参阅 应用架构指南

其他资源

要学习使用视频和代码教程进行 Android 开发,请参阅 Udacity 课程 使用 Kotlin 开发 Android 应用

继续阅读关于

意图和意图过滤器
了解如何使用 `Intent API 激活应用组件(例如活动和服务),以及如何使您的应用组件可供其他应用使用。
活动简介
了解如何创建 `Activity 类的实例,该类在您的应用中提供具有用户界面的独立屏幕。
应用资源概述
了解 Android 应用如何构建以将应用资源与应用代码分开,包括如何为特定设备配置提供替代资源。

其他感兴趣的内容

设备兼容性概述
了解 Android 如何在不同类型的设备上运行,以及如何针对每个设备优化您的应用或限制您的应用在不同设备上的可用性。
Android 上的权限
了解 Android 如何使用权限系统限制应用对某些 API 的访问,该系统需要用户的同意才能让您的应用使用这些 API。