SDK 运行时概览

Android 平台使用应用沙盒的概念来维持应用代码的稳健执行和安全边界,以及维持进程边界。在应用中包含第三方代码是一种常见的做法,通常采用 SDK 的形式,例如广告 SDK 或分析 SDK。这种重用使得应用开发者能够专注于开发其应用特有的功能,同时利用领域专家的工作成果来扩展其应用的执行能力,使其超出应用本身可以轻松达到的程度。

和大多数操作系统一样,在 Android 中,SDK 在主应用的沙盒内执行,完全继承主应用的特权和权限,并且有权访问主应用的内存和存储空间。虽然这种架构能够使 SDK 和应用灵活集成,但也为在未披露的情况下收集和共享用户数据提供了可乘之机。此外,应用开发者可能并不完全了解第三方 SDK 的功能范围及其访问的数据,这使得解释其应用的数据收集和共享实践变得非常困难。

在 Android 14 中,我们添加了一项新的平台功能,让第三方 SDK 可以在专用运行时环境中运行,该功能称为 SDK 运行时。SDK 运行时可为用户数据收集和共享提供以下更强的安全保障和保证:

  • 经过修改的执行环境
  • 为 SDK 明确定义的权限和数据访问权

我们正在积极征求移动应用广告社区对于这种设计的反馈。我们也欢迎更广泛的开发者社区提供反馈,协助我们打造 SDK 运行时的未来版本,包括支持其他用例。

目标

该方案力求实现以下目标:

  • 通过进程隔离以及明确定义的 API 和数据访问权限控制,减少第三方 SDK 在未披露的情况下访问和共享用户的应用数据。如需详细了解进程隔离,请参阅本文档中的单独部分。
  • 通过限制 SDK 访问专属的永久标识符,减少第三方 SDK 在未披露的情况下跟踪用户的应用使用情况数据。
  • 通过减轻应用开发者和最终用户的负担,安全地加快向应用分发 SDK 更新的速度。如需详细了解我们提议的可信 SDK 分发模型,请参阅本文档中的另一部分。
  • 帮助应用开发者更妥善地规划应用的数据访问和共享实践。
  • 通过限制某些不安全的语言结构(例如 JNI 代码),帮助 SDK 开发者防范其他 SDK 的篡改行为。
  • 通过完全控制用于显示媒体的远程视图,帮助广告 SDK 检测和防范无效流量和广告欺诈。
  • 尽可能减少对应用开发者和 SDK 开发者的不当影响。

SDK 在独立进程中执行

我们提议的 SDK 运行时让兼容的 SDK(下文称“支持运行时 [RE] 的 SDK”)可在相应应用的单独进程中运行。平台可协助应用进程与其 SDK 运行时之间的双向通信。如需了解详情,请参阅本文档中的“通信”部分。非 RE SDK 仍会像现在一样在应用进程中运行。下图说明了这些更改:

显示运行应用进程的所有内容的之前图
在添加到 SDK 运行时之前,SDK 调用代码以及从该代码接收调用的 SDK 都位于应用进程中

显示应用进程和 SDK 运行时进程之间进程分块的“之后”示意图
在添加到 SDK 运行时之后,SDK 调用代码会在应用的前台进程中与 SDK 接口进行通信。然后,这些接口会跨越进程边界进入 SDK 运行时进程,以调用 SDK 本身。

适用于 SDK 的全新可信分发模型

这种将 SDK 与应用进行分离的提议引发了另一个分离概念,就是将 SDK 分发和应用分发进行分离。我们的方案需要可信的分发和安装机制,以确保在应用的 SDK 运行时内安装正确的 SDK。这有助于避免用户和应用开发者加载无效的 SDK,同时让应用商店显著减轻应用开发者的 SDK 分发负担。

在将 SDK 上传到应用商店进行分发之前,不再需要将 SDK 静态关联到应用,也不再需要将其与应用打包在一起。现在,取而代之的是以下过程:

  1. SDK 开发者可以将其版本化 SDK 上传到应用商店(与应用本身分开)。
  2. 应用开发者可以按版本指定 SDK 依赖项,进行构建,然后上传不包含实际 SDK 依赖项的应用版本。
  3. 当用户下载此应用时,安装进程可以使用应用的指定 SDK 依赖项,这样一来便可以从应用商店下载它们。

借助这种新颖的分发机制,SDK 开发者可以对其 SDK 进行非破坏性更改(即不更改 API 或其语义),并分发到设备,而且无需应用开发者参与。您可以部署或回滚这些非破坏性 SDK 更改,而无需等待应用开发者使用新的 SDK 重新构建其应用,也无需等待最终用户更新其应用。破坏性更改仍需要由应用开发者进行更新,但 SDK 开发者能够以更加统一的方式更快地向更多人分发其最新的非破坏性更改和修复程序,尽可能减少版本支持需求。

下图展示了我们在 SDK 分发方面提议的更改:

“之前”示意图
在引入 SDK 运行时之前,开发者会直接将其 SDK 发送到应用。

“之后”示意图
在 SDK 运行时引入后,SDK 开发者将使用 SDK 上传界面将其 SDK 发布到应用商店。 然后,应用商店会处理分发操作,将应用以及所有 SDK 依赖项分发到最终用户的设备。

对构建、运行和分发 SDK 及应用的方式进行了变更

这是初始方案,旨在实现灵活的 SDK 运行时和分发技术。下面各个部分针对以下广泛类别提议了一系列更改:

  • 访问权限:权限、内存、存储空间
  • 执行:语言、运行时更改、生命周期、媒体呈现
  • 通信:“从应用到 SDK”和“从 SDK 到 SDK”
  • 开发:如何在此模型中进行构建、调试和测试
  • 分发:如何跨 Android、应用和 SDK 的各个版本分发、更新、回滚

本文档还包含常见问题解答,以帮助解决常见问题。

这是初始设计方案。我们了解,对于生态系统的某些成员来说,这可能是一项非常有意义的改变。因此,我们正在积极征求您的反馈,您可以通过问题跟踪器将您的建议告诉我们。

访问权限

管理系统的隐私保护意味着管理不同方可以如何访问不同的资源。为了实现我们在隐私保护方面的价值主张,我们提议更新用于访问应用、SDK 和用户数据的模型,以遵循最小权限原则,进而防范在未披露的情况下访问潜在敏感数据。

SDK 权限

作为一个单独的进程,SDK 运行时将拥有自己的一组明确定义的权限,而不是继承用户授予应用的权限。根据对广告相关 SDK 所用权限进行的初步研究,我们提议 SDK 运行时内的 SDK 将默认拥有以下权限:

  • INTERNET:互联网访问权限,以便与网络服务通信。
  • ACCESS_NETWORK_STATE:网络相关信息访问权限。
  • READ_BASIC_PHONE_STATE:访问与手机状态相关的信息,例如移动网络类型。
  • 隐私保护 API 访问权限,这些 API 能够提供核心广告功能,而无需访问跨应用标识符。
  • AD_ID:能够请求广告 ID。这同样受制于应用是否拥有此权限。

我们正在调查是否需要以及如何授予其他权限,以便从隐私保护和易用性角度限制对最终用户的影响。如果您遇到任何可能无法通过这组权限满足要求的用例,可以向我们提供反馈

内存

SDK 运行时将拥有自己的独立内存空间,因为它拥有自己的进程。默认情况下,这种结构会拒绝 SDK 访问应用的内存空间,并且应用同样无法访问 SDK 的内存空间。我们建议保持这种默认行为,以便与最小权限原则保持一致。

存储空间

该方案旨在平衡 SDK 对于访问存储空间以执行其正常操作的需求,并最大限度地减少使用永久性存储空间的跨应用跟踪和跨进程跟踪。我们提议对当今的存储空间访问方式进行以下更新:

  • 应用将无法直接访问其 SDK 存储空间,反之亦然。
  • SDK 将无法访问设备的外部存储空间。
  • 在每个 SDK 运行时内,将具有可供所有 SDK 访问的存储空间,以及指定 SDK 专用的存储空间。

与当前的存储模型一样,存储空间本身没有大小方面的任意限制。SDK 可以使用存储空间来缓存资源。当 SDK 没在活跃运行时,系统会定期清除此存储空间。

执行

为了确保应用、SDK 和用户之间存在私享系统,执行上下文本身(代码格式、语言结构、可访问的 API,以及系统数据)需要强化这些隐私边界,或至少不会引入能够规避它们的可乘之机。同时,我们希望保留对富功能平台的访问权限,以及 SDK 当前拥有的大部分运行时功能。我们在此建议对运行时环境进行一组更新,以实现这种平衡。

代码

Android 代码(应用和 SDK)主要由 Android 运行时 (ART) 解读,无论代码是使用 Kotlin 还是 Java 编写的,都是如此。ART 的丰富性及其提供的语言结构,再加上比替代方案(特别是原生代码)多出的可验证性,似乎能兼顾功能和隐私保护。我们提议在支持运行时的 SDK 代码中只使用 Dex 字节码,不要支持 JNI 访问。

我们知道,有些用例(例如使用自定义的已打包 SQLite)因为使用了原生代码,需要替换为 Android SDK 的内置版 SQLite 等替代方案。

SELinux

在 Android 中,每个进程(包括作为 root 运行的进程)都使用特定的 SELinux 上下文运行,这使得内核能够管理对系统服务、文件、设备和其他进程的访问权限控制。为了保留大部分 SDK 用例,同时尽量减少对我们正在努力推进的隐私保护措施的规避行为,我们提议从非系统应用的 SELinux 上下文中为 SDK 运行时进行以下更新:

  • 将有数量有限的一组系统服务可供访问。(我们正在积极进行相关设计)
  • SDK 将只能在其 APK 中加载和执行代码。
  • 将有数量有限的一组系统属性可供访问。(我们正在积极进行相关设计)

API

允许在 SDK 运行时中使用反射和调用 API。但是,某个 SDK 不可在另一个支持运行时的 SDK 上使用反射或调用 API。我们将在日后的更新中分享被禁止的 API 的完整方案。

此外,为了加强隐私保护,近期推出的 Android 平台版本对访问永久标识符的限制越来越多。对于 SDK 运行时,我们提议进一步限制访问可用于进行跨应用跟踪的标识符。

SDK 运行时 API 只能从前台运行的应用进行访问。尝试从后台运行的应用访问 SdkSandboxManager API 会导致抛出 SecurityException

最后,无论在任何时间,RE SDK 都无法使用通知 API 来发送用户通知。

生命周期

应用 SDK 目前遵从主应用的生命周期,也就是说,当应用进入或离开前台、关闭或因内存紧张而被操作系统强行停止时,应用的 SDK 也会如此。我们提议将应用的 SDK 拆分到另一个进程中,这意味着生命周期将发生以下变化:

  • 应用可被用户或操作系统终止。之后,SDK 运行时将立即自动终止。
  • 在出现诸如以下情况时,SDK 运行时可被操作系统终止:内存紧张,或 SDK 中存在未处理的异常。

    对于 Android 14,当应用在前台运行时,SDK 运行时会以高优先级运行,并且不太可能被终止。当应用转至后台后,SDK 运行时进程的优先级会降低,将符合终止的条件。SDK 运行时进程仍保持低优先级,即使应用返回前台也不例外。因此,在内存紧张时,相较于应用,它极有可能被终止。

    对于 Android 14 及更高版本,应用的进程优先级和 SDK 运行时一致。应用的 ActivityManager.RunningAppProcessInfo.importance 进程优先级和 SDK 运行时应大致相同。

    如果 SDK 运行时在应用处于活动状态时终止(例如当 SDK 中存在未处理的异常时),SDK 运行时的状态(包括所有之前加载的 SDK 和远程视图)将会丢失。应用开发者可以使用以下任一方法处理 SDK 运行时终止:

    • 此方案为应用开发者提供了相关的生命周期回调方法,以检测 SDK 运行时已于何时终止。
    • 如果 SDK 运行时在展示广告时终止,广告可能无法按预期运行。例如,视图可能会在屏幕上卡住,无法再进行互动。如果广告视图不会影响用户体验,则应用可以将其移除。
    • 应用可以再次尝试加载 SDK 并请求广告。
    • 对于 Android 14,如果 SDK 运行时在加载了 SDK 后终止,并且应用开发者尚未注册前述生命周期回调方法,则默认情况下,应用会终止。只有已加载 SDK 的应用进程会正常终止和退出。
    • SDK 返回的要与之进行通信的 binder 对象(例如 SandboxedSdk)会在应用使用时抛出 DeadObjectException

    此生命周期模型可能会在未来的更新中发生更改。

    如果出现持续失败的情况,应用开发者应计划在不使用 SDK 的情况下进行优雅降级,或者如果 SDK 对于应用的核心功能来说至关重要,则通知用户。如需进一步详细了解这种“从应用到 SDK”的交互,请参阅本文档中的“通信”部分

非 RE SDK 可以继续使用可用于其嵌入式应用的标准操作系统基元(包括服务、activity 和广播),RE SDK 则无法使用它们。

特殊情况

以下情况不受支持,并可能导致意外行为:

  • 如果多个应用共用一个 UID,SDK 运行时可能会无法正常运行。未来可能会添加对共用 UID 的支持。
  • 对于具有多个进程的应用,应在主进程中加载 SDK。

媒体呈现

有些 SDK 是在应用指定的视图中呈现文本、图片和视频等内容。为了实现这一点,我们提议了一种远程呈现方法。在采用该方法时,SDK 将从 SDK 运行时内呈现媒体,但会使用 SurfaceControlViewHost API 以允许媒体在应用指定的视图中呈现。这使得 SDK 能够以用户专享的方式呈现相应媒体,同时有助于防范和检测与呈现的媒体之间的无效或欺诈性用户互动。

SDK 运行时内的 SDK 将支持原生广告,即那些不是由 SDK 而是由应用呈现的广告。信号收集和广告素材提取过程与呈现非原生广告时一致。我们正在对该领域进行积极研究。

插播视频广告是指在视频播放期间插播的广告,在应用内的播放器中显示。鉴于视频在应用内的播放器中播放,而不是在 SDK 中的播放器或视图中播放,因此呈现模型与其他广告格式的不同。我们正在积极探索相关机制,以便同时支持服务器端广告插播和基于 SDK 的广告插播。

系统运行状况

我们力求最大限度地减少 SDK 运行时对最终用户设备的系统运行状况影响,并正在设计相应方法。不过,一些系统资源非常有限的入门级 Android 14 设备(例如 Android(Go 版))很可能不会支持 SDK 运行时,因为会对系统运行状况产生影响。我们很快将分享成功使用 SDK 运行时需要达到的最低要求。

通信

由于应用和 SDK 目前在同一进程中运行,因此它们之间的通信不受约束且不经过任何中间环节。此外,Android 允许应用间通信,即使通信的起点和终点是 SDK,也没有问题。这种自由流动通信模型支持多种用例,但同时也为在未披露的情况下在应用之间、应用内的 SDK 之间以及应用之间共享数据提供了可乘之机。我们提议对这种通信模型进行以下更新,力争在发挥此类通信的价值和实现我们的既定目标之间取得平衡。

从应用到 SDK

应用和 SDK 之间的接口是向 SDK 进行通信的最常用通信路径,而 SDK 的 API 则是许多面向用户的独特功能和创新功能的所在之处。我们力争将 SDK 的创新功能和独特功能保留在此处。因此,我们的方案可助力 SDK 向应用公开 API,并确保应用可以受益于所有这些创新功能。

鉴于 SDK 运行时的进程边界结构,我们提议构建一个可在应用内访问的编组层,以便在应用和 SDK 之间跨此边界传送 API 调用和响应或回调。我们提议由 SDK 开发者定义该编组层的接口,并通过我们将开发的官方开源构建工具生成这个接口。

通过此方案,我们希望使应用开发者和 SDK 开发者无需再进行样板编组工作,同时让 SDK 开发者能够灵活地进行开发,并确保 SDK 代码在 SDK 运行时内运行,以实现我们的隐私保护目标。如果我们采用这种方式,则需要根据您的意见和建议设计 API 定义语言和工具。

一般交互模型将如下所示:

  • 应用通过接口调用 SDK,以便传入回调。
  • SDK 以异步方式满足请求,并使用回调进行响应。
  • 这可以泛化到任何“发布商-订阅者”模型,即意味着应用可以通过回调来订阅 SDK 中的事件,当这些事件发生时,将会触发回调。

此方案采用的这种新的跨进程结构导致的一个后果是,需要管理两个进程生命周期:一个是应用本身的进程生命周期,另一个是 SDK 运行时的进程生命周期。我们的方案旨在尽可能多地实现自动化,以便最大限度地减少对应用开发者和 SDK 开发者的影响。下图显示了我们正在考虑的一种方法:

图示
应用和 SDK 启动期间的“从应用到 SDK”交互的序列示意图。

该平台将为应用公开新的 API,以便将 SDK 动态加载到 SDK 运行时进程,接收关于进程状态更改的通知,并与加载到 SDK 运行时的 SDK 进行交互。

上图中的示意图显示了低层(没有编组层)的应用到 SDK 通信。

应用按照以下步骤与 SDK 运行时进程中运行的 SDK 进行通信:

  1. 应用必须先请求平台加载 SDK,然后才能与 SDK 交互。为了确保系统的完整性,应用将在清单文件中指定想要加载的 SDK,这些 SDK 将是唯一可加载的 SDK。

    以下代码片段提供了一个说明性的 API 示例:

    SdkSandboxManager.loadSdk(String sdkName, Bundle data, Executor executor,
        OutcomeReceiver<SandboxedSdk, LoadSdkException> receiver)
    
  2. 已加载的 SDK 会收到说明其已被加载的通知,并返回其接口。此接口应当由应用进程使用。如需在进程边界外共享该接口,必须将其作为 IBinder 对象返回。

    绑定服务指南介绍了提供 IBinder 的不同方式。无论您选择哪种方式,SDK 和调用方应用都必须保持一致。示意图以 AIDL 为例。

  3. SdkSandboxManager 收到 IBinder 接口并将其返回给应用。

  4. 应用获取 IBinder 并将其转换为 SDK 接口,调用其函数:

    IBinder binder = sandboxSdk.getInterface();
    ISdkInterface mySdkInterface = ISdkInterface.Stub.asInterface(binder);
    mySdkInterface.something();
    

应用还可以按照以下步骤从 SDK 呈现媒体:

  1. 如本文档中的“媒体呈现”部分所述,为了使应用获取 SDK 以便在视图中呈现媒体,应用可以调用 requestSurfacePackage() 并获取相应的 SurfaceControlViewHost.SurfacePackage

    以下代码片段提供了一个说明性的 API 示例:

    SdkSandboxManager.requestSurfacePackage(String sdkName, Bundle extraParams,
            Executor executor,
            OutcomeReceiver<Bundle, RequestSurfacePackageException> receiver)
    
  2. 这样一来,应用便可以通过 SurfaceView 中的 setChildSurfacePackage API 将返回的 SurfacePackage 嵌入到 SurfaceView 中。

    以下代码片段提供了一个说明性的 API 示例:

    SurfaceView.setChildSurfacePackage(SurfacePackage surfacePackage)
    

我们的方案是,IBinderrequestSurfacePackage() API 是通用的,并且不能由应用直接调用。相反,这些 API 将由生成的 API 引用(如上所述)在“shim”层中调用,以减轻应用开发者的负担。

从 SDK 到 SDK

同一应用中的两个 SDK 通常需要进行通信。当指定的 SDK 设计为包含多个组成 SDK 时,可能会发生这种情况,并且当来自不同方的两个 SDK 需要协作以满足来自调用方应用的请求时,也可能会发生这种情况。

您需要考虑以下两种主要情况:

  • 两个 SDK 均支持运行时。在这种情况下,两个 SDK 都是在 SDK 运行时内运行,并可获享其所有保护功能。SDK 无法像现在那样在应用中进行通信。因此,SdkSandboxController 中添加了一个 API,用于为所有已加载 RE-SDK 提取 SandboxedSdk 对象。这样,RE-SDK 就能与 SDK 运行时中加载的其他 SDK 进行通信。
  • 只有一个 SDK 支持运行时
    • 如果调用方 SDK 在应用内运行,这与应用本身调用 SDK 运行时内的第二个 SDK 没有什么不同。
    • 如果调用方 SDK 在 SDK 运行时内运行,我们提议使用“从应用到 SDK”部分所述的 IBinder 公开一个方法,让应用中的代码监听、处理和响应系统提供的回调。
    • 不支持运行时的广告 SDK 可能无法自行注册,我们提议创建一个中介 SDK,它将所有合作伙伴或应用 SDK 作为应用的直接依赖项包含进去并负责处理注册。此中介 SDK 可在不支持运行时的 SDK 或其他应用依赖项与支持运行时的中介(发挥适配器的作用)之间建立通信。

SDK-SDK 通信的特征集分为以下几个类别:

  • SDK 运行时内的 SDK-SDK 通信(适用于最新的开发者预览版)
  • 应用与 SDK 运行时之间的 SDK-SDK 通信(适用于最新的开发者预览版)
  • 视图和远程渲染应如何用于中介(开发中的提案)

设计基元时,考虑以下用例:

  1. 中介和出价。许多广告 SDK 都提供中介或出价功能,以便 SDK 调用各种其他 SDK 来进行广告展示(中介),或调用各种其他 SDK 来收集信号以运行竞价(出价)。通常,协调 SDK 将通过其提供的适配器调用其他 SDK。有了上述基元后,协调 SDK(无论是否为 RE)都应该能够访问所有 RE SDK 和非 RE SDK,以进行正常操作。我们正在对这种上下文中的呈现功能进行积极调查。
  2. 功能推介。有些 SDK 产品由较小的 SDK 组成,这些较小的 SDK 通过 SDK 间发现进程来确定向应用开发者公开的最终功能集。注册和发现基元应支持此用例。
  3. 发布器订阅模型。有些 SDK 设计为有一个核心的事件发布器,其他 SDK 或应用将能够订阅该发布器,以便通过回调接收通知。上面的基元应支持此用例。

从应用到应用

从应用到应用的通信是指两个通信的进程中至少有一个是支持运行时的 SDK,并且是在不披露的情况下共享数据的潜在途径。因此,SDK 运行时无法与客户端应用以外的任何应用或为其他应用创建的其他 SDK 运行时中的 SDK 建立直接通信通道。这以如下方式实现:

  • SDK 无法在其清单中定义 <service><contentprovider><activity> 等组件。
  • SDK 无法发布 ContentProvider 或发送广播。
  • SDK 可启动属于其他应用的 activity,但在 intent 中可发送的内容方面有限制。例如,不能向此 intent 添加任何 extra 或自定义操作。
  • SDK 只能启动或绑定到服务的许可名单。
  • SDK 只能访问系统 ContentProvider 的部分内容(例如 com.android.providers.settings.SettingsProvider),其中获取的数据缺少标识符,无法用于生成用户的指纹。这些检查也适用于使用 ContentResolver 访问 ContentProvider 的情况。
  • SDK 只能访问一部分受保护的广播接收器(例如 android.intent.action.AIRPLANE_MODE)。

清单标记

安装 SDK 后,PackageManager 会解析 SDK 的清单;如果存在禁止使用的清单标记,将无法安装 SDK。例如,SDK 不可定义 <service>, <activity>, <provider><receiver> 等组件,也不得在清单中声明 <permission>。SDK 运行时不支持安装失败的标记。未来的 Android 版本可能会支持安装失败后被静默忽略的标记。

SDK 用来创建 SDK 软件包的构建时工具也可应用此类检查。另外,在上传到应用商店时,也可应用此类检查。

activity 支持

SDK 运行时环境中的 SDK 无法向其清单文件添加 activity 标记,也无法使用 Context.startActivity 启动自己的 activity。相反,平台会在收到请求时为 SDK 创建 activity,并与 SDK 共享这些 activity。

平台 activity 的类型为 android.app.Activity。平台 activity 从应用的一个 activity 启动,是应用任务的一部分。不支持 FLAG_ACTIVITY_NEW_TASK

SDK 若要启动 activity,则应注册一个类型为 SdkSandboxActivityHandler 的实例,用于在应用调用 SdkSandboxManager::startSdkSandboxActivity(Activity, IBinder) 来启动 activity 时通知 activity 创建情况。

下图显示了请求 activity 的流程。

图示
显示 activity 启动流程的序列图。

开发

该方案的一个主要原则是,尽可能降低对开发者生态系统的影响。该方案为开发者提供一整套开发工具,以便他们编写、构建、调试 RE 应用和 SDK。为了确保该方案的完整性,我们将对 RE 应用和 SDK 的配置、编写和构建方式做出一些更改。

编写

Android Studio 和相关工具将更新为 SDK 运行时感知型,以便帮助确保开发者已正确配置其 RE 应用和 SDK,并确保旧版调用或不受支持的调用会更新为相关的较新替代方案。在编写阶段,我们的方案中有一些需要开发者执行的步骤。

应用开发者

应用将需要在其应用清单中指定其 RE SDK 和 SDK 证书依赖项。在我们的方案中,我们将此视为应用开发者提供的可信来源。例如:

  • 名称:SDK 或库的软件包名称。
  • 主要版本:SDK 的主要版本代码。
  • 证书摘要:SDK build 的证书摘要。对于指定的 build,我们提议 SDK 开发者通过相关的应用商店获取并注册此值。

这仅适用于通过应用商店分发的 SDK(无论是否为 RE)。静态关联 SDK 的应用将使用当前的依赖项机制。

鉴于我们的目标是尽可能降低对开发者的影响,因此做到以下一点至关重要:一旦指定了支持 SDK 运行时的目标 API 级别,应用开发者便只需要一个 build,这可以是在支持 SDK 运行时的设备上运行的 build,也可以是在不支持 SDK 运行时的设备上运行的 build。

SDK 开发者

在我们提议的设计中,RE SDK 开发者需要在清单中明确声明一个代表 SDK 或库实体的新元素。此外,还需要提供一组与依赖项类似的值,以及一个次要版本:

  • 名称:SDK 或库的软件包名称。
  • 主要版本:SDK 的主要版本代码。
  • 次要版本:SDK 的次要版本代码。

如果 RE SDK 开发者使用其他 RE SDK 作为构建时依赖项,他们可能需要按照应用开发者声明这些依赖项的方式来声明它们。依赖于非 RE SDK 的 RE SDK 将静态关联它们。如果非 RE SDK 需要 SDK 运行时不支持的功能,或者必须在应用的进程中运行,这种静态关联可能会引发一些问题,而系统会在构建时或测试通过期间检测到这些问题。

RE SDK 开发者可能希望继续支持非 RE 设备,例如搭载 Android 12 或更低版本的设备,以及系统资源非常有限的入门级 Android 14 设备(如本文档中的“系统运行状况”部分所述)。我们正在设法确保 SDK 开发者可以保留单个代码库来支持 RE 环境和非 RE 环境。

构建

应用开发者

我们预计应用开发者在构建步骤中需要执行的操作只有很小的变化。SDK 依赖项(无论是在本地分发还是通过应用商店分发,也无论是否为 RE)将需要存在于机器上,以便执行 lint 请求、编译和构建操作。我们提议 Android Studio 在正常使用情况下从应用开发者那里获取这些详细信息,并使其尽可能公开透明。

虽然我们预计调试 build 将需要包含调试 build 中存在的所有代码和符号,以实现可调试性,但发布 build 将可以选择从最终工件中移除所有通过应用商店分发的 SDK(无论是否为 RE)。

我们目前处于设计阶段的早期,并将在实现这一点时分享更多信息。

SDK 开发者

我们正在努力确保 SDK 的非 RE 版本和 RE 版本都可以内置到单个工件中以进行分发。这样一来,应用开发者将无需为 SDK 的 RE 版本和非 RE 版本分别支持单独的 build。

与应用非常相似,任何通过应用商店分发的依赖项 SDK 都将需要存在于机器上,以便执行 lint 请求、编译和构建操作,并且我们预计 Android Studio 应该能够无缝地促进这一点。

测试

应用开发者

正如我们的方案中所述,应用开发者将能够在搭载 Android 14 的设备上按照正常方式测试其应用。开发者构建应用后,应用将能够安装在 RE 设备或模拟器上。此安装过程可确保将正确的 SDK 安装到设备或模拟器的 SDK 运行时内,无论 SDK 是从远程 SDK 仓库中提取的,还是从构建系统的缓存中提取的。

SDK 开发者

SDK 开发者通常在设备和模拟器上使用内部测试应用来测试其开发成果。我们的方案不会改变这一点,而应用内验证将遵循上面针对应用开发者介绍的相同步骤,并且使用同时适用于 RE 应用和非 RE 应用的单个 build 工件。SDK 开发者将能够单步调试其代码(无论是否位于 SDK 运行时内),但在使用高级调试和性能剖析工具方面可能存在一些限制。我们正在对该领域进行积极调查。

分发

我们将应用与其 SDK 进行分离的设计方案为通过应用商店分发 SDK 创造了可能。这种可能性涵盖了所有应用商店,而非仅限于任何特定应用商店。这种方式的好处显而易见:

  • 确保 SDK 的质量和一致性。
  • 为 SDK 开发者简化发布工作。
  • 加快向已安装的应用分发 SDK 次要版本更新。

为了支持 SDK 分发,应用商店可能需要提供以下大部分功能:

  • 可供 SDK 开发者执行以下操作的机制:将其可通过应用商店分发的 SDK 上传到商店、更新这些应用、回滚这些应用,可能还会移除这些应用。
  • 可实现以下目标的机制:确保 SDK 及其来源的完整性,确保应用及其来源的可靠性,以及解析其依赖项。
  • 可实现以下目标的机制:以始终可靠且高效的方式将它们部署到设备上。

限制逐渐演变

我们预计 SDK 运行时中的代码所面临的限制将随着 Android 版本的升高而演变。为了确保应用兼容性,对于给定 SDK 级别,我们不会通过 Mainline 模块更新更改这些限制。在通过应用商店政策废弃对相应 targetSdkVersion 的支持之前,与给定 targetSdkVersion 相关联的行为将一直保留;并且与应用相比,targetSdkVersion 可能会更快废弃。 各种 Android SDK 版本的限制会经常变化,特别是在前几个版本中。

此外,我们还将构建一种 Canary 版机制,让外部和内部测试人员加入一个群组。该群组会收到针对下一版本 Android 提议的一套限制。这将有助于我们获得反馈,从而有信心地确定针对这组限制提议的更改是否可行。

常见问题解答

  1. 什么是广告相关 SDK?

    广告相关 SDK 是指在不归广告主所有的应用上,可促进出于商业目的通过消息定位用户的任何环节的 SDK。这包括但不限于可以为后续定位创建用户群组的分析 SDK、广告服务 SDK、适用于广告的防滥用和防欺诈 SDK、互动 SDK 和归因 SDK。

  2. 任何 SDK 都可以在 SDK 运行时内运行吗?

    虽然最初的侧重点是广告相关 SDK,但非广告相关 SDK 的开发者如果寻求保护隐私并相信它们可以在上述条件下运行,则可以分享有关其 SDK 在 SDK 运行时内运行的反馈。不过,SDK 运行时并非与所有 SDK 设计都兼容。除了已记录的限制,SDK 运行时可能不适合需要与主应用进行实时通信或高吞吐量通信的 SDK。

  3. 为什么选择进程隔离,而不是在进程的基于 Java 的运行时内隔离?

    我们致力于为 Android 用户提供隐私保证,但基于 Java 的运行时目前不能促进实现这一保证所必需的安全边界。尝试实现此类边界可能需要多年的努力,而且不一定能成功。因此,Privacy Sandbox 使用进程边界,这是一项经过长期测试并且易于理解的技术。

  4. 将 SDK 移入 SDK 运行时进程能否节省下载量或空间?

    如果将多个应用与相同版本且支持运行时的 SDK 集成,就能节省下载量和磁盘可用空间。

  5. SDK 在 SDK 运行时内能访问哪些类型的应用生命周期事件(例如,应用进入后台)?

    我们正积极设计相关支持,以在客户端应用发生应用级生命周期事件(例如,应用进入后台、应用进入前台)时通知 SDK 运行时。我们会在即将发布的开发者预览版中分享设计和示例代码。