在 Android 上实现 Topics API

设置

如需实现 Topics API,您需要先设置开发环境。执行以下设置步骤:

  1. 使用最新版 Android Privacy Sandbox SDK 获取最新 可保护隐私的 API 版本

  2. 将以下内容添加到您的清单中:

    • 权限:添加 ACCESS_ADSERVICES_TOPICS 权限,以允许您的应用访问 Topics API:

      <uses-permission android:name="android.permission.ACCESS_ADSERVICES_TOPICS" />
      
    • 广告服务配置:在清单的 <application> 元素中引用广告服务配置文件。

      <property android:name="android.adservices.AD_SERVICES_CONFIG"
      android:resource="@xml/ad_services_config" />
      

      指定清单中引用的广告服务 XML 资源,例如 res/xml/ad_services_config.xml。您可以使用 allowAllToAccess 属性授予对所有 SDK 的访问权限,也可以使用 allowSdksToAccess 属性授予对各个 SDK 的访问权限:详细了解广告服务权限和 SDK 访问权限控制

      <ad-services-config>
          <topics allowAllToAccess="true"/>
      </ad-services-config>
      
  3. 在 Privacy Sandbox 中注册您的广告技术平台,以便在您的 SDK 中调用 Topics API。如需在本地进行测试,您可以使用以下命令停用 Topics 注册检查:

    adb shell setprop debug.adservices.disable_topics_enrollment_check true
    
  4. 启用对 Topics API 的访问权限。默认情况下,Topics API 处于停用状态。您需要使用 ADB 命令启用它:

    adb shell device_config put adservices ppapi_app_signature_allow_list \"\*\"
    adb shell setprop debug.adservices.disable_topics_enrollment_check true
  5. 通过对 JavaKotlin 版本的示例应用进行分叉来开始实现,以便熟悉如何在设备上检索主题。

请求一组主题

Topics API 的主要功能位于 getTopics() 方法中 在TopicsManager内 对象,如以下示例中所示:

fun getTopics(
        getTopicsRequest: GetTopicsRequest,
        executor: Executor,
        callback: OutcomeReceiver<GetTopicsResponse, Exception>
    ) { }
public void getTopics (@NonNull GetTopicsRequest getTopicsRequest,
    @NonNull Executor executor,
    @NonNull OutcomeReceiver<GetTopicsResponse, Exception> callback)

如需使用此方法,请初始化 TopicsManager 对象以及接收主题数据所需的参数。GetTopicsRequest 用于传递所需信息以检索 Topics API 数据,其中包括一个用于指示调用方是否作为观察者的标志。作为观察者时,getTopics 调用将返回上一个周期的主题,但不会影响下一个周期的主题数据。OutcomeReceiver 回调会异步处理结果。例如:

KotlinJava
private fun topicGetter() {
    val mContext = baseContext
    val mTopicsManager = mContext.getSystemService(TopicsManager::class.java)
    val mExecutor: Executor = Executors.newCachedThreadPool()
    val shouldRecordObservation = false
    val mTopicsRequestBuilder: GetTopicsRequest.Builder = GetTopicsRequest.Builder()
    mTopicsRequestBuilder.setAdsSdkName(baseContext.packageName)
    mTopicsRequestBuilder.setShouldRecordObservation(shouldRecordObservation)
    mTopicsManager.getTopics(mTopicsRequestBuilder.build(), mExecutor,
        mCallback as OutcomeReceiver<GetTopicsResponse, Exception>)
}
private var mCallback: OutcomeReceiver<GetTopicsResponse, java.lang.Exception> =
object : OutcomeReceiver<GetTopicsResponse, java.lang.Exception> {
    override fun onResult(result: GetTopicsResponse) {
        // handle successful result
        val topicsResult = result.topics
        for (i in topicsResult.indices) {
            Log.i("Topic", topicsResult[i].getTopicId().toString())
        }
        if (topicsResult.size == 0) {
            Log.i("Topic", "Returned Empty")
        }
    }
    override fun onError(error: java.lang.Exception) {
        // handle error
        Log.i("Topic", "Error, did not return successfully")
    }
}
public void TopicGetter() {
    @NonNull Context mContext = getBaseContext();
    TopicsManager mTopicsManager = mContext.getSystemService(TopicsManager.class);
    Executor mExecutor = Executors.newCachedThreadPool();
    boolean shouldRecordObservation = false;
    GetTopicsRequest.Builder mTopicsRequestBuilder = new GetTopicsRequest.Builder();
    mTopicsRequestBuilder.setAdsSdkName(getBaseContext().getPackageName());
    mTopicsRequestBuilder.setShouldRecordObservation(shouldRecordObservation);
    mTopicsManager.getTopics(mTopicsRequestBuilder.build(), mExecutor, mCallback);
}
OutcomeReceiver mCallback = new OutcomeReceiver<GetTopicsResponse, Exception>() {
    @Override
    public void onResult(@NonNull GetTopicsResponse result) {
        //Handle Successful Result
        List<Topic> topicsResult = result.getTopics();
        for (int i = 0; i < topicsResult.size(); i++) {
            Log.i("Topic", topicsResult.get(i).getTopicId().toString());
        }
        if (topicsResult.size() == 0) {
            Log.i("Topic", "Returned Empty");
        }
    }
    @Override
    public void onError(@NonNull Exception error) {
        // Handle error
        Log.i("Topic", "Experienced an error, and did not return successfully");
    }
};

设置就绪后,您就可以调用 getTopics() 方法来接收 GetTopicsResponse 结果:

Kotlin Java
mTopicsManager.getTopics(mTopicsRequestBuilder.build(), mExecutor,
        mCallback as OutcomeReceiver<GetTopicsResponse, java.lang.Exception>)
mTopicsManager.getTopics(mTopicsRequestBuilder.build(), mExecutor, mCallback);

此调用会提供包含 ID 值的 Topics 对象列表 开源分类中与以下主题相关的主题: 或相关错误。这些主题对象列表将类似于以下示例:

/Internet & Telecom/Text & Instant Messaging

如需查看可能返回的主题列表,请参阅分类。此分类是开源的,您可以使用此页面顶部的反馈按钮提交建议的更改。

测试

Topics API 会根据应用使用情况提供最新的相关主题。此早期版本可预览 API 行为,我们会在将来的版本中提高主题的质量。

为了获得最充分的体验,建议您使用含有多个应用的测试环境,在该环境中调用 getTopics() 来查看如何选择主题。通过 SDK 运行时和隐私保护 API 代码库 包含一组独立的 Android Studio 项目,以帮助您 ,其中包括展示如何初始化和调用 Topics API 的示例。

主题的计算在一个周期结束时进行。默认情况下,每个周期为 7 天,但您可以修改获取结果的这一时间间隔。以下 Android 调试桥 shell 命令可以将周期长度缩短为 5 分钟:

adb shell device_config put adservices topics_epoch_job_period_ms 300000

您可以使用 get 确认 topics_epoch_job_period_ms 值:

adb shell device_config get adservices topics_epoch_job_period_ms

如需手动触发周期计算,请执行以下命令:

adb shell cmd jobscheduler run -f com.google.android.adservices.api 2

除了使用示例应用之外,您还可以使用 Colab 针对主题分类器测试不同的应用信息组合。使用此 Colab 查看您的应用在调用 getTopics 时可能会获得的结果类型。

加密详细信息

随着加密功能的引入,调用 GetTopics() 现在会生成 包含 EncryptedTopic 对象列表的响应。解密这些结果将会 生成的对象与前一个 Topic 对象具有相同的 JSON 格式。

Topics API 支持 HPKE(混合公钥)的一次性实现 加密)。我们希望已注册的调用方在注册期间提供的公共加密网址端点上托管 32 位公钥。这些密钥应采用 Base64 编码。

EncryptedTopic 对象有 3 个字段。返回的主题列表可以是 获取此值。

出于开发目的,您可以通过停用注册检查来测试 Topics API 加密。这会强制 API 使用测试公钥来加密您的响应。您可以使用 相应的私钥。

限制

有关 Topics API 的正在开发中的功能列表,请查看版本说明

报告 bug 和问题

您的反馈对 Privacy Sandbox on Android 至关重要。如果发现任何问题或有任何关于改进 Privacy Sandbox on Android 的想法,欢迎告诉我们。

<ph type="x-smartling-placeholder"></ph> 后续步骤

了解用户和开发者如何管理和自定义 Topics API,以满足用户的偏好和需求。
了解 Topics 在 Android 上的工作原理,并了解 API 流程的核心步骤。

See also

Check out our resources to better understand the Topics API on Android.