Privacy Sandbox on Android 开发者预览版现已推出!了解如何开始使用,并继续提供反馈

SDK 运行时开发者指南

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

提供反馈

借助 SDK 运行时,SDK 可在与调用方应用不同的专用沙盒中运行。SDK 运行时可为用户数据收集提供增强的保护措施和安全保障。这是通过修改执行环境来实现的,修改后的执行环境可以限制数据访问权限和允许的权限集。如需详细了解 SDK 运行时,请参阅设计方案

本页上的步骤将指导您完成支持运行时的 SDK 的创建过程,该 SDK 定义了可以远程呈现到调用方应用中的基于网络的视图。

准备工作

开始之前,请先完成以下步骤:

  1. 为 Privacy Sandbox on Android 设置开发环境。 支持 SDK 运行时的工具正在积极开发中,因此本指南将要求您使用最新的 Canary 版 Android Studio。您可以并行使用此版本的 Android Studio 和您使用的其他版本,如果您无法满足此要求,请告知我们

  2. 将系统映像安装到受支持的设备上设置支持 Privacy Sandbox on Android 的模拟器

在 Android Studio 中设置项目

如需试用 SDK 运行时,请使用与客户端-服务器模型类似的模型。主要区别在于应用(客户端)和 SDK(“服务器”)在同一设备上运行。

  1. 将应用模块添加到项目中。此模块将充当驱动 SDK 的客户端。
  2. 在应用模块中,声明必要权限配置 API 专属广告服务
  3. 将一个库模块添加到项目中。此模块包含 SDK 代码。
  4. 在 SDK 模块中,声明必要权限。在此模块中无需配置 API 专属广告服务。
  5. 在库模块的 build.gradle 文件中,移除 SDK 不使用的 dependencies。在大多数情况下,您可以移除所有依赖项。您可以创建一个名称与您的 SDK 对应的新目录来实现此目的。
  6. 使用 com.android.privacy-sandbox-sdk 类型手动创建新模块。此代码与 SDK 代码捆绑在一起,用于创建可部署到设备上的 APK。您可以创建一个名称与您的 SDK 对应的新目录来实现此目的。添加一个空的 build.gradle 文件。本指南稍后将填充此文件的内容。

  7. 将以下代码段添加到 gradle.properties 文件中:

    android.experimental.privacysandboxsdk.enable=true
    
  8. 下载 TiramisuPrivacySandbox 模拟器映像,然后使用此映像(其中包含 Play 商店)创建模拟器。

根据您是 SDK 开发者还是应用开发者,最终的设置可能与上一段中所述的不同。

使用 Android Studio 或 Android 调试桥 (ADB) 将 SDK 安装到测试设备上,方法与安装应用类似。

为帮助您开始使用,我们已使用 Kotlin 和 Java 编程语言创建了示例应用,您可以在此 GitHub 代码库中找到这些应用。自述文件及清单文件中的注释说明了必须更改哪些内容,才能在稳定版 Android Studio 中运行该示例应用。

准备 SDK

  1. 手动创建一个模块级目录。它将充当您构建 SDK APK 的实现代码的封装容器。在这个新目录中,添加一个 build.gradle 文件并在其中填充以下代码段。为支持运行时的 SDK (RE-SDK) 使用唯一的名称,并提供版本。在 dependencies 部分添加库模块。

    plugins {
        id 'com.android.privacy-sandbox-sdk'
    }
    
    android {
        compileSdkPreview 'TiramisuPrivacySandbox'
        minSdkPreview 'TiramisuPrivacySandbox'
        namespace = "com.example.example-sdk"
    
        bundle {
            packageName = "com.example.privacysandbox.provider"
            sdkProviderClassName = "com.example.sdk_implementation.SdkProviderImpl"
            setVersion(1, 0, 0)
        }
    }
    
    dependencies {
        include project(':<your-library-here>')
    }
    
  2. 在实现库中创建一个类,用作 SDK 的入口点。该类的名称应与 sdkProviderClassName 的值对应,并扩展 SandboxedSdkProvider

SDK 的入口点扩展了 SandboxedSdkProviderSandboxedSdkProvider 包含 SDK 的 Context 对象,可以通过调用 getContext() 对其进行访问。只有在调用 onLoadSdk() 后,才能访问此上下文。

为使 SDK 应用能够编译,您需要替换用于处理 SDK 生命周期的方法:

onLoadSdk()

在沙盒中加载 SDK。当 SDK 准备好处理请求时,将其接口作为 IBinder 对象封装在新的 SandboxedSdk 对象内传递,以通知调用方应用。绑定服务指南介绍了提供 IBinder 的不同方式。您可以灵活选择所需方式,但 SDK 和调用方应用所用的方式必须保持一致。

AIDL 为例,您应定义一个 AIDL 文件来提供将要共享并供应用使用的 IBinder

// ISdkInterface.aidl
interface ISdkInterface {
    // the public functions to share with the App.
    int doSomthing();
}
getView()

为您的广告创建并设置视图,以初始化任何其他 Android 视图的相同方式初始化该视图,然后返回该视图以在指定宽度和高度(以像素为单位)的窗口中远程呈现。

以下代码段展示了如何替换这些方法:

Kotlin

class SdkProviderImpl : SandboxedSdkProvider() {
    override fun onLoadSdk(params: Bundle?): SandboxedSdk {
        // Returns a SandboxedSdk, passed back to the client. The IBinder used
        // to create the SandboxedSdk object is used by the app to call into the
        // SDK.
        return SandboxedSdk(SdkInterfaceProxy())
    }

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

    private class SdkInterfaceProxy : ISdkInterface.Stub() {
        fun doSomething() {
            // Implementation of the API.
        }
    }
}

Java

public class SdkProviderImpl extends SandboxedSdkProvider {
    @Override
    public SandboxedSdk onLoadSdk(Bundle params) {
        // Returns a SandboxedSdk, passed back to the client. The IBinder used
        // to create the SandboxedSdk object is used by the app to call into the
        // SDK.
        return new SandboxedSdk(new SdkInterfaceProxy());
    }

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

    private static class SdkInterfaceProxy extends ISdkInterface.Stub {
        @Override
        public void doSomething() {
            // Implementation of the API.
        }
    }
}

在 SDK 运行时中测试视频播放器

除了支持横幅广告之外,Privacy Sandbox 还致力于支持在 SDK 运行时中运行的视频播放器。

视频播放器的测试流程与横幅广告的测试流程类似。请更改 SDK 入口点的 getView() 方法,以在返回的 View 对象中添加视频播放器。测试您希望 Privacy Sandbox 支持的所有视频播放器流程。请注意,SDK 与客户端应用之间关于视频生命周期的通信不在当前讨论范围以内,因此尚不需要获取相应的反馈。

通过测试和反馈,您可以确保 SDK 运行时能够支持您的首选视频播放器的所有用例。

以下代码段演示了如何返回从网址加载的简单视频视图。

Kotlin

    class SdkProviderImpl : SandboxedSdkProvider() {

        override fun getView(windowContext: Context, bundle: Bundle, width: Int,
                height: Int): View {
            val videoView = VideoView(windowContext)
            val layoutParams = LinearLayout.LayoutParams(width, height)
            videoView.setLayoutParams(layoutParams)
            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 bundle, int width,
                int height) {
            VideoView videoView = new VideoView(windowContext);
            LinearLayout.LayoutParams layoutParams =
                    new LinearLayout.LayoutParams(width, height);
            videoView.setLayoutParams(layoutParams);
            videoView.setVideoURI(Uri.parse("https://test.website/video.mp4"));
            videoView.setOnPreparedListener(mp -> {
                mp.start();
            });
            return videoView;
        }
    }

在 SDK 中使用存储 API

SDK 运行时中的 SDK 无法再访问、读取或写入应用的内部存储空间,反之亦然。系统会为 SDK 运行时分配自己的内部存储区域,该区域可确保与应用相分离。

SDK 将能够使用 SandboxedSdkProvider#getContext() 返回的 Context 对象上的文件存储 API 访问这一单独的内部存储空间。SDK 只能使用内部存储空间,因此只能使用内部存储空间 API,例如 Context.getFilesDir()Context.getCacheDir()。如需查看更多示例,请参阅从内部存储空间访问

不支持从 SDK 运行时访问外部存储空间。调用 API 以访问外部存储空间会抛出异常或返回 null。示例如下:

在 Android 13 中,SDK 运行时中的所有 SDK 将共用分配给 SDK 运行时的内部存储空间。存储空间将持久保留,直到客户端应用被卸载或客户端应用数据被清理。

您必须使用 SandboxedSdkProvider.getContext() 返回的 Context 进行存储。无法保证在所有情况下或将来对任何其他 Context 对象实例(例如应用上下文)使用文件存储 API 能够有效运行。

以下代码段演示了如何在 SDK 运行时中使用存储空间:

Kotlin

    private static class SdkInterfaceStorage extends ISdkInterface.Stub {
    override fun doSomething() {
        val filename = "myfile"
        val fileContents = "content"
        try {
            getContext().openFileOutput(filename, Context.MODE_PRIVATE).use {
                it.write(fileContents.toByteArray())
            } catch (e: Exception) {
                throw RuntimeException(e)
            }
        }
    }
}

    

Java

    private static class SdkInterfaceStorage extends ISdkInterface.Stub {
    @Override
    public void doSomething() {
        final filename = "myFile";
        final String fileContents = "content";
        try (FileOutputStream fos = getContext().openFileOutput(filename, Context.MODE_PRIVATE)) {
            fos.write(fileContents.toByteArray());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

    

SDK 级存储空间

在每个 SDK 运行时的单独内部存储空间中,每个 SDK 都会获得自己的存储目录,称 SDK 级存储空间。SDK 级存储空间是对 SDK 运行时内部存储空间的逻辑隔离,有助于解释每个 SDK 使用了多少存储空间。

在 Android 13 中,只有一个 API 会返回 SDK 级存储空间的路径:Context#getDataDir()。我们打算将来让所有 API 都支持 SDK 级存储空间。

读取客户端的 SharedPreferences

客户端应用可以从 SharedPreferences 中选择一组要与 SdkSandbox 共享的密钥。SDK 可以使用 SdkSanboxController#getClientSharedPreferences() API 读取从客户端应用同步的数据。此 API 返回的 SharedPreferences 仅供读取。您不应向其中写入内容。

更新客户端应用

若要调用在 SDK 运行时中运行的 SDK,请对发出调用的客户端应用进行以下更改:

  1. INTERNETACCESS_NETWORK_STATE 权限添加到应用的清单中:

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    
  2. 在包含广告的应用 activity 中声明以下内容:对 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;
    
  3. 检查此设备上 SDK 运行时进程是否可用。

    1. 检查 SdkSandboxState 常量 (getSdkSandboxState())。SDK_SANDBOX_STATE_ENABLED_PROCESS_ISOLATION 表示 SDK 运行时可用。

    2. 检查调用 loadSdk() 是否成功。如果未抛出任何异常且接收器是 SandboxedSdk 的实例,则调用成功。

      • 从前台调用 loadSdk(),如果从后台调用,则会抛出 SecurityException

      • 检查 OutcomeReceiver 中是否有 SandboxedSdk 的实例,以验证是否抛出了 LoadSdkException。如有异常,则表示 SDK 运行时可能不可用。

    如果 SdkSandboxStateloadSdk 调用失败,则 SDK 运行时不可用,并且调用应回退到现有 SDK。

  4. 通过实现 OutcomeReceiver 定义一个回调类,用于在加载 SDK 后与运行时环境中的 SDK 进行交互。在以下示例中,客户端使用回调以等待该 SDK 成功加载,并在加载成功后尝试从 SDK 呈现网页视图。此步骤的后面部分提供了回调的定义。

    Kotlin

        private inner class LoadSdkOutcomeReceiverImpl private constructor() :
                OutcomeReceiver {
    
          override fun onResult(sandboxedSdk: SandboxedSdk) {
              mSdkLoaded = true
    
              val binder: IBinder = sandboxedSdk.getInterface()
              if (!binderInterface.isPresent()) {
                  // SDK is not loaded anymore.
                  return
              }
              val sdkInterface: ISdkInterface = ISdkInterface.Stub.asInterface(binder)
              sdkInterface.doSomething()
    
              Handler(Looper.getMainLooper()).post {
                  val bundle = Bundle()
                  bundle.putInt(SdkSandboxManager.EXTRA_WIDTH_IN_PIXELS, mClientView.getWidth())
                  bundle.putInt(SdkSandboxManager.EXTRA_HEIGHT_IN_PIXELS, mClientView.getHeight())
                  bundle.putInt(SdkSandboxManager.EXTRA_DISPLAY_ID, display!!.displayId)
                  bundle.putInt(SdkSandboxManager.EXTRA_HOST_TOKEN, mClientView.getHostToken())
                  mSdkSandboxManager!!.requestSurfacePackage(
                          SDK_NAME, bundle, { obj: Runnable -> obj.run() },
                          RequestSurfacePackageOutcomeReceiverImpl())
              }
          }
    
          override fun onError(error: LoadSdkException) {
                  // Log or show error.
          }
        }
    

    Java

        import static android.app.sdksandbox.SdkSandboxManager.EXTRA_DISPLAY_ID;
        import static android.app.sdksandbox.SdkSandboxManager.EXTRA_HEIGHT_IN_PIXELS;
        import static android.app.sdksandbox.SdkSandboxManager.EXTRA_HOST_TOKEN;
        import static android.app.sdksandbox.SdkSandboxManager.EXTRA_WIDTH_IN_PIXELS;
    
        private class LoadSdkOutcomeReceiverImpl
                implements OutcomeReceiver {
            private LoadSdkOutcomeReceiverImpl() {}
    
            @Override
            public void onResult(@NonNull SandboxedSdk sandboxedSdk) {
                mSdkLoaded = true;
    
                IBinder binder = sandboxedSdk.getInterface();
                if (!binderInterface.isPresent()) {
                    // SDK is not loaded anymore.
                    return;
                }
                ISdkInterface sdkInterface = ISdkInterface.Stub.asInterface(binder);
                sdkInterface.doSomething();
    
                new Handler(Looper.getMainLooper()).post(() -> {
                    Bundle bundle = new Bundle();
                    bundle.putInt(EXTRA_WIDTH_IN_PIXELS, mClientView.getWidth());
                    bundle.putInt(EXTRA_HEIGHT_IN_PIXELS, mClientView.getHeight());
                    bundle.putInt(EXTRA_DISPLAY_ID, getDisplay().getDisplayId());
                    bundle.putInt(EXTRA_HOST_TOKEN, mClientView.getHostToken());
    
                    mSdkSandboxManager.requestSurfacePackage(
                            SDK_NAME, bundle, Runnable::run,
                            new RequestSurfacePackageOutcomeReceiverImpl());
                });
            }
    
            @Override
            public void onError(@NonNull LoadSdkException error) {
                // Log or show error.
            }
        }
    

    如需在调用 requestSurfacePackage() 时从运行时的 SDK 返回远程视图,请实现 OutcomeReceiver<Bundle, RequestSurfacePackageException> 接口:

    Kotlin

        private inner class RequestSurfacePackageOutcomeReceiverImpl :
                OutcomeReceiver {
            fun onResult(@NonNull result: Bundle) {
                Handler(Looper.getMainLooper())
                        .post {
                            val surfacePackage: SurfacePackage = result.getParcelable(
                                    EXTRA_SURFACE_PACKAGE,
                                    SurfacePackage::class.java)
                            mRenderedView.setChildSurfacePackage(surfacePackage)
                            mRenderedView.setVisibility(View.VISIBLE)
                        }
            }
    
            fun onError(@NonNull error: RequestSurfacePackageException?) {
                // Error handling
            }
        }
    

    Java

        import static android.app.sdksandbox.SdkSandboxManager.EXTRA_SURFACE_PACKAGE;
    
        private class RequestSurfacePackageOutcomeReceiverImpl
                implements OutcomeReceiver {
            @Override
            public void onResult(@NonNull Bundle result) {
                new Handler(Looper.getMainLooper())
                        .post(
                                () -> {
                                    SurfacePackage surfacePackage =
                                            result.getParcelable(
                                                    EXTRA_SURFACE_PACKAGE,
                                                    SurfacePackage.class);
                                    mRenderedView.setChildSurfacePackage(surfacePackage);
                                    mRenderedView.setVisibility(View.VISIBLE);
                                });
            }
            @Override
            public void onError(@NonNull RequestSurfacePackageException error) {
                // Error handling
            }
        }
    
  5. onCreate() 中,初始化 SdkSandboxManager 和必要的回调,然后发出加载 SDK 的请求:

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

    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);
    }
    
  6. 应用可以选择与沙盒共享其默认 SharedPreferences 的某些密钥。可通过对 SdkSandbox 管理器的任何实例调用 SdkSandboxManager#addSyncedSharedPreferencesKeys(Set<String>keys) 方法来实现此目的。应用通知 SdkSandboxManager 要同步哪些密钥后,SdkSandboxManager 会将这些密钥的值同步到沙盒和 SDK,然后使用 SdkSandboxController#getClientSharedPreferences 读取它们。如需了解详情,请参阅读取客户端的 SharedPreferences

    同步后的该组密钥在应用重启后不会保留,同步到沙盒的数据会在沙盒重启时被清除。因此,应用必须在每次启动时调用 addSyncedSharedPreferencesKeys 来开始同步。

    您可以通过调用 SdkSandboxManager#removeSyncedSharedPreferencesKeys(Set<String>keys) 来移除密钥,以便修改同步后的此组密钥。如需查看正在同步的一组密钥,请使用 SdkSandboxManager#getSyncedSharedPreferencesKeys()

    我们建议您尽可能缩减此组密钥的大小,且仅在必要时使用此组密钥。如果您希望将信息传递给 SDK 以用于常规用途,请直接使用 SDK 的 SandboxedSdk 接口与 SDK 通信。使用此类 API 的一种可能情况是,应用使用的是意见征求管理平台 (CMP) SDK,而沙盒内的 SDK 则想要读取 CMP SDK 储存在默认 SharedPreferences 中的数据。

    Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        …
        // At some point, initiate the set of keys for synchronization with sandbox
        mSdkSandboxManager.addSyncedSharedPreferencesKeys(Set.of("foo", "bar"));
    }
    
    

    Java

    @Override
        protected void onCreate(Bundle savedInstanceState) {
        …
        // At some point, initiate the set of keys for synchronization with sandbox
        mSdkSandboxManager.addSyncedSharedPreferencesKeys(Set.of("foo", "bar"));
    }
    
    
  7. 为了处理 SDK 沙盒进程意外终止的情况,请为 SdkSandboxProcessDeathCallback 接口定义一个实现:

    Kotlin

        private inner class SdkSandboxLifecycleCallbackImpl() : SdkSandboxProcessDeathCallback {
            override fun onSdkSandboxDied() {
                // The SDK runtime process has terminated. To bring back up the
                // sandbox and continue using SDKs, load the SDKs again.
                val loadSdkCallback = LoadSdkOutcomeReceiverImpl()
                mSdkSandboxManager.loadSdk(
                          SDK_NAME, Bundle(), { obj: Runnable -> obj.run() },
                          loadSdkCallback)
            }
        }
    

    Java

          private class SdkSandboxLifecycleCallbackImpl
                  implements SdkSandboxProcessDeathCallback {
              @Override
              public void onSdkSandboxDied() {
                  // The SDK runtime process has terminated. To bring back up
                  // the sandbox and continue using SDKs, load the SDKs again.
                  LoadSdkOutcomeReceiverImpl loadSdkCallback =
                          new LoadSdkOutcomeReceiverImpl();
                  mSdkSandboxManager.loadSdk(
                              SDK_NAME, new Bundle(), Runnable::run, loadSdkCallback);
              }
          }
    

    如需注册此回调以接收有关 SDK 沙盒何时终止的信息,您可以随时添加以下代码行:

    Kotlin

        mSdkSandboxManager.addSdkSandboxProcessDeathCallback({ obj: Runnable -> obj.run() },
                SdkSandboxLifecycleCallbackImpl())
    

    Java

        mSdkSandboxManager.addSdkSandboxProcessDeathCallback(Runnable::run,
                new SdkSandboxLifecycleCallbackImpl());
    

    由于沙盒的状态会在其进程终止时丢失,因此已由 SDK 远程呈现的视图可能无法再正常运行。如需继续与 SDK 交互,必须重新加载这些视图,以启动新的沙盒进程。如需监控新创建的沙盒进程的状态,请使用 addSdkSandboxProcessDeathCallback() 重新注册该回调。

  8. 将 SDK 模块的依赖项添加到客户端应用的 build.gradle 中:

    dependencies {
        ...
        implementation project(':<your-sdk-module>')
        ...
    }

测试应用

如需运行客户端应用,请使用 Android Studio 或命令行将 SDK 应用和客户端应用安装到测试设备上。

通过 Android Studio 部署

通过 Android Studio 进行部署时,请完成以下步骤:

  1. 打开客户端应用的 Android Studio 项目。
  2. 依次转到 Run > Edit Configurations。此时会显示 Run/Debug Configuration 窗口。
  3. Launch Options 下,将 Launch 设为 Specspecified Activity
  4. 点击“Activity”旁边的三点状菜单,然后为您的客户端选择 Main Activity
  5. 点击 Apply,然后点击 OK
  6. 点击 Run 图标 ,在测试设备上安装客户端应用和 SDK。

通过命令行部署

通过命令行进行部署时,请完成以下列表中的步骤。本部分假定您的 SDK 应用模块的名称为 sdk-app,您的客户端应用模块的名称为 client-app

  1. 在命令行终端中,构建 Privacy Sandbox SDK APK:

    ./gradlew :client-app:buildPrivacySandboxSdkApksForDebug
    

    这会输出生成的 APK 的位置。这些 APK 均使用本地调试密钥进行了签名。在下一个命令中,您需要使用此路径。

  2. 在您的设备上安装 APK:

    adb install -t /path/to/your/standalone.apk
    
  3. 在 Android Studio 中,依次点击 Run > Edit Configurations。此时会显示 Run/Debug Configuration 窗口。

  4. Installation Options 下,将 Deploy 设置为 Default APK

  5. 点击 Apply,然后点击 OK

  6. 点击 Run 以在测试设备上安装 APK 软件包。

调试应用

如需调试客户端应用,请点击 Android Studio 中的 Debug 按钮

如需调试 SDK 应用,请依次转到 Run > Attach to Process,此时会弹出一个界面(图 1)。勾选 Show all processes 复选框。在显示的列表中,查找名为 CLIENT_APP_PROCESS_sdk_sandbox 的进程。选择此选项,并在 SDK 应用的代码中添加断点以开始调试您的 SDK。

SDK 应用进程会显示在对话框底部附近的列表视图中
图 1. Choose process 界面,您可以在其中选择要调试的 SDK 应用

从命令行启动和停止 SDK 运行时

如需为应用启动 SDK 运行时进程,请使用以下 shell 命令:

adb shell cmd sdk_sandbox start [--user <USER_ID> | current] <CLIENT_APP_PACKAGE>

同样,如需停止 SDK 运行时进程,请运行以下命令:

adb shell cmd sdk_sandbox stop [--user <USER_ID> | current] <CLIENT_APP_PACKAGE>

检查当前加载的 SDK

您可以使用 SdkSandboxManager 中的 getLoadedSdkLibrariesInfo 功能来检查当前已加载的 SDK。

限制

有关 SDK 运行时的正在开发中的功能列表,请查看版本说明

代码示例

为帮助您开始使用,GitHub 上的 SDK 运行时和隐私保护 API 代码库中提供了一组独立的 Android Studio 项目,其中包括展示如何初始化和调用 SDK 运行时的示例。

报告 bug 和问题

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