设备端个性化开发者指南

设备端个性化 (ODP) 旨在保护最终用户的信息,不让应用获取用户的确切信息。应用使用 ODP 为最终用户定制其产品和服务,但无法看到为用户进行的确切自定义(除非应用与最终用户在 ODP 之外进行直接互动)。对于使用机器学习模型或进行统计分析的应用,ODP 提供了一组服务和算法,以确保使用适当的差分隐私机制对这些模型和分析进行适当的匿名化处理。如需了解更多详情,请参阅设备端个性化中的说明。

ODP 在 IsolatedProcess 中运行开发者代码,该代码无法直接访问网络、本地磁盘或设备上运行的其他服务,但可以访问以下本地持久化数据源:

  • RemoteData - 从开发者运营的远程后端下载的不可变键值对数据(如果适用)。
  • LocalData - 由开发者在本地保留的可变键值对数据(如果适用)。
  • UserData - 平台提供的用户数据。

支持以下输出:

  • 持久性输出:这些输出可用于日后的本地处理,用于生成显示的输出、联邦学习协助的模型训练或联邦分析协助的跨设备统计分析。
    • 开发者可以将请求及其处理结果写入本地 REQUESTS 表。
    • 开发者可以将与先前请求关联的其他数据写入 EVENTS 表。
  • 显示的输出:
    • 开发者可以在 SurfaceView 内的 WebView 中返回由 ODP 呈现的 HTML。其中呈现的内容对发起调用的应用不可见。
    • 开发者可以在 HTML 输出中嵌入 ODP 提供的事件网址,以触发记录和处理用户与所呈现的 HTML 的互动。ODP 会拦截对这些网址的请求,并调用代码来生成将写入 EVENTS 表的数据。

客户端应用和 SDK 可以调用 ODP,以使用 ODP API 在 SurfaceView 中显示 HTML 内容。在 SurfaceView 中呈现的内容对调用应用不可见。客户端应用或 SDK 可以是与使用 ODP 进行开发的应用不同的实体。

ODP 服务管理希望调用 ODP 以在其界面中显示个性化内容的客户端应用。它从开发者提供的端点下载内容,并调用逻辑来对下载的数据进行后处理。它还会协调 IsolatedProcess 与其他服务和应用之间的所有通信。

客户端应用使用 OnDevicePersonalizationManager 类中的方法与 IsolatedProcess 中运行的开发者代码进行交互。在 IsolatedProcess 中运行的开发者代码扩展了 IsolatedService 类并实现 IsolatedWorker 接口。IsolatedService 需要为每个请求创建一个 IsolatedWorker 实例。

下图显示了 OnDevicePersonalizationManagerIsolatedWorker 中方法之间的关系。

OnDevicePersonalizationManagerIsolatedWorker 之间的关系示意图。

客户端应用使用 execute 方法并通过指定的 IsolatedService 调用 ODP。ODP 服务将调用转发到 IsolatedWorkeronExecute 方法。IsolatedWorker 会返回要保留的记录和要显示的内容。ODP 服务将永久性输出写入 REQUESTSEVENTS 表,并将对显示输出的不透明引用返回给客户端应用。客户端应用可以在未来的 requestSurfacePackage 调用中使用此不透明引用,以在其界面中显示任何显示内容。

持久性输出

在开发者实现的 onExecute 返回后,ODP 服务会在 REQUESTS 表中保留记录。REQUESTS 表中的每个记录都包含 ODP 服务生成的一些常见的每次请求数据,以及返回的 Rows 列表。每个 Row 都包含一个 (key, value) 对列表。每个值都是标量、字符串或 Blob。数值可在聚合后报告,字符串或 blob 数据可在应用局部或中心差分隐私后报告。开发者还可以将后续用户互动事件写入 EVENTS 表,EVENTS 表中的每条记录都与 REQUESTS 表中的一行相关联。ODP 服务会在每条记录中透明地记录时间戳以及发起调用的应用和 ODP 开发者的 APK 的软件包名称。

准备工作

在开始使用 ODP 进行开发之前,您需要设置软件包清单并启用开发者模式。

软件包清单设置

如需使用 ODP,您需要满足以下要求:

  1. AndroidManifest.xml 中的 <property> 标记,指向软件包中包含 ODP 配置信息的 XML 资源。
  2. AndroidManifest.xml 中的 <service> 标记,用于标识扩展 IsolatedService 的类,如以下示例所示。<service> 标记中的服务必须将属性 exportedisolatedProcess 设置为 true
  3. 第 1 步中指定的 XML 资源中的 <service> 标记,用于标识第 2 步中的服务类。<service> 标记还必须在标记本身中包含其他 ODP 专用设置,如第二个示例所示。

AndroidManifest.xml

<!-- Contents of AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.odpsample" >
    <application android:label="OdpSample">
        <!-- XML resource that contains other ODP settings. -->
        <property android:name="android.ondevicepersonalization.ON_DEVICE_PERSONALIZATION_CONFIG"
                  android:resource="@xml/OdpSettings"></property>
        <!-- The service that ODP binds to. -->
        <service android:name="com.example.odpsample.SampleService"
                android:exported="true" android:isolatedProcess="true" />
    </application>
</manifest>

XML 资源中的 ODP 专用清单

<property> 标记中指定的 XML 资源文件还必须在 <service> 标记中声明服务类,并指定 ODP 将从中下载内容以填充 RemoteData 表的网址端点,如以下示例所示。如果您使用的是联合计算功能,则还需要指定联合计算客户端将连接到的联合计算服务器网址端点。

<!-- Contents of res/xml/OdpSettings.xml -->
<on-device-personalization>
   <!-- Name of the service subclass -->
   <service name="com.example.odpsample.SampleService">
     <!-- If this tag is present, ODP will periodically poll this URL and
          download content to populate REMOTE_DATA. Developers that do not need to
          download content from their servers can skip this tag. -->
     <download-settings url="https://example.com/get" />
     <!-- If you want to use federated compute feature to train a model, you
          need to specify this tag. -->
     <federated-compute-settings url="https://fcpserver.example.com/" />
   </service>
</on-device-personalization>

启用开发者模式

按照 Android Studio 文档中启用开发者选项部分中的说明启用开发者模式。

开关和标志设置

ODP 有一组用于控制某些功能的开关和标志:

  • _global_killswitch:用于所有 ODP 功能的全局开关;设置为 false 可使用 ODP
  • _federated_compute_kill_switch:此开关用于控制 ODP 的所有训练(联邦学习)功能;设置为 false 即可使用训练
  • _caller_app_allowlist:控制允许调用 ODP 的用户。您可以在此处添加应用(pkg 名称,[可选] 证书),或将其设置为 * 以允许全部
  • _isolated_service_allowlist:用于控制哪些服务可以在独立服务进程中运行。

您可以运行以下命令,将所有开关和标志配置为无限制地使用 ODP:

# Set flags and killswitches
adb shell device_config set_sync_disabled_for_tests persistent
adb shell device_config put on_device_personalization global_kill_switch false
adb shell device_config put on_device_personalization federated_compute_kill_switch false
adb shell device_config put on_device_personalization caller_app_allow_list \"*\"
adb shell device_config put on_device_personalization isolated_service_allow_list \"*\"

设备端 API

如需了解 ODP,请参阅 Android API 参考文档

IsolatedService 的互动

IsolatedService 类是一个抽象基类,所有打算针对 ODP 进行开发的开发者都必须扩展此类,并在其软件包清单中声明其在隔离的进程中运行。ODP 服务在一个独立进程中启动此服务,并向该服务发出请求。IsolatedService 会接收来自 ODP 服务的请求,并创建 IsolatedWorker 来处理请求。

开发者需要从 IsolatedWorker 接口实现这些方法,以处理客户端应用请求、下载完成以及由所呈现的 HTML 触发的事件。所有这些方法都有默认的无操作实现,因此开发者可以跳过实现不感兴趣的方法。

OnDevicePersonalizationManager 类提供了一个 API,供应用和 SDK 与在独立进程中运行的开发者实现的 IsolatedService 进行交互。以下是一些预期用例:

生成要在 SurfaceView 中显示的 HTML 内容

如需生成要显示的内容,通过 OnDevicePersonalizationManager#execute,发起调用的应用可以在后续的 requestSurfacePackage 调用中使用返回的 SurfacePackageToken 对象,以请求在 SurfaceView 中呈现结果。

成功后,系统会使用 ODP 服务呈现的 View 的 SurfacePackage 调用接收器。客户端应用需要将 SurfacePackage 插入其 View 层次结构中的 SurfaceView

当应用使用先前的 OnDevicePersonalizationManager#execute 返回的 SurfacePackageToken 进行 requestSurfacePackage 调用时,ODP 服务会调用 IsolatedWorker#onRender 来提取要在围栏帧中呈现的 HTML 代码段。在此阶段,开发者无权访问 LocalDataUserData。这样可以防止开发者在所生成的 HTML 中的素材资源提取网址中嵌入可能敏感的 UserData。开发者可以使用 IsolatedService#getEventUrlProvider 生成要包含在生成的 HTML 中的跟踪网址。呈现 HTML 时,ODP 服务会拦截对这些网址的请求,并调用 IsolatedWorker#onEvent。您可以在实现 onRender() 时调用 getRemoteData()

跟踪 HTML 内容中的事件

EventUrlProvider 类提供了用于生成事件跟踪网址的 API,开发者可以将这些网址添加到其 HTML 输出中。呈现 HTML 后,ODP 会使用事件网址的载荷调用 IsolatedWorker#onEvent

ODP 服务会在呈现的 HTML 中拦截对 ODP 生成的事件网址的请求,调用 IsolatedWorker#onEvent 并将返回的 EventLogRecord 记录到 EVENTS 表中。

写入永久性结果

借助 OnDevicePersonalizationManager#execute,该服务可以选择将数据写入永久存储空间(REQUESTSEVENTS 表)。以下是可写入这些表中的条目:

  • 要添加到 REQUESTS 表中的 RequestLogRecord
  • 要添加到 EVENTS 表的 EventLogRecord 对象的列表,每个对象都包含指向之前写入的 RequestLogRecord 的指针。

设备端存储空间中的持久结果可供联合学习用于模型训练。

管理设备端训练任务

当联合计算训练作业开始并希望获取采用 ODP 的开发者提供的训练示例时,ODP 服务会调用 IsolatedWorker#onTrainingExample。您可以在实现 onTrainingExample() 时调用 getRemoteData()getLocalData()getUserData()getLogReader()

如需安排或取消联邦计算作业,您可以使用 FederatedComputeScheduler 类,该类为所有 ODP IsolatedService 提供 API。每个联合计算作业都可以通过其总体名称进行标识。

在安排新的联合计算作业之前,请先执行以下操作:

  • 远程联邦计算服务器上应该已创建使用此填充名称的任务。
  • 应该已在软件包清单设置中使用 federated-compute-settings 标记指定联合计算服务器网址端点。

与永久性输出的交互

以下部分介绍了如何与 ODP 中的持久性输出进行交互。

读取本地表

LogReader 类提供了用于读取 REQUESTSEVENTS 表的 API。这些表包含 IsolatedServiceonExecute()onEvent() 调用期间写入的数据。这些表中的数据可用于联邦学习协助的模型训练,或联邦分析协助的跨设备统计分析。

与已下载内容的互动

以下部分介绍了如何在 ODP 中与下载的内容互动。

从服务器下载内容

ODP 服务会定期从 IsolatedService 的软件包清单中声明的网址下载内容,并在下载完成后调用 onDownloadCompleted。下载内容是一个包含键值对的 JSON 文件。

采用 ODP 的开发者可以选择应将已下载内容的哪个子集添加到 RemoteData 表中,以及删除哪个子集。开发者无法修改下载的内容,这可确保 RemoteData 表不包含任何用户数据。此外,开发者可以根据自己的选择自由填充 LocalData 表;例如,他们可以缓存一些预计算结果。

下载请求格式

ODP 会定期轮询开发者软件包清单中声明的网址端点,以提取内容来填充 RemoteData 表。

该端点应返回 JSON 响应,如后文所述。JSON 响应必须包含用于标识所发送数据版本的 syncToken,以及要填充的键值对列表。syncToken 值必须为以秒为单位的时间戳,并限制在 UTC 小时边界内。在下载请求中,ODP 会将之前完成的下载的 syncToken 和设备所在的国家/地区作为下载网址中的 syncToken 和 country 参数提供。服务器可以使用之前的 syncToken 来实现增量下载。

下载文件格式

下载的文件是一个 JSON 文件,具有以下结构。JSON 文件应包含 syncToken,以标识要下载的数据的版本。syncToken 必须是经过限值处理(限制在小时边界内)的 UTC 时间戳,并且必须大于上次下载的 syncToken。如果 syncToken 不满足这两个要求,系统会舍弃下载的内容,而不进行处理。

contents 字段是一系列(键、数据、编码)元组。key 应为 UTF-8 字符串。encoding 字段是一个可选参数,用于指定 data 字段的编码方式 - 它可以设置为“utf8”或“base64”,默认情况下假定为“utf8”。在调用 onDownloadCompleted(). 之前,key 字段会转换为 String 对象,data 字段会转换为字节数组。

{
  // syncToken must be a UTC timestamp clamped to an hour boundary, and must be
  // greater than the syncToken of the previously completed download.
  "syncToken": <timeStampInSecRoundedToUtcHour>,
  "contents": [
    // List of { key, data } pairs.
    { "key": "key1",
      "data": "data1"
    },
    { "key": "key2",
      "data": "data2",
      "encoding": "base64"
    },
    // ...
  ]
}

服务器端 API

本部分介绍如何与联合计算服务器 API 进行交互。

联邦计算服务器 API

如需在客户端安排联邦计算作业,您需要在远程联邦计算服务器上创建具有填充名称的任务。在本部分,我们将介绍如何在联合计算服务器上创建此类任务。

联合计算客户端-服务器拓扑图。

为任务构建器创建新任务时,ODP 开发者应提供两组文件:

  1. 通过调用 tff.learning.models.save_functional_model API 调用保存的 tff.learning.models.FunctionalModel 模型。您可以在我们的 GitHub 代码库中找到一个示例
  2. fcp_server_config.json,其中包含政策、联邦学习设置和差分隐私设置。以下是 fcp_server_config.json 示例:
{
  # Task execution mode.
  mode: TRAINING_AND_EVAL
  # Identifies the set of client devices that participate.
  population_name: "mnist_cnn_task"
  policies {
    # Policy for sampling on-device examples. It is checked every
    # time a device is attempting to start a new training.
    min_separation_policy {
      # The minimum separation required between two successful
      # consective task executions. If a client successfully contributes
      # to a task at index `x`, the earliest they can contribute again
      # is at index `(x + minimum_separation)`. This is required by
      # DP.
      minimum_separation: 1
    }
    data_availability_policy {
      # The minimum number of examples on a device to be considered
      # eligible for training.
      min_example_count: 1
    }
    # Policy for releasing training results to developers adopting ODP.
    model_release_policy {
      # The maximum number of training rounds.
      num_max_training_rounds: 512
    }
  }

  # Federated learning setups. They are applied inside Task Builder.
  federated_learning {
    # Use federated averaging to build federated learning process.
    # Options you can choose:
      # * FED_AVG: Federated Averaging algorithm
      #            (https://arxiv.org/abs/2003.00295)
      # * FED_SGD: Federated SGD algorithm
      #            (https://arxiv.org/abs/1602.05629)
    type: FED_AVG
    learning_process {
      # Optimizer used at client side training. Options you can choose:
      # * ADAM
      # * SGD
      client_optimizer: SGD
      # Learning rate used at client side training.
      client_learning_rate: 0.02
      # Optimizer used at server side training. Options you can choose:
      # * ADAM
      # * SGD
      server_optimizer: SGD
      # Learning rate used at server side training.
      server_learning_rate: 1.0
      runtime_config {
        # Number of participating devices for each round of training.
      report_goal: 2
      }
      metrics {
        name: "sparse_categorical_accuracy"
      }
    }
    evaluation {
      # A checkpoint selector controls how checkpoints are chosen for
      # evaluation. One evaluation task typically runs per training
      # task, and on each round of execution, the eval task
      # randomly picks one checkpoint from the past 24 hours that has
      # been selected for evaluation by these rules.
      # Every_k_round and every_k_hour are definitions of quantization
      # buckets which each checkpoint is placed in for selection.
      checkpoint_selector: "every_1_round"
      # The percentage of a populate that should delicate to this
      # evaluation task.
      evaluation_traffic: 0.2
      # Number of participating devices for each round of evaluation.
      report_goal: 2
    }
  }

  # Differential Privacy setups. They are enforced inside the Task
  # Builder.
  differential_privacy {
    # * fixed_gaussian: DP-SGD with fixed clipping norm described in
    #                   "Learning Differentially Private Recurrent
    #                   Language Models"
    #                   (https://arxiv.org/abs/1710.06963).
    type: FIXED_GAUSSIAN
    #   The value of the clipping norm.
    clip_norm: 0.1
    # Noise multiplier for the Gaussian noise.
    noise_multiplier: 0.1
  }
}

您可以在我们的 GitHub 代码库中找到更多示例

准备好这两项输入后,调用任务构建器以构建工件并生成新任务。我们的 GitHub 代码库中提供了更详细的说明