SDK 執行階段開發人員指南

提供意見回饋

SDK 執行階段可讓 SDK 在獨立於通話應用程式的專屬沙箱中執行。SDK 執行階段圍繞使用資料蒐集提供增強的保護和保證措施。這些措施是利用經過改進的執行環境實現的,改進後的執行環境能夠限制資料存取權限和其他的一些權限。如要進一步瞭解 SDK 執行階段,請參閱設計提案

此頁面將逐步引導您建立啟用執行階段的 SDK,以定義可遠端轉譯至通話應用程式的網頁式檢視畫面。

事前準備

在開始之前,請先完成下列步驟:

  1. 在 Android 裝置上為 Privacy Sandbox 設定開發環境
  2. 將系統映像檔安裝到支援的裝置上,或者設定支援 Android 版 Privacy Sandbox 的模擬器

在 Android Studio 中設定專案

如要試用 SDK 執行階段,請使用與用戶端伺服器模型類似的模型。主要差別在於應用程式 (用戶端) 和 SDK (「伺服器」) 在同一個裝置上執行。

將一個應用程式模組和一個 SDK 模組新增至您的專案,方便您並排執行和測試程式碼。建議您建立兩個不同的應用程式,以確保應用程式碼和 SDK 程式碼分開處理。

視您是 SDK 開發人員還是應用程式開發人員而定,您選用的最終設定可能會與上一段所述的設定不同。

利用 Android Studio 或 Android Debug Bridge (ADB),將 SDK 安裝到測試應用程式中,方法與安裝應用程式類似。

為了協助您快速上手,我們以 Kotlin 和 Java 程式語言建立了一些範例應用程式,詳情請參閱這個 GitHub 存放區

準備 SDK

在 SDK 應用程式的 AndroidManifest.xml 檔案中,加入 <application> 區段中的 <sdk-library><property> 元素,如以下程式碼片段所示。請為啟用執行階段的 SDK 設定一個專屬的名稱,並提供版本。

<application ...>
  <sdk-library android:name="com.example.privacysandbox.provider"
               android:versionMajor="1" />
  <property
        android:name="android.sdksandbox.PROPERTY_SDK_PROVIDER_CLASS_NAME"
        android:value="com.example.provider.SdkProviderImpl" />

</application>

SDK 的進入點會擴充 SandboxedSdkProvider。如要讓 SDK 應用程式進行編譯,您必須透過覆寫方法來處理 SDK 生命週期。

initSdk()
初始化 SDK,並在初始化完成且可供使用時通知呼叫應用程式。
getView()
建立及設定廣告的檢視畫面,採用與任何其他 Android 檢視畫面相同的方式來初始化該檢視畫面,並傳回可用於遠端轉譯的檢視畫面。
onDataReceived()
處理使用 sendData() 從主機應用程式傳送的任何中繼資料。

以下程式碼片段演示了如何覆寫這些方法:

Kotlin

class SdkProviderImpl : SandboxedSdkProvider() {
    override fun initSdk(
        sandboxedSdkContext: SandboxedSdkContext, params: Bundle,
        executor: Executor, initSdkCallback: InitSdkCallback
    ) {
        // Update the callback with optional data to show that initialization
        // is complete.
        executor.execute { initSdkCallback.onInitSdkFinished(Bundle()) }
    }

    override fun getView(windowContext: Context, bundle: Bundle): View {
        val webView = WebView(windowContext)
        webView.loadUrl("https://developer.android.com/privacy-sandbox")
        return webView
    }

    override fun onDataReceived(bundle: Bundle, dataReceivedCallback: DataReceivedCallback) {
        // Update the callback with optional data to show that the receiving
        // or processing of data is complete
        dataReceivedCallback.onDataReceivedSuccess(Bundle())
    }
}

Java

public class SdkProviderImpl extends SandboxedSdkProvider {
    @Override
    public void initSdk(SandboxedSdkContext sandboxedSdkContext, Bundle params,
            Executor executor, InitSdkCallback initSdkCallback) {
        // Update the callback with optional data to show that the
        // initialization is complete.
        executor.execute(() -> initSdkCallback.onInitSdkFinished(new Bundle()));
    }

    @Override
    public View getView(Context windowContext, Bundle bundle) {
        WebView webView = new WebView(windowContext);
        webView.loadUrl("https://developer.android.com/privacy-sandbox");
        return webView;
    }

    @Override
    public void onDataReceived(Bundle bundle, DataReceivedCallback callback) {
        // Update the callback with optional data to show that the receiving or
        // processing of data is complete
        dataReceivedCallback.onDataReceivedSuccess(new Bundle());
    }
}

在 SDK 執行階段中測試影片播放器

除了支援橫幅廣告外,Privacy Sandbox 也致力於支援在 SDK 執行階段內執行的影片播放器。

測試影片播放器的流程與測試橫幅廣告類似。變更 SDK 進入點的 getView() 方法,以將影片播放器加入傳回的 View 物件中。測試預期 Privacy Sandbox 應會支援的所有影片播放器流程。請注意,SDK 和用戶端應用程式之間的影片生命週期已超過範圍,因此目前還不需要對此提供意見。

測試和意見回饋可確保 SDK 執行階段支援您偏好的影片播放器的所有用途。

以下的程式碼片段示範如何傳回從網址載入的簡易影片檢視。

Kotlin

class SdkProviderImpl : SandboxedSdkProvider() {

    override fun getView(windowContext: Context, params: Bundle): View {
        val videoView = VideoView(windowContext)
        videoView.setVideoURI(Uri.parse("https://test.website/video.mp4"))
        videoView.setOnPreparedListener { mp -> mp.start() }
        return videoView
    }
}

Java

public class SdkProviderImpl extends SandboxedSdkProvider {

    @Override
    public View getView(Context windowContext, Bundle params) {
        VideoView videoView = new VideoView(windowContext);
        videoView.setVideoURI(Uri.parse("https://test.website/video.mp4"));
        videoView.setOnPreparedListener(mp -> {
            mp.start();
        });
        return videoView;
    }
}

在 SDK 中使用 Storage API

SDK Runtime 中的 SDK 無法再存取、讀取或寫入應用程式的內部儲存空間,反之亦然。 SDK 執行階段會分配到自己的內部儲存區域,該區域保證與應用程式分隔開來。

在每個 SDK Runtime 的個別內部儲存空間中,每個 SDK 都會提供自己的儲存空間目錄 (稱為「每個 SDK 儲存空間」)。按 SDK 儲存是 SDK Runtime 內部儲存空間的邏輯區隔 (用來區分每個 SDK 使用的儲存空間用量)。

每個 SDK 都可利用 SDK 的 initSdk() 方法收到的 SandboxedSdkContext 物件上的檔案儲存空間 API,來使用其每個 SDK 的儲存空間。系統會保留個別 SDK 儲存空間,直到解除安裝用戶端應用程式,或清除用戶端應用程式資料。

SDK 只能使用內部儲存空間。因此,只有內部儲存空間 API (例如 SandboxedSdkContext.getFilesDir()SandboxedSdkContext.getCacheDir()) 才能運作。如需更多範例,請參閱「從內部儲存空間存取」。

無法透過 SDK 執行階段存取外部儲存空間。呼叫 API 以存取外部儲存空間時,系統會擲回例外狀況或傳回 null。以下提供幾個範例:

您必須使用系統提供的 SandboxedSdkContext 做為儲存空間。在任何其他 Context 物件執行個體 (例如應用程式內容) 上使用檔案儲存 API 不會使用每個 SDK 的內部儲存空間,也不保證會在所有情境或日後執行。

下列程式碼片段示範如何在 SDK Runtime 中使用儲存空間:

Java

public class SdkProviderImpl extends SandboxedSdkProvider {
    private SandboxedSdkContext mContext;

    @Override
    public void initSdk(SandboxedSdkContext sandboxedSdkContext, Bundle params,
            Executor executor, InitSdkCallback initSdkCallback) {
        // Store reference to the SandboxedSdkContext for later usage
        mContext = sandboxedSdkContext;
    }

    @Override
    public void onDataReceived(Bundle data, DataReceivedCallback callback) {
        // Use the SandboxedSdkContext to use storage
        final filename = "extraData";
        final String fileContents = data.getString("extraData", "");
        try (FileOutputStream fos = mContext.openFileOutput(filename, Context.MODE_PRIVATE)) {
            fos.write(fileContents.toByteArray());
            callback.onDataReceivedSuccess();
        } catch (Exception e) {
            callback.onDataReceivedError("Unable to process data.");
        }
    }
}

Kotlin

class SdkProviderImpl : SandboxedSdkProvider() {
    private lateinit var mContext: SandboxedSdkContext

    override fun initSdk(
        sandboxedSdkContext: SandboxedSdkContext, params: Bundle,
        executor: Executor, initSdkCallback: InitSdkCallback
    ) {
        // Store reference to the SandboxedContext for later usage
        mContext = sandboxedSdkContext;
    }

    override fun onDataReceived(data: Bundle, callback: DataReceivedCallback) {
        // Use the SandboxedSdkContext to use storage
        val filename = "myfile"
        val fileContents = data.getString("extraData", "")
        try {
            mContext.openFileOutput(filename, Context.MODE_PRIVATE).use {
                it.write(fileContents.toByteArray())
                callback.onDataReceivedSuccess()
        } catch (e: Exception) {
            callback.onDataReceivedError("Unable to process data.");
        }
    }
}

更新用戶端應用程式

如要呼叫在 SDK 執行階段中執行的 SDK,請對呼叫用戶端應用程式進行下列變更:

  1. 在包含廣告的應用程式活動中,宣告 SdkSandboxManager 的參照、用於瞭解 SDK 是否載入的布林值,以及 用於遠端轉譯的 SurfaceView 物件:

    Kotlin

    private lateinit var mSdkSandboxManager: SdkSandboxManager
    private lateinit var mClientView: SurfaceView
    private var mSdkLoaded = false
    
    companion object {
        private const val SDK_NAME = "com.example.privacysandbox.provider"
    }

    Java

    private static final String SDK_NAME = "com.example.privacysandbox.provider";
    
    private SdkSandboxManager mSdkSandboxManager;
    private SurfaceView mClientView;
    private boolean mSdkLoaded = false;
  2. 導入 LoadSdkCallback 以定義回呼類別,以在執行階段中與 SDK 互動:

    Kotlin

    private inner class RemoteSdkCallbackImpl() : RemoteSdkCallback {
        override fun onLoadSdkSuccess(params: Bundle) {
            mSdkLoaded = true
        }
    
        override fun onLoadSdkFailure(errorCode: Int, errorMessage: String) {
            // log/show error
        }
    }
    

    Java

    private class RemoteSdkCallbackImpl implements RemoteSdkCallback {
        private RemoteSdkCallbackImpl() {}
    
        @Override
        public void onLoadSdkSuccess(Bundle params) {
            mSdkLoaded = true;
        }
    
        @Override
        public void onLoadSdkFailure(int errorCode, String errorMessage) {
            // log/show error
        }
    }
    

如要在呼叫 requestSurfacePackage() 時在執行階段從 SDK 傳回遠端視圖,請定義回呼類別 RequestSurfacePackageCallback

Kotlin

private inner class RequestSurfacePackageCallbackImpl() : RequestSurfacePackageCallback {

    // Loads the remote view specified in the SDK's getView() method.
    override fun onSurfacePackageReady(
        surfacePackage: SurfacePackage,
        surfacePackageId: Int, params: Bundle
    ) {
        Handler(Looper.getMainLooper()).post {
            mClientView.setChildSurfacePackage(surfacePackage)
            mClientView.visibility = View.VISIBLE
        }
    }

    override fun onSurfacePackageError(errorCode: Int, errorMessage: String) {
        // log/show error
    }
}

Java

private class RequestSurfacePackageCallbackImpl implements RequestSurfacePackageCallback {

    // Loads the remote view specified in the SDK's getView() method.
    @Override
    public void onSurfacePackageReady(SurfacePackage surfacePackage,
            int surfacePackageId, Bundle params) {
        new Handler(Looper.getMainLooper()).post(() -> {
            mClientView.setChildSurfacePackage(surfacePackage);
            mClientView.setVisibility(View.VISIBLE);
        });
    }

    @Override
    public void onSurfacePackageError(int errorCode, String errorMessage) {
        // log/show error
    }
}

如要追蹤 sendData() 的狀態並傳回 SDK 傳回的任何資料,請定義回呼類別 SendDataCallback

Kotlin

private inner class SendDataCallbackImpl() : SendDataCallback {

    override fun onSendDataSuccess(
        params: Bundle
    ) {
        // Use the data returned by the SDK if required.
    }

    override fun onSendDataError(errorCode: Int, errorMessage: String) {
        // log/show error
    }
}

Java

private class SendDataCallbackImpl implements SendDataCallback {

    @Override
    public void onSendDataSuccess(Bundle params) {
        // Use the data returned by the SDK if required.
    }

    @Override
    public void onSendDataError(int errorCode, String errorMessage) {
        // log/show error
    }
}
  1. onCreate() 中,初始化 SdkSandboxManager、必要的回呼,然後提出顯示遠端檢視畫面的要求:

    Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mSdkSandboxManager = applicationContext.getSystemService(
                SdkSandboxManager::class.java
        )
    
        mClientView = findViewById(R.id.rendered_view)
        mClientView.setZOrderOnTop(true)
    
        val loadSdkCallback = LoadSdkCallbackImpl()
        mSdkSandboxManager.loadSdk(
                SDK_NAME, Bundle(), { obj: Runnable -> obj.run() }, loadSdkCallback
        )
    
        val sendDataCallback = SendDataCallback()
        // Send some data to the SDK if needed
        mSdkSandboxManager.sendData(SDK_NAME, Bundle(),{ obj: Runnable -> obj.run() }, sendDataCallback)
    
        val requestSurfacePackageCallback = RequestSurfacePackageCallback()
        Handler(Looper.getMainLooper()).post {
            val bundle = Bundle()
            mSdkSandboxManager.requestSurfacePackage(
                    SDK_NAME, display!!.displayId,
                    mClientView.width, mClientView.height,
                    bundle, requestSurfacePackageCallback
            )
        }
    }
    

    Java

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        mSdkSandboxManager = getApplicationContext().getSystemService(
                SdkSandboxManager.class);
    
        mClientView = findViewById(R.id.rendered_view);
        mClientView.setZOrderOnTop(true);
    
        LoadSdkCallbackImpl loadSdkCallback = new LoadSdkCallbackImpl();
        mSdkSandboxManager.loadSdk(
                SDK_NAME, new Bundle(), Runnable::run, loadSdkCallback);
    
        SendDataCallback sendDataCallback = new SendDataCallback();
        // Send some data to the SDK if needed
        mSdkSandboxManager.sendData(SDK_NAME, new Bundle(), Runnable::run, sendDataCallback);
    
        RequestSurfacePackageCallback requestSurfacePackageCallback = new RequestSurfacePackageCallback();
        new Handler(Looper.getMainLooper()).post(() -> {
            Bundle bundle = new Bundle();
            mSdkSandboxManager.requestSurfacePackage(
                    SDK_NAME, getDisplay().getDisplayId(),
                    mClientView.getWidth(), mClientView.getHeight(), bundle, requestSurfacePackageCallback);
        });
    }
    
  2. 手動指定憑證摘要。如要尋找憑證摘要,請使用 keytool 從偵錯 KeyStore 檔案中擷取憑證。預設密碼為 android

    keytool -list -keystore ~/.android/debug.keystore
    
  3. 在應用程式資訊清單檔案的 <application> 元素中,加入 <uses-sdk-library> 元素。在這個元素中,將 android:certDigest 屬性設為前一個步驟的輸出內容:

    <application ...>
      <uses-sdk-library
          android:name="com.example.basicsandboxservice"
          android:versionMajor="1"
          android:certDigest="27:76:B1:2D:...:B1:BE:E0:28:5E" />
    </application>
    

    注意:如果為 android:certDigest 輸入不正確的值,會發生下列錯誤:

    Installation failed due to: 'Failed to commit install session \
    SESSION_ID with command cmd package install-commit SESSION_ID. \
    Error: INSTALL_FAILED_MISSING_SHARED_LIBRARY: Reconciliation \
    failed...: Reconcile failed: Package PACKAGE_NAME \
    requires differently signed sdk library; failing!'

部署應用程式

在執行您的用戶端應用程式之前,請使用 Android Studio 或指令列在測試裝置上安裝 SDK 應用程式和用戶端應用程式。

透過 Android Studio 部署

透過 Android Studio 部署時,請完成下列步驟:

  1. 開啟 SDK 應用程式的 Android Studio 專案。
  2. 依序前往「Run」>「Edit Configurations」。系統隨即會顯示「Run/Debug Configuration」視窗。
  3. 在「Launch Options」下,將「Launch」設為「Nothing」,因為沒有任何活動可以啟動。
  4. 依序點選「Apply」(套用) 和「OK」(確定)
  5. 按一下「Run」(執行) ,以在您的測試裝置上安裝 SDK 應用程式。
  6. 在「Project」工具視窗中,前往您的用戶端應用程式模組。
  7. 依序前往「Run」>「Edit Configurations」。系統隨即會顯示「Run/Debug Configuration」視窗。
  8. 將「Launch Options」設為用戶端應用程式的主要活動。
  9. 依序點選「Apply」(套用) 和「OK」(確定)
  10. 按一下「Run」(執行) ,以在您的測試裝置上安裝客戶端應用程式。

透過指令列部署

在使用指令列進行部署時,請完成下列清單中的步驟。本節假設您的 SDK 應用程式模組名稱為 sdk-app,用戶端應用程式模組的名稱為 client-app

  1. 部署 SDK 應用程式:

    ./gradlew sdk-app:installDebug
    
  2. 部署用戶端應用程式:

    ./gradlew client-app:installDebug && \
      # Start the app's activity. This example uses the sample app.
      adb shell am start -n \
      com.example.privacysandbox.client/com.example.client.MainActivity
    

對您的應用程式進行偵錯

如要對用戶端應用程式進行偵錯,請按一下 Android Studio 中的「Debug」(偵錯) 按鈕。

如要對 SDK 應用程式進行偵錯,請依序前往「Run」>「Attach to Process」,以顯示彈出式視窗 (圖 1)。勾選「Show all processes」方塊。在顯示的清單中,尋找名為 sdk_sandbox_CLIENT_APP_UID 的程序。請選取這個選項,並在 SDK 應用程式的程式碼中加入中斷點,以開始對 SDK 進行偵錯。

SDK 應用程式程序會顯示在接近對話方塊底部的清單檢視畫面中
圖 1.「Choose process」畫面,可讓您選取要偵錯的 SDK 應用程式

限制

如需 SDK 執行階段的進行中功能清單,請參閱版本資訊

程式碼範例

GitHub 上的 SDK 執行階段和隱私權保護 API 存放區包含一組可幫助您入手的 Android Studio 計劃,其中包括一些展示如何初始化和呼叫 SDK 執行階段的範例。

回報錯誤和問題

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