在 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。如要在本機測試,您可以使用下列指令停用主題註冊檢查:

    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 的主要功能位於 TopicsManager 物件中的 getTopics() 方法中,如以下範例所示:

Kotlin

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

Java

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

如要使用這個方法,請初始化 TopicsManager 物件,以及接收主題資料所必需的參數。GetTopicsRequest 會傳送必要資訊來擷取 Topics API 資料,包括用來表示呼叫端是否做為觀測器的旗標。做為觀測器時,getTopics 呼叫會傳回上個週期的主題,但不會影響下一個週期的主題資料。而 OutcomeReceiver 回呼會以非同步的方式處理結果。例如:

Kotlin

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")
    }
}

Java

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

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

Java

mTopicsManager.getTopics(mTopicsRequestBuilder.build(), mExecutor, mCallback);

這個叫用會提供包含 ID 值的 Topics 物件清單,這些值會對應至開放原始碼分類中與使用者相關的主題,或對應至相關的錯誤。主題會類似於下列範例:

/Internet & Telecom/Text & Instant Messaging

如需可能傳回的主題清單,請參閱分類相關資訊。這套分類是開放原始碼,因此您可以使用本頁頂端的意見回饋按鈕提出建議,指出需要的變更。

測試

Topics API 會根據應用程式的使用情況提供相關的最新主題。這個早期版本可讓您預覽 API 的行為,我們會在日後版本中不斷改進主題的品質。

為獲得完整體驗,建議使用包含多個應用程式的測試環境,並可在其中呼叫 getTopics() 瞭解主題的選用情形。GitHub 上的 SDK 執行階段和隱私權保護 API 存放區包含一組可幫助您入門的 Android Studio 專案,其中包括一些示範如何初始化及呼叫 Topics API 的範例。

主題計算會在週期結束時進行。在預設情況下,每個週期都是 7 天,但您可以修改時間間隔,以便快速取得結果。此 Android Debug Bridge 殼層指令會將週期的長度縮短為 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 物件有三個欄位。您可以使用公開金鑰的對應私密金鑰,取得傳回主題的清單。

為了進行開發作業,您可以停用註冊檢查功能,測試 Topics API 加密功能。這會強制 API 使用測試公開金鑰來加密回應。您可以使用對應的私密金鑰解密已加密的主題。

限制

如需 Topics API 處於開發階段的功能清單,請參閱「版本資訊」。

回報錯誤和問題

您的意見回饋對 Android 版 Privacy Sandbox 至關重要。如果您發現任何問題,或希望對 Android 版 Privacy Sandbox 提出改進意見,請告訴我們

後續步驟

瞭解使用者和開發人員如何管理及自訂 Topics API,以符合使用者的偏好設定和需求。
瞭解 Topics 在 Android 上的運作方式,並瞭解 API 流程的核心步驟。

另請參閱

請參閱我們的資源,進一步瞭解 Android 上的 Topics API。