Android复习(六)核心组件—>Activity 任何和返回栈、进程和应用生命周期、Parcelable和Bundle

了解任务和返回堆栈

任务是用户在执行某项工作时与之互动的一系列 Activity 的集合。这些 Activity 按照每个 Activity 打开的顺序排列在一个返回堆栈中。例如,电子邮件应用可能有一个 Activity 来显示新邮件列表。当用户选择一封邮件时,系统会打开一个新的 Activity 来显示该邮件。这个新的 Activity 会添加到返回堆栈中。如果用户按返回按钮,这个新的 Activity 即会完成并从堆栈中退出。通过以下视频可以大致了解返回堆栈的工作原理。

Android 7.0(API 级别 24)及更高版本支持多窗口环境,当应用在这种环境中同时运行时,系统会单独管理每个窗口的任务;而每个窗口可能包含多项任务。在 Chromebook 上运行的 Android 应用也是如此:系统按窗口管理任务或任务组。

大多数任务都从设备主屏幕上启动。当用户轻触应用启动器中的图标(或主屏幕上的快捷方式)时,该应用的任务就会转到前台运行。如果该应用没有任务存在(应用最近没有使用过),则会创建一个新的任务,并且该应用的“主”Activity 将会作为堆栈的根 Activity 打开。

在当前 Activity 启动另一个 Activity 时,新的 Activity 将被推送到堆栈顶部并获得焦点。上一个 Activity 仍保留在堆栈中,但会停止。当 Activity 停止时,系统会保留其界面的当前状态。当用户按返回按钮时,当前 Activity 会从堆栈顶部退出(该 Activity 销毁),上一个 Activity 会恢复(界面会恢复到上一个状态)。堆栈中的 Activity 永远不会重新排列,只会被送入和退出,在当前 Activity 启动时被送入堆栈,在用户使用返回按钮离开时从堆栈中退出。因此,返回堆栈按照“后进先出”的对象结构运作。图 1 借助一个时间轴直观地显示了这种行为。该时间轴显示了 Activity 之间的进展以及每个时间点的当前返回堆栈。

图 1. 有关任务中的每个新 Activity 如何添加到返回堆栈的图示。当用户按返回按钮时,当前 Activity 会销毁,上一个 Activity 将恢复。

如果用户继续按返回,则堆栈中的 Activity 会逐个退出,以显示前一个 Activity,直到用户返回到主屏幕(或任务开始时运行的 Activity)。移除堆栈中的所有 Activity 后,该任务将不复存在。

图 2. 两个任务:任务 B 在前台接收用户互动,任务 A 在后台等待恢复。

任务是一个整体单元,当用户开始一个新任务或通过主屏幕按钮进入主屏幕时,任务可移至“后台”。在后台时,任务中的所有 Activity 都会停止,但任务的返回堆栈会保持不变,当其他任务启动时,当前任务只是失去了焦点,如图 2 所示。这样一来,任务就可以返回到“前台”,以便用户可以从他们离开的地方继续操作。举例来说,假设当前任务(任务 A)的堆栈中有 3 个 Activity,当前 Activity 下有 2 个 Activity。用户按主屏幕按钮,然后从应用启动器中启动新应用。主屏幕出现后,任务 A 转到后台。当新应用启动时,系统会启动该应用的任务(任务 B),该任务具有自己的 Activity 堆栈。与该应用互动后,用户再次返回到主屏幕并选择最初启动任务 A 的应用。现在,任务 A 进入前台,其堆栈中的所有三个 Activity 都完好如初,堆栈顶部的 Activity 恢复运行。此时,用户仍可通过以下方式切换到任务 B:转到主屏幕并选择启动该任务的应用图标(或者从最近使用的应用屏幕中选择该应用的任务)。这就是在 Android 上进行多任务处理的一个例子。

注意:多个任务可以同时在后台进行。但是,如果用户同时运行很多后台任务,系统可能会为了恢复内存而开始销毁后台 Activity,导致 Activity 状态丢失。

图 3. 单个 Activity 会被多次实例化。

由于返回堆栈中的 Activity 不会被重新排列,如果您的应用允许用户从多个 Activity 启动特定的 Activity,系统便会创建该 Activity 的新实例并将其推送到堆栈中(而不是将该 Activity 的某个先前的实例移至堆栈顶部)。这样一来,应用中的一个 Activity 就可能被多次实例化(甚至是从其他任务对其进行实例化),如图 3 所示。因此,如果用户使用返回按钮向后导航,Activity 的每个实例将按照它们被打开的顺序显示出来(每个实例都有自己的界面状态)。不过,如果您不希望某个 Activity 被实例化多次,可以修改此行为。有关如何实现此操作,将在后面的管理任务部分中讨论。

Activity 和任务的默认行为总结如下:

  • 当 Activity A 启动 Activity B 时,Activity A 会停止,但系统会保留其状态(例如滚动位置和输入到表单中的文本)。如果用户在 Activity B 中按返回按钮,系统会恢复 Activity A 及其状态。
  • 当用户通过按主屏幕按钮离开任务时,当前 Activity 会停止,其任务会转到后台。系统会保留任务中每个 Activity 的状态。如果用户稍后通过点按该任务的启动器图标来恢复该任务,该任务会进入前台并恢复堆栈顶部的 Activity。
  • 如果用户按返回按钮,当前 Activity 将从堆栈中退出并销毁。堆栈中的上一个 Activity 将恢复。Activity 被销毁后,系统不会保留该 Activity 的状态。
  • Activity 可以多次实例化,甚至是从其他任务对其进行实例化。

导航设计

要详细了解 Android 上的应用导航如何运作,请参阅 Android 设计中的导航指南。

管理任务

如上文所述,Android 管理任务和返回堆栈的方式是将所有接连启动的 Activity 放到同一任务和一个“后进先出”堆栈中,这对于大多数应用都很有效,而且您不必担心 Activity 如何与任务相关联,或者它们如何存在于返回堆栈中。不过,您可能需要决定是否要打破正常行为。或许您希望应用中的某个 Activity 在启动时开启一个新的任务(而不是被放入当前的任务中),或者当您启动某个 Activity 时,您希望调用它的一个现有实例(而不是在返回堆栈顶部创建一个新实例),或者您希望在用户离开任务时清除返回堆栈中除根 Activity 以外的所有 Activity。

您可以借助 <activity> 清单元素中的属性以及您传递给 startActivity() 的 intent 中的标记来实现上述目的。

在这方面,您可以使用的主要 <activity> 属性包括:

您可以使用的主要 intent 标记包括:

在下面几节中,您将了解到如何使用这些清单属性和 intent 标记来定义 Activity 与任务之间的关联方式,以及它们在返回堆栈中的行为。

另外,下面还分别介绍了如何在最近使用的应用屏幕中表示和管理任务与 Activity。有关详情,请参阅“最近使用的应用”屏幕。通常,您应允许系统定义任务和 Activity 在最近使用的应用屏幕中的表示方式,您无需修改此行为。

注意:大多数应用不应打破 Activity 和任务的默认行为。如果您确定需要让 Activity 改变默认行为,请谨慎操作,并且务必要测试该 Activity 在以下情况下的可用性:启动期间以及您通过返回按钮从其他 Activity 和任务返回该 Activity 时。务必要测试是否存在可能与用户预期的行为冲突的导航行为。

定义启动模式

您可以通过启动模式定义 Activity 的新实例如何与当前任务关联。您可以通过两种方式定义不同的启动模式:

  • 使用清单文件

    当您在清单文件中声明 Activity 时,您可以指定该 Activity 在启动时如何与任务关联。

  • 使用 Intent 标记

    当您调用 startActivity() 时,可以在 Intent 中添加一个标记,用于声明新 Activity 如何(或是否)与当前任务相关联。

因此,如果 Activity A 启动 Activity B,Activity B 可在其清单中定义如何与当前任务相关联(如果关联的话),Activity A 也可以请求 Activity B 应该如何与当前任务关联。如果两个 Activity 都定义了 Activity B 应如何与任务关联,将优先遵循 Activity A 的请求(在 intent 中定义),而不是 Activity B 的请求(在清单中定义)。

注意:有些启动模式可通过清单文件定义,但不能通过 intent 标记定义,同样,有些启动模式可通过 intent 标记定义,却不能在清单中定义。

使用清单文件

在清单文件中声明 Activity 时,可以使用 <activity> 元素的 launchMode 属性指定 Activity 应该如何与任务关联。

launchMode 属性说明了 Activity 应如何启动到任务中。您可以为 launchMode 属性指定 4 种不同的启动模式:

"standard"(默认模式)
默认值。系统在启动该 Activity 的任务中创建 Activity 的新实例,并将 intent 传送给该实例。Activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以拥有多个实例。
"singleTop"
如果当前任务的顶部已存在 Activity 的实例,则系统会通过调用其 onNewIntent() 方法来将 intent 转送给该实例,而不是创建 Activity 的新实例。Activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以拥有多个实例(但前提是返回堆栈顶部的 Activity 不是该 Activity 的现有实例)。

例如,假设任务的返回堆栈包含根 Activity A 以及 Activity B、C 和位于顶部的 D(堆栈为 A-B-C-D;D 位于顶部)。收到以 D 类型 Activity 为目标的 intent。如果 D 采用默认的 "standard" 启动模式,则会启动该类的新实例,并且堆栈将变为 A-B-C-D-D。但是,如果 D 的启动模式为 "singleTop",则 D 的现有实例会通过 onNewIntent() 接收 intent,因为它位于堆栈顶部,堆栈仍为 A-B-C-D。但是,如果收到以 B 类型 Activity 为目标的 intent,则会在堆栈中添加 B 的新实例,即使其启动模式为 "singleTop" 也是如此。

注意:创建 Activity 的新实例后,用户可以按返回按钮返回到上一个 Activity。但是,当由 Activity 的现有实例处理新 intent 时,用户将无法通过按返回按钮返回到 onNewIntent() 收到新 intent 之前的 Activity 状态。

"singleTask"
系统会创建新任务,并实例化新任务的根 Activity。但是,如果另外的任务中已存在该 Activity 的实例,则系统会通过调用其 onNewIntent() 方法将 intent 转送到该现有实例,而不是创建新实例。Activity 一次只能有一个实例存在。

注意:虽然 Activity 在新任务中启动,但用户按返回按钮仍会返回到上一个 Activity。

"singleInstance"
与 "singleTask" 相似,唯一不同的是系统不会将任何其他 Activity 启动到包含该实例的任务中。该 Activity 始终是其任务唯一的成员;由该 Activity 启动的任何 Activity 都会在其他的任务中打开。

再举个例子,Android 浏览器应用在 <activity> 元素中指定 singleTask 启动模式,由此声明网络浏览器 Activity 应始终在它自己的任务中打开。这意味着,如果您的应用发出打开 Android 浏览器的 intent,系统不会将其 Activity 置于您的应用所在的任务中,而是会为浏览器启动一个新任务,如果浏览器已经有任务在后台运行,则会将该任务转到前台来处理新 intent。

无论 Activity 是在新任务中启动的,还是在和启动它的 Activity 相同的任务中启动,用户按返回按钮都会回到上一个 Activity。但是,如果您启动了指定 singleTask 启动模式的 Activity,而后台任务中已存在该 Activity 的实例,则系统会将该后台任务整个转到前台运行。此时,返回堆栈包含了转到前台的任务中的所有 Activity,这些 Activity 都位于堆栈的顶部。图 4 展示了具体的情景。

图 4. 采用“singleTask”启动模式的 Activity 添加到返回堆栈的过程图示。如果 Activity 已经存在于某个具有自己的返回堆栈的后台任务中,那么整个返回堆栈也会转到前台,覆盖当前任务。

要详细了解如何在清单文件中设置启动模式,请参阅 <activity> 元素的说明文档,里面详细介绍了 launchMode 属性和可接受的值。

注意:您通过 launchMode 属性为 Activity 指定的行为,可被启动 Activity 的 intent 所包含的标记替换。下一节将对此进行介绍。

使用 Intent 标记

启动 Activity 时,您可以在传送给 startActivity() 的 intent 中添加相应的标记来修改 Activity 与其任务的默认关联。您可以使用以下标记来修改默认行为:

FLAG_ACTIVITY_NEW_TASK
在新任务中启动 Activity。如果您现在启动的 Activity 已经有任务在运行,则系统会将该任务转到前台并恢复其最后的状态,而 Activity 将在 onNewIntent() 中收到新的 intent。

这与上一节中介绍的 "singleTask" launchMode 值产生的行为相同。

FLAG_ACTIVITY_SINGLE_TOP
如果要启动的 Activity 是当前 Activity(即位于返回堆栈顶部的 Activity),则现有实例会收到对 onNewIntent() 的调用,而不会创建 Activity 的新实例。

这与上一节中介绍的 "singleTop" launchMode 值产生的行为相同。

FLAG_ACTIVITY_CLEAR_TOP
如果要启动的 Activity 已经在当前任务中运行,则不会启动该 Activity 的新实例,而是会销毁位于它之上的所有其他 Activity,并通过 onNewIntent() 将此 intent 传送给它的已恢复实例(现在位于堆栈顶部)。

launchMode 属性没有可产生此行为的值。

FLAG_ACTIVITY_CLEAR_TOP 最常与 FLAG_ACTIVITY_NEW_TASK 结合使用。将这两个标记结合使用,可以查找其他任务中的现有 Activity,并将其置于能够响应 intent 的位置。

注意:如果指定 Activity 的启动模式为 "standard",系统也会将其从堆栈中移除,并在它的位置启动一个新实例来处理传入的 intent。这是因为当启动模式为 "standard" 时,始终会为新 intent 创建新的实例。

处理亲和性

“亲和性”表示 Activity 倾向于属于哪个任务。默认情况下,同一应用中的所有 Activity 彼此具有亲和性。因此,在默认情况下,同一应用中的所有 Activity 都倾向于位于同一任务。不过,您可以修改 Activity 的默认亲和性。在不同应用中定义的 Activity 可以具有相同的亲和性,或者在同一应用中定义的 Activity 也可以被指定不同的任务亲和性。

您可以使用 <activity> 元素的 taskAffinity 属性修改任何给定 Activity 的亲和性。

taskAffinity 属性采用字符串值,该值必须不同于 <manifest> 元素中声明的默认软件包名称,因为系统使用该名称来标识应用的默认任务亲和性。

亲和性可在两种情况下发挥作用:

  • 当启动 Activity 的 intent 包含 FLAG_ACTIVITY_NEW_TASK 标记时。

    默认情况下,新 Activity 会启动到调用 startActivity() 的 Activity 的任务中。它会被推送到调用方 Activity 所在的返回堆栈中。但是,如果传递给 startActivity() 的 intent 包含 FLAG_ACTIVITY_NEW_TASK 标记,则系统会寻找其他任务来容纳新 Activity。通常会是一个新任务,但也可能不是。如果已存在与新 Activity 具有相同亲和性的现有任务,则会将 Activity 启动到该任务中。如果不存在,则会启动一个新任务。

    如果此标记导致 Activity 启动一个新任务,而用户按下主屏幕按钮离开该任务,则必须为用户提供某种方式来返回到该任务。有些实体(例如通知管理器)总是在外部任务中启动 Activity,而不在它们自己的任务中启动,因此它们总是将 FLAG_ACTIVITY_NEW_TASK 添加到传递给 startActivity() 的 intent 中。如果您的 Activity 可由外部实体调用,而该实体可能使用此标记,请注意用户可以通过一种独立的方式返回到所启动的任务,例如使用启动器图标(任务的根 Activity 具有一个 CATEGORY_LAUNCHER intent 过滤器;请参阅下面的启动任务部分)。

  • 当 Activity 的 allowTaskReparenting 属性设为 "true" 时。

    在这种情况下,一旦和 Activity 有亲和性的任务进入前台运行,Activity 就可从其启动的任务转移到该任务。

    举例来说,假设一款旅行应用中定义了一个报告特定城市天气状况的 Activity。该 Activity 与同一应用中的其他 Activity 具有相同的亲和性(默认应用亲和性),并通过此属性支持重新归属。当您的某个 Activity 启动该天气预报 Activity 时,该天气预报 Activity 最初会和您的 Activity 同属于一个任务。不过,当旅行应用的任务进入前台运行时,该天气预报 Activity 就会被重新分配给该任务并显示在其中。

提示:如果一个 APK 文件中包含了就用户角度而言的多个“应用”,您可能需要使用 taskAffinity 属性为每个“应用”所关联的 Activity 指定不同的亲和性。

清除返回堆栈

如果用户离开任务较长时间,系统会清除任务中除根 Activity 以外的所有 Activity。当用户再次返回到该任务时,只有根 Activity 会恢复。系统之所以采取这种行为方式是因为,经过一段时间后,用户可能已经放弃了之前执行的操作,现在返回任务是为了开始某项新的操作。

您可以使用一些 Activity 属性来修改此行为:

alwaysRetainTaskState
如果在任务的根 Activity 中将该属性设为 "true",则不会发生上述默认行为。即使经过很长一段时间后,任务仍会在其堆栈中保留所有 Activity。
clearTaskOnLaunch
如果在任务的根 Activity 中将该属性设为 "true",那么只要用户离开任务再返回,堆栈就会被清除到只剩根 Activity。也就是说,它与 alwaysRetainTaskState 正好相反。用户始终会返回到任务的初始状态,即便只是短暂离开任务也是如此。
finishOnTaskLaunch
该属性与 clearTaskOnLaunch 类似,但它只会作用于单个 Activity 而非整个任务。它还可导致任何 Activity 消失,包括根 Activity。如果将该属性设为 "true",则 Activity 仅在当前会话中归属于任务。如果用户离开任务再返回,则该任务将不再存在。

启动任务

您可以设置一个 Activity 作为任务的入口点,方法是为该 Activity 提供一个 intent 过滤器,并将 "android.intent.action.MAIN" 作为指定操作,将 "android.intent.category.LAUNCHER" 作为指定类别。例如:

    <activity ... >
        <intent-filter ... >
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
        ...
    </activity>
   
 

这种 intent 过滤器可在应用启动器中显示 Activity 的图标和标签,让用户可以启动 Activity 并在启动后随时返回到该 Activity 创建的任务。

第二个作用非常重要:用户必须能够离开任务,之后再使用此 Activity 启动器返回到该任务。因此,只有当 Activity 具有 ACTION_MAIN 和 CATEGORY_LAUNCHER 过滤器时,才应使用 "singleTask" 和 "singleInstance" 这两种启动模式,它们会将 Activity 标记为始终启动任务。比如,可以想象一下,如果缺少该过滤器会发生什么情况:intent 会启动 "singleTask" Activity,随之启动新任务,用户花了一些时间在该任务上。然后,用户按主屏幕按钮。此时,该任务会转到后台,不再可见。现在,用户无法返回到该任务,因为它未显示在应用启动器中。

对于那些您不希望用户能够返回到 Activity 的情况,请将 <activity> 元素的 finishOnTaskLaunch 设置为 "true"(请参阅清除返回堆栈)。

如需详细了解如何在概览屏幕中显示和管理任务及 Activity,请参阅“最近使用的应用”屏幕

进程和应用生命周期

在大多数情况下,每个 Android 应用都在各自的 Linux 进程中运行。当需要运行应用的一些代码时,系统会为应用创建此进程,并使其保持运行,直到不再需要它且系统需要回收其内存以供其他应用使用。

应用进程的生命周期并不由应用本身直接控制,而是由系统综合多种因素来确定的,比如系统所知道的正在运行的应用部分、这些内容对用户的重要程度,以及系统中可用的总内存量。这是 Android 非常独特的一个基本功能。

应用开发者必须了解不同的应用组件(特别是 ActivityService 和 BroadcastReceiver)对应用进程的生命周期有何影响。这些组件使用不当会导致系统在应用进程正执行重要任务时将它终止。

进程生命周期错误的一个常见示例是当 BroadcastReceiver 在其 BroadcastReceiver.onReceive() 方法中接收到一个 Intent 时,它会启动一个线程,然后从该函数返回。一旦返回,则系统会认为 BroadcastReceiver 不再处于活动状态,因此不再需要其托管进程(除非其中有其他应用组件处于活动状态)。因此,系统可能会随时终止进程以回收内存,这样会终止在进程中运行的衍生线程。要解决这个问题,通常可以从 BroadcastReceiver 调度 JobService,这样系统就知道进程中还有处于活动状态的任务正在进行中。

为了确定在内存不足时应该终止哪些进程,Android 会根据每个进程中运行的组件以及这些组件的状态,将它们放入“重要性层次结构”。这些进程类型包括(按重要性排序):

    1. A 前台进程是用户目前执行操作所需的进程。在不同的情况下,进程可能会因为其所包含的各种应用组件而被视为前台进程。如果以下任一条件成立,则进程会被认为位于前台:

系统中只有少数此类进程,而且除非内存过低,导致连这些进程都无法继续运行,才会在最后一步终止这些进程。通常,此时设备已达到内存分页状态,因此必须执行此操作才能使用户界面保持响应。

  1. 可见进程正在进行用户当前知晓的任务,因此终止该进程会对用户体验造成明显的负面影响。在以下条件下,进程将被视为可见:
    • 它正在运行的 Activity 在屏幕上对用户可见,但不在前台(其 onPause() 方法已被调用)。举例来说,如果前台 Activity 显示为一个对话框,而这个对话框允许在其后面看到上一个 Activity,则可能会出现这种情况。
    • 它有一个 Service 正在通过 Service.startForeground()(要求系统将该服务视为用户知晓或基本上对用户可见的服务)作为前台服务运行。
    • 系统正在使用其托管的服务实现用户知晓的特定功能,例如动态壁纸、输入法服务等。

    相比前台进程,系统中运行的这些进程数量较不受限制,但仍相对受控。这些进程被认为非常重要,除非系统为了使所有前台进程保持运行而需要终止它们,否则不会这么做。

  2. 服务流程包含一个已使用 startService() 方法启动的 Service。虽然用户无法直接看到这些进程,但它们通常正在执行用户关心的任务(例如后台网络数据上传或下载),因此系统会始终使此类进程保持运行,除非没有足够的内存来保留所有前台和可见进程。

    已经运行了很长时间(例如 30 分钟或更长时间)的服务的重要性可能会降位,以使其进程降至下文所述的缓存 LRU 列表。这有助于避免超长时间运行的服务因内存泄露或其他问题占用大量内存,进而妨碍系统有效利用缓存进程。

  3. 缓存进程是目前不需要的进程,因此,如果其他地方需要内存,系统可以根据需要自由地终止该进程。在正常运行的系统中,这些是内存管理中涉及的唯一进程:运行良好的系统将始终有多个缓存进程可用(为了更高效地切换应用),并根据需要定期终止最早的进程。只有在非常危急(且具有不良影响)的情况下,系统中的所有缓存进程才会被终止,此时系统必须开始终止服务进程。

    这些进程通常包含用户当前不可见的一个或多个 Activity 实例(onStop() 方法已被调用并返回)。只要它们正确实现其 Activity 生命周期(详情请见 Activity),那么当系统终止此类流程时,就不会影响用户返回该应用时的体验,因为当关联的 Activity 在新的进程中重新创建时,它可以恢复之前保存的状态。

    这些进程保存在伪 LRU 列表中,列表中的最后一个进程是为了回收内存而终止的第一个进程。此列表的确切排序政策是平台的实现细节,但它通常会先尝试保留更多有用的进程(比如托管用户的主屏幕应用、用户最后看到的 Activity 的进程等),再保留其他类型的进程。还可以针对终止进程应用其他政策:比如对允许的进程数量的硬限制,对进程可持续保持缓存状态的时间长短的限制等。

在决定如何对进程进行分类时,系统会参考进程中当前活动的所有组件中最重要的级别。请参阅 ActivityService 和 BroadcastReceiver 文档,详细了解这些组件各自对进程的整体生命周期有何影响。每个类的文档都详细介绍了它们对应用的整个生命周期有何影响。

进程的优先级也可能因从属于进程的其他依赖项而提升。例如,如果进程 A 已通过 Context.BIND_AUTO_CREATE 标记绑定到 Service,或在使用进程 B 中的 ContentProvider,则进程 B 的分类始终至少和进程 A 一样重要。

Parcelable 和 Bundle

Parcelable 和 Bundle 对象可跨进程边界使用,例如与 IPC/Binder 事务之间,带有 intent 的 Activity 之间等,还可以用来存储跨配置更改的瞬时状态。本页介绍了使用 Parcelable 和 Bundle 对象的建议和最佳做法。

注意:Parcel 不是通用序列化机制,您绝不能将任何 Parcel 数据存储在磁盘上或通过网络发送。

在 Activity 之间发送数据

当应用创建 Intent 对象以在 startActivity(android.content.Intent) 中用于启动新的 Activity 时,应用可使用 putExtra(java.lang.String, java.lang.String) 方法传入参数。

以下示例代码段演示了如何执行此操作。

    val intent = Intent(this, MyActivity::class.java).apply {
        putExtra("media_id", "a1b2c3")
        // ...
    }
    startActivity(intent)
   
 

操作系统会将 intent 的基础 Bundle 打包。然后,操作系统会创建新的 Activity,将数据拆包,并将 intent 传递给新的 Activity。

我们建议您使用 Bundle 类为 Intent 对象设置操作系统已知的基元。Bundle 类针对使用 parcel 进行编组和解组进行了高度优化。

在某些情况下,您可能需要一种机制来跨 Activity 发送复合对象或复杂对象。在这种情况下,自定义类应实现 Parcelable,并提供相应的 writeToParcel(android.os.Parcel, int) 方法。它还必须提供实现 Parcelable.Creator 接口的非空字段 CREATOR,该接口的 createFromParcel() 方法用于将 Parcel 转回为当前对象。如需了解详情,请参阅 Parcelable 对象的参考文档。

通过 intent 发送数据时,应小心地将数据大小限制为几 KB。发送过多数据会导致系统抛出 TransactionTooLargeException 异常。

在进程之间发送数据

在进程之间发送数据与在 Activity 之间发送数据类似。不过,在进程之间发送时,我们建议您不要使用自定义 Parcelable。如果您将一个自定义 Parcelable 对象从一个应用发送到另一个应用,则需要确保发送和接收的应用上都存在版本完全相同的自定义类。通常情况下,这可能是在两个应用中都会使用的通用库。如果您的应用尝试向系统发送自定义 Parblelable,则可能会发生错误,因为系统无法对其不了解的类进行解组。

例如,某个应用可能会使用 AlarmManager 类设置闹钟,并对闹钟 intent 使用自定义Parcelable。当闹钟响铃时,系统会修改 intent 的 extra Bundle 以添加重复计数。此修改可导致系统从 extra 中剥离自定义 Parcelable。这种剥离进而会导致应用在收到修改后的警报 intent 时崩溃,因为应用预计会收到 extra 数据,而它已不存在。

Binder 事务缓冲区的大小固定有限,目前为 1MB,由进程中正在处理的所有事务共享。由于此限制是进程级别而不是 Activity 级别的限制,因此这些事务包括应用中的所有 binder 事务,例如 onSaveInstanceState,startActivity 以及与系统的任何互动。超过大小限制时,将引发 TransactionTooLargeException。

对于 savedInstanceState 的具体情况,应将数据量保持在较小的规模,因为只要用户可以返回到该 Activity,系统进程就需要保留所提供的数据(即使 Activity 的进程已终止)。我们建议您将保存的状态保持在 50k 数据以下。

注意:在 Android 7.0(API 级别 24)或更高版本中,系统会在运行时抛出 TransactionTooLargeException 异常。在较低版本的 Android 中,系统仅在 logcat 中显示警告。

原文地址:https://www.cnblogs.com/developer-wang/p/13066097.html