Key concepts | Set up your development environment | Build an RE SDK | Consume the RE SDK | Testing, and building for distribution |
构建支持运行时的 SDK
如需构建支持运行时的 SDK,您必须完成以下步骤:
设置项目结构
我们建议您将项目整理成以下模块:
- 应用模块 - 用于测试和开发 SDK 代表真实应用客户端将会使用的内容。您的应用应 依赖于现有广告库模块(运行时感知 SDK)。
- 现有广告库模块(运行时感知 SDK)- 一个 Android 库模块
包含现有的“未启用运行时”SDK 逻辑
关联的 SDK。
- 首先,这些功能可以拆分。例如,有些代码可以 由现有 SDK 处理,有些可以路由到支持运行时的 SDK SDK。
- 支持运行时的广告库模块 - 包含支持运行时的 SDK 业务逻辑您可以在 Android Studio 上将其作为 Android 库进行创建 模块。
- 支持运行时的 ASB 模块 - 定义将
支持运行时的 SDK 代码复制到 ASB 中。
- 您需要使用 com.android.privacy-sandbox-sdk 类型。为此,您可以创建一个 新目录
- 此模块不应包含任何代码,而应只包含一个空的 build.gradle 文件中包含支持运行时的广告库模块的依赖项。 此文件的内容在 准备 SDK。
- 请务必将此模块添加到 settings.gradle 文件中,并在 现有广告库模块
本指南中的项目结构是建议,您可以选择其他 结构,并采用相同的技术原则。您可以随时创建其他模块,对应用和库模块中的代码进行模块化处理。
准备 SDK
如需准备您的项目以进行支持运行时的 SDK 开发,您需要: 首先定义一些工具和库依赖项:
- SDK 运行时向后兼容库,这些库为
未安装 Privacy Sandbox 的设备(Android 13 及更低版本)
(
androidx.privacysandbox.sdkruntime:
) - 用于支持广告展示的界面库 (
androidx.privacysandbox.ui:
) - 用于支持 SDK API 声明和 shim 生成的 SDK 开发者工具 (
androidx.privacysandbox.tools:
)
将此标志添加到项目的 gradle.properties 文件中,以启用创建支持运行时的 SDK 的功能。
# This enables the Privacy Sandbox for your project on Android Studio. android.experimental.privacysandboxsdk.enable=true android.experimental.privacysandboxsdk.requireServices=false
修改项目的 build.gradle 以包含帮助程序 Jetpack 库和其他依赖项:
// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext.kotlin_version = '1.9.10' ext.ksp_version = "$kotlin_version-1.0.13" ext.privacy_sandbox_activity_version = "1.0.0-alpha01" ext.privacy_sandbox_sdk_runtime_version = "1.0.0-alpha13" ext.privacy_sandbox_tools_version = "1.0.0-alpha09" ext.privacy_sandbox_ui_version = "1.0.0-alpha09" repositories { mavenCentral() } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } plugins { id 'com.android.application' version '8.4.0-alpha13' apply false id 'com.android.library' version '8.4.0-alpha13' apply false // These two plugins do annotation processing and code generation for the sdk-implementation. id 'androidx.privacysandbox.library' version '1.0.0-alpha02' apply false id 'com.google.devtools.ksp' version "$ksp_version" apply false id 'org.jetbrains.kotlin.jvm' version '1.9.10' apply false } task clean(type: Delete) { delete rootProject.buildDir }
更新支持运行时的广告库 (RE SDK) 模块中的 build.gradle 文件,以包含这些依赖项。
dependencies { // This allows Android Studio to parse and validate your SDK APIs. ksp "androidx.privacysandbox.tools:tools-apicompiler:$privacy_sandbox_tools_version" // This contains the annotation classes to decorate your SDK APIs. implementation "androidx.privacysandbox.tools:tools:$privacy_sandbox_tools_version" // This is runtime dependency required by the generated server shim code for // backward compatibility. implementation "androidx.privacysandbox.sdkruntime:sdkruntime-provider:$privacy_sandbox_sdk_runtime_version" // These are runtime dependencies required by the generated server shim code as // they use Kotlin. implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1" implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1' // This is the core part of the UI library to help with UI notifications. implementation "androidx.privacysandbox.ui:ui-core:$privacy_sandbox_ui_version" // This helps the SDK open sessions for the ad. implementation "androidx.privacysandbox.ui:ui-provider:$privacy_sandbox_ui_version" // This is needed if your SDK implements mediation use cases implementation "androidx.privacysandbox.ui:ui-client:$privacy_sandbox_ui_version" }
将支持运行时的 ASB 模块中的 build.gradle 文件替换为以下代码:
plugins { id 'com.android.privacy-sandbox-sdk' } android { compileSdk 34 minSdk 21 bundle { // This is the package name of the SDK that you want to publish. // This is used as the public identifier of your SDK. // You use this later on to load the runtime-enabled SDK packageName = '<package name of your runtime-enabled SDK>' // This is the version of the SDK that you want to publish. // This is used as the public identifier of your SDK version. setVersion(1, 0, 0) // SDK provider defined in the SDK Runtime library. // This is an important part of the future backwards compatibility // support, most SDKs won't need to change it. sdkProviderClassName = "androidx.privacysandbox.sdkruntime.provider.SandboxedSdkProviderAdapter" // This is the class path of your implementation of the SandboxedSdkProviderCompat class. // It's the implementation of your runtime-enabled SDK's entry-point. // If you miss this step, your runtime-enabled SDK will fail to load at runtime: compatSdkProviderClassName = "<your-sandboxed-sdk-provider-compat-fully-qualified-class-name>" } } dependencies { // This declares the dependency on your runtime-enabled ad library module. include project(':<your-runtime-enabled-ad-library-here>') }
更新现有广告库 (RA SDK) 模块中的 build.gradle 文件,以添加以下依赖项:
dependencies { // This declares the client's dependency on the runtime-enabled ASB module. // ⚠️ Important: We depend on the ASB module, not the runtime-enabled module. implementation project(':<your-runtime-enabled-asb-module-here>') // Required for backwards compatibility on devices where SDK Runtime is unavailable. implementation "androidx.privacysandbox.sdkruntime:sdkruntime-client:$privacy_sandbox_sdk_runtime_version" // This is required to display banner ads using the SandboxedUiAdapter interface. implementation "androidx.privacysandbox.ui:ui-core:$privacy_sandbox_ui_version" implementation "androidx.privacysandbox.ui:ui-client:$privacy_sandbox_ui_version" // This is required to use SDK ActivityLaunchers. implementation "androidx.privacysandbox.activity:activity-core:$privacy_sandbox_activity_version" implementation "androidx.privacysandbox.activity:activity-client:$privacy_sandbox_activity_version" }
添加 SDK 业务逻辑
按照您在 支持运行时的广告库模块。
如果您有一个要迁移的现有 SDK,请在此阶段根据需要迁移尽可能多的业务逻辑、接口和系统功能,但请考虑将来进行完整迁移。
如果您需要使用存储空间、Google Play 广告 ID 或应用组 ID,请阅读以下部分:
在 SDK 中使用 Storage API
SDK 运行时中的 SDK 无法再访问、读取或写入应用的内部存储空间 反之亦然。
系统会为 SDK 运行时分配自己的内部存储区域,该区域与应用分隔。
SDK 能够使用 SandboxedSdkProvider#getContext()
返回的 Context
对象上的文件存储 API 来访问这个单独的内部存储空间。
SDK 只能使用内部存储空间,因此只能使用内部存储空间 API,例如 Context.getFilesDir()
或
Context.getCacheDir()
的工作。查看更多示例
从内部存储空间访问。
不支持从 SDK 运行时访问外部存储空间。调用 API 来访问外部存储空间将抛出异常或返回 null。下面列出了一些示例:
- 使用存储访问框架访问文件会抛出 SecurityException。
getExternalFilsDir()
始终返回 null。
您必须使用 SandboxedSdkProvider.getContext()
返回的 Context
进行存储。如果对任何其他 Context
对象实例(例如应用上下文)使用文件存储 API,我们无法保证在所有情况下都能按预期运行。
以下代码段演示了如何在 SDK 运行时中使用存储空间:
class SdkServiceImpl(private val context: Context) : SdkService { override suspend fun getMessage(): String = "Hello from Privacy Sandbox!" override suspend fun createFile(sizeInMb: Int): String { val path = Paths.get( context.dataDir.path, "file.txt" ) withContext(Dispatchers.IO) { Files.deleteIfExists(path) Files.createFile(path) val buffer = ByteArray(sizeInMb * 1024 * 1024) Files.write(path, buffer) } val file = File(path.toString()) val actualFileSize: Long = file.length() / (1024 * 1024) return "Created $actualFileSize MB file successfully" } }
在每个 SDK 运行时的单独内部存储空间中,每个 SDK 都有自己的存储目录。SDK 级存储空间是对 SDK 运行时内部存储空间的逻辑隔离,有助于解释每个 SDK 使用了多少存储空间。
Context
对象上的所有内部存储 API 都会为每个 SDK 返回一个存储路径。
访问 Google Play 服务提供的广告 ID
如果您的 SDK 需要访问 Google Play 服务提供的广告 ID,请使用 AdIdManager#getAdId()
异步检索该值。
访问 Google Play 服务提供的应用组 ID
如果您的 SDK 需要访问 Google Play 服务提供的应用组 ID,请使用
AppSetIdManager#getAppSetId()
用于异步检索值。
声明 SDK API
若要在运行时外部访问支持运行时的 SDK,您需满足以下条件: 来定义客户端(RA SDK 或客户端应用)可以使用的 API。
请使用注解来声明这些接口。
注释
需要在 Kotlin 中使用 以下注释:
注释 | |
---|---|
@PrivacySandboxService |
|
@PrivacySandboxInterface |
|
@PrivacySandboxValue |
|
@PrivacySandboxCallback |
|
您需要在 支持运行时的广告库模块。
如需了解这些注解的用法,请参阅以下部分。
@PrivacySandboxService
@PrivacySandboxService interface SdkService { suspend fun getMessage(): String suspend fun createFile(sizeInMb: Int): String suspend fun getBanner(request: SdkBannerRequest, requestMediatedAd: Boolean): SdkSandboxedUiAdapter? suspend fun getFullscreenAd(): FullscreenAd }
@PrivacySandboxInterface
@PrivacySandboxInterface interface SdkSandboxedUiAdapter : SandboxedUiAdapter
@PrivacySandboxValue
@PrivacySandboxValue data class SdkBannerRequest( /** The package name of the app. */ val appPackageName: String, /** * An [SdkActivityLauncher] used to launch an activity when the banner is clicked. */ val activityLauncher: SdkActivityLauncher, /** * Denotes if a WebView banner ad needs to be loaded. */ val isWebViewBannerAd: Boolean )
@PrivacySandboxCallback
@PrivacySandboxCallback interface InAppMediateeSdkInterface { suspend fun show() }
支持的类型
支持运行时的 SDK API 支持以下类型:
- Java 编程语言中的所有基元类型(例如 int、long、 char、布尔值等)
- 字符串
- 带有
@PrivacySandboxInterface
或@PrivacySandboxCallback
- 带有
@PrivacySandboxValue
注解的 Kotlin 数据类 - java.lang.List - List 中的所有元素都必须是受支持的数据之一 类型
下面是一些其他注意事项:
- 带有
@PrivacySandboxValue
注解的数据类不能包含 类型:@PrivacySandboxCallback
- 返回类型不能包含带有
@PrivacySandboxCallback
注解的类型 - 列表不能包含带有以下注解的类型的元素:
@PrivacySandboxInterface
或@PrivacySandboxCallback
异步 API
由于 SDK API 总是调用单独的进程,因此我们需要 确保这些调用不会阻塞客户端的调用线程。
为了实现这一点,带有
@PrivacySandboxService
、@PrivacySandboxInterface
和@PrivacySandboxCallback
必须明确声明为异步 API。
在 Kotlin 中,异步 API 可以通过两种方式实现:
- 使用挂起函数。
- 接受在操作完成时获得通知的回调,或者 其他事件发生。该 函数必须是一个单位。
异常
SDK API 不支持任何形式的受检异常。
生成的 shim 代码会捕获 SDK 抛出的所有运行时异常,并且
以 PrivacySandboxException
的形式将它们抛出给客户端,并提供
包含其中的原因。
界面库
如果您有表示广告的接口(例如横幅广告),还需要实现 SandboxedUiAdapter
接口,才能为已加载的广告打开会话。
这些会话在客户端和 SDK 之间形成边信道, 实现两个主要目的:
- 每当界面发生变化时都会收到通知。
- 将界面呈现的任何变化告知客户。
由于客户端可以使用带有 @PrivacySandboxService
注解的接口
与您的 SDK 进行通信,任何用于加载广告的 API 都可以添加到此
界面。
当客户端请求加载广告时,加载该广告并返回
实现 SandboxedUiAdapter
的接口。这样,客户端就可以请求该广告的打开会话。
当客户端请求打开会话时,支持运行时的 SDK 可以使用广告响应和提供的上下文创建广告视图。
为此,请创建一个实现 SandboxedUiAdapter.Session
接口的类,并在调用 SandboxedUiAdapter.openSession()
时确保调用 client.onSessionOpened()
,并将 Session
类的实例作为参数传递。
class SdkSandboxedUiAdapterImpl(
private val sdkContext: Context,
private val request: SdkBannerRequest,
) : SdkSandboxedUiAdapter {
override fun openSession(
context: Context,
windowInputToken: IBinder,
initialWidth: Int,
initialHeight: Int,
isZOrderOnTop: Boolean,
clientExecutor: Executor,
client: SandboxedUiAdapter.SessionClient
) {
val session = SdkUiSession(clientExecutor, sdkContext, request)
clientExecutor.execute {
client.onSessionOpened(session)
}
}
}
每当界面发生更改时,该类也会接收通知。您可以 使用此类来调整广告大小,或者知道配置何时发生更改。
activity 支持
如需从 Privacy Sandbox 中启动 SDK 拥有的 activity,您需要修改 SDK API 以接收 SdkActivityLauncher
对象,该对象同样由界面库提供。
例如,以下 SDK API 应该启动 activity,因此需要 SdkActivityLauncher
参数:
@PrivacySandboxInterface
interface FullscreenAd {
suspend fun show(activityLauncher: SdkActivityLauncher)
}
SDK 入口点
抽象类 SandboxedSdkProvider
封装 SDK 运行时用于与加载到其中的 SDK 进行交互的 API。
支持运行时的 SDK 必须实现此抽象类,才能生成入口点,以便 SDK 运行时能够与其通信。
为实现向后兼容性支持,我们引入了以下类:
SandboxedSdkProviderAdapter
,用于扩展SandboxedSdkProvider
并处理 SDK 加载请求,无论 SDK 运行时是否可用。此令牌在内部使用,在 ASB 模块中声明。SandboxedSdkProviderCompat
,一个模拟SandboxedSdkProvider
接口的抽象类。
shim 生成工具添加了另一层抽象:它们使用您带有 @PrivacySandboxService
注解的接口生成一个名为 AbstractSandboxedSdkProvider
的抽象类。
此类扩展 SandboxedSdkProviderCompat
,并且与添加了此类注解的接口位于同一软件包下。
// Auto-generated code.
abstract class AbstractSandboxedSdkProvider : SandboxedSdkProviderCompat {
abstract fun createMySdk(context: Context): MySdk
}
这个生成的类公开了一个抽象工厂方法,该方法接受
Context
并预期返回入口点注解接口。
此方法以 @PrivacySandboxService
接口命名,位于前缀
create
。例如,如果您的接口名为 MySdk
,工具
生成 createMySdk
。
如需完全连接您的入口点,您必须在支持运行时的 SDK 中将带有 @PrivacySandboxService
注解的接口的实现提供给生成的 AbstractSandboxedSdkProvider
。
class MySdkSandboxedSdkProvider : AbstractSandboxedSdkProvider() {
override fun createMySdk(context: Context): MySdk = MySdkImpl(context)
}
ASB 模块的更改
您需要在 ASB 模块的 build.gradle 的 compatSdkProviderClassName
字段中声明 SandboxedSdkProviderCompat
实现的完全限定类名。
这是您在上一步中实现的类,您需要修改 ASB 模块上的 build.gradle,如下所示:
bundle {
packageName = '<package name of your runtime-enabled SDK>'
setVersion(1, 0, 0)
// SDK provider defined in the SDK Runtime library.
sdkProviderClassName = "androidx.privacysandbox.sdkruntime.provider.SandboxedSdkProviderAdapter"
// This is the class that extends AbstractSandboxedSdkProvider,
// MySdkSandboxProvider as per the example provided.
compatSdkProviderClassName = "com.example.mysdk.MySdkSandboxProvider"
}
第 2 步:设置开发环境第 4 步:使用支持运行时的 SDK