안내가 다를 수 있으므로 Android의 개인 정보 보호 샌드박스 문서를 읽으면서 개발자 프리뷰 또는 베타 버튼을 사용하여 작업 중인 프로그램 버전을 선택하세요.
SDK 런타임을 사용하면 SDK가 호출 앱과는 별도인 전용 샌드박스에서 실행될 수 있습니다. SDK 런타임은 사용자 데이터 수집에 관한 향상된 보호 장치와 보장을 제공합니다. 이는 데이터 액세스 권한과 허용된 권한 집합을 제한하는 수정된 실행 환경을 통해 이루어집니다. SDK 런타임에 관한 자세한 내용은 설계 제안을 참고하세요.
이 페이지의 단계는 호출 앱에 원격으로 렌더링될 수 있는 웹 기반 뷰를 정의하는 런타임 지원 SDK를 만드는 프로세스를 안내합니다.
알려진 제한사항
SDK 런타임의 진행 중인 기능 목록은 출시 노트를 참고하세요.
다음 제한사항은 다음 메이저 Android 플랫폼 버전에서 수정될 예정입니다.
- 스크롤 가능한 뷰 내 광고 렌더링. 예를 들어
RecyclerView
가 제대로 작동하지 않습니다.- 크기 조절 시 버벅거림이 발생할 수 있습니다.
- 사용자 터치 스크롤 이벤트가 런타임에 제대로 전달되지 않습니다.
- Storage API
- SDK별 저장소는 Android 13에서 사용할 수 없습니다.
다음 문제는 2023년에 수정될 예정입니다.
getAdId
및getAppSetId
API를 위한 지원이 아직 활성화되지 않았으므로 이 두 API가 아직 제대로 작동하지 않습니다.
시작하기 전에
시작하기 전에 다음 단계를 완료하세요.
Android의 개인 정보 보호 샌드박스의 개발 환경을 설정합니다. 도구를 사용하여 SDK 런타임을 지원하는 기능은 현재 개발 중이므로 이 가이드에서는 최신 Android 스튜디오 Canary 버전을 사용해야 합니다. 이 버전의 Android 스튜디오는 사용 중인 다른 버전과 동시에 실행할 수 있으므로 이 요구사항이 적합하지 않은 경우 Google에 알려주세요.
지원되는 기기에 시스템 이미지를 설치하거나 Android의 개인 정보 보호 샌드박스 지원이 포함된 에뮬레이터를 설정합니다.
Android 스튜디오에서 프로젝트 설정
SDK 런타임을 사용해 보려면 클라이언트-서버 모델과 유사한 모델을 사용하세요. 주요 차이점은 앱(클라이언트)과 SDK('서버')가 동일한 기기에서 실행된다는 점입니다.
- 프로젝트에 앱 모듈을 추가합니다. 이 모듈은 SDK를 구동하는 클라이언트 역할을 합니다.
- 앱 모듈에서 SDK 런타임을 사용 설정하고 필요한 권한을 선언하고 API별 광고 서비스를 구성합니다.
- 프로젝트에 라이브러리 모듈 1개를 추가합니다. 이 모듈에는 SDK 코드가 포함됩니다.
- SDK 모듈에서 필요한 권한을 선언합니다. 이 모듈에서는 API별 광고 서비스를 구성할 필요가 없습니다.
- SDK가 사용하지 않는 라이브러리 모듈의
build.gradle
파일에서dependencies
를 삭제합니다. 대부분의 경우 종속 항목을 모두 삭제할 수 있습니다. 그렇게 하려면 SDK에 해당하는 이름의 새 디렉터리를 만들면 됩니다. com.android.privacy-sandbox-sdk
유형을 사용하여 직접 새 모듈을 만듭니다. 이는 기기에 배포할 수 있는 APK를 만들기 위해 SDK 코드와 함께 번들로 제공됩니다. 그렇게 하려면 SDK에 해당하는 이름의 새 디렉터리를 만들면 됩니다. 빈build.gradle
파일을 추가합니다. 이 파일의 콘텐츠는 이 가이드의 뒷부분에서 채워집니다.gradle.properties
파일에 다음 스니펫을 추가합니다.android.experimental.privacysandboxsdk.enable=true
TiramisuPrivacySandbox 에뮬레이터 이미지를 다운로드하고 Play 스토어가 포함된 이 이미지로 에뮬레이터를 만듭니다.
SDK 개발자인지 앱 개발자인지에 따라 최종 설정이 이전 단락에서 설명한 것과 다를 수 있습니다.
앱을 설치하는 방법과 유사하게 Android 스튜디오나 Android 디버그 브리지(adb)를 사용하여 테스트 기기에 SDK를 설치합니다. 시작하는 데 도움이 되도록 Kotlin 및 자바 프로그래밍 언어로 샘플 앱을 만들었으며 이 GitHub 저장소에서 확인할 수 있습니다. 리드미 및 매니페스트 파일에는 안정화 버전의 Android 스튜디오에서 샘플을 실행하려면 무엇을 변경해야 하는지 설명하는 주석이 있습니다.
SDK 준비
수동으로 모듈 수준 디렉터리를 만듭니다. 이는 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>') }
구현 라이브러리에 SDK의 진입점 역할을 하는 클래스를 만듭니다. 클래스 이름은
sdkProviderClassName
값에 매핑되고SandboxedSdkProvider
를 확장해야 합니다.
SDK의 진입점은 SandboxedSdkProvider
를 확장합니다. SandboxedSdkProvider
에는 SDK의 Context
객체가 포함되어 있으며 getContext()
를 호출하여 이 객체에 액세스할 수 있습니다. onLoadSdk()
가 호출된 후에만 이 컨텍스트에 액세스해야 합니다.
SDK 앱을 컴파일하려면 SDK 수명 주기를 처리하는 메서드를 재정의해야 합니다.
onLoadSdk()
샌드박스에서 SDK를 로드하고 SDK가 새
SandboxedSdk
객체 내에 래핑된IBinder
객체로 인터페이스를 전달하여 요청을 처리할 준비가 되면 호출 앱에 알립니다. 바인드된 서비스 가이드는IBinder
를 제공하는 다양한 방법을 제공합니다. 원하는 방식을 선택할 수 있지만 SDK와 호출 앱에 일관되어야 합니다.예를 들어 AIDL을 사용하면 앱에서 공유하고 사용할
IBinder
를 표시하도록 AIDL 파일을 정의해야 합니다.// ISdkInterface.aidl interface ISdkInterface { // the public functions to share with the App. int doSomething(); }
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. } } }
SdkSandboxController
SdkSandboxController
는 SDK에서 사용할 수 있는 컨텍스트 기반 시스템 서비스 래퍼입니다. SandboxedSdkProvider#getContext()
에서 수신한 컨텍스트를 사용하고 이 컨텍스트에서 context.getSystemService(SdkSandboxController.class)
를 호출하여 SDK에서 가져올 수 있습니다. 컨트롤러에는 SDK가 개인 정보 보호 샌드박스에서 정보를 가져오고 상호작용하는 데 도움이 되는 API가 있습니다.
컨트롤러에 있는 대부분의 API는 SandboxedSdkContext
가 액세스에 사용되는 컨텍스트가 아니면 예외를 발생시킵니다. Google에서는 다른 컨텍스트에서 서비스 래퍼를 사용할 수 없도록 할 계획입니다. SdkSandboxController
는 SDK 제공업체에서 사용하도록 고안되었으며 앱 개발자는 사용하지 않는 것이 좋습니다.
SDK 간 통신
런타임의 SDK는 서로 통신하여 미디에이션 및 관련 사용 사례를 지원할 수 있어야 합니다. 여기에 설명된 도구 모음은 샌드박스 내의 다른 SDK에서 사용할 수 있는 SDK와 작동하는 인터페이스를 제공합니다. 이 SDK 런타임 구현은 SDK 간 통신을 설정하기 위한 첫 번째 단계이며, 아직 개인 정보 보호 샌드박스에서의 미디에이션을 위한 모든 사용 사례를 포함하지 않을 수 있습니다.
SdkSandboxController
의 getSandboxedSdks()
API는 개인 정보 보호 샌드박스의 모든 로드된 SDK에 SandboxedSdk
클래스를 제공합니다. SandboxedSdk
객체에는 SDK에 관한 세부정보와 클라이언트가 SDK와 통신할 수 있는 sdkInterface
가 포함되어 있습니다.
개인 정보 보호 샌드박스의 SDK는 다음과 같은 코드 스니펫을 사용하여 다른 SDK와 통신해야 합니다. 'SDK1'이 'SDK2'와 통신하기 위해 이 코드를 작성했다고 가정해 보겠습니다(둘 다 개인 정보 보호 샌드박스의 앱에서 로드함).
SdkSandboxController controller = mSdkContext
.getSystemService(SdkSandboxController.class);
List<SandboxedSdk> sandboxedSdks = controller.getSandboxedSdks();
SandboxedSdk sdk2 = sandboxedSdks.stream().filter( // The SDK it wants to
// connect to, based on SDK name or SharedLibraryInfo.
try {
IBinder binder = sdk2.getInterface();
ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder);
// Call API on SDK2
message = sdkApi.getMessage();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
위 예에서 SDK1은 SDK2의 AIDL 라이브러리를 종속 항목으로 추가합니다. 이 클라이언트 SDK에는 AIDL에서 생성된 바인더 코드가 포함되어 있습니다. 두 SDK 모두 이 AIDL 라이브러리를 내보내야 합니다. 이는 앱이 개인 정보 보호 샌드박스의 SDK와 통신할 때 예상되는 작업과 동일합니다.
SDK 간 인터페이스를 공유하는 자동 생성 방식에 대한 지원이 향후 업데이트에 추가될 예정입니다.
런타임의 SDK는 아직 런타임이 지원되지 않는 앱 종속 항목 및 광고 SDK와 통신해야 할 수 있습니다.
SdkSandboxManager
의 registerAppOwnedSdkSandboxInterface()
API는 런타임이 지원되지 않는 SDK가 인터페이스를 플랫폼에 등록할 수 있는 방법을 제공합니다. SdkSandboxController
의 getAppOwnedSdkSandboxInterfaces()
API는 정적으로 연결된 모든 등록된 SDK에 AppOwnedSdkSandboxInterface
를 제공합니다.
다음 예에서는 런타임 지원 SDK 통신에서 사용할 수 있도록 인터페이스를 등록하는 방법을 보여줍니다.
// Register AppOwnedSdkSandboxInterface
mSdkSandboxManager.registerAppOwnedSdkSandboxInterface(
new AppOwnedSdkSandboxInterface(
APP_OWNED_SDK_NAME, (long) APP_OWNED_SDK_VERSION, new AppOwnedSdkApi())
);
이 예에서는 런타임이 지원되지 않는 SDK와의 통신을 계측하는 방법을 보여줍니다.
// Get AppOwnedSdkSandboxInterface
List<AppOwnedSdkSandboxInterface> appOwnedSdks = mSdkContext
.getSystemService(SdkSandboxController.class)
.getAppOwnedSdkSandboxInterfaces();
AppOwnedSdkSandboxInterface appOwnedSdk = appOwnedSdks.stream()
.filter(s -> s.getName().contains(APP_OWNED_SDK_NAME))
.findAny()
.get();
IAppOwnedSdkApi appOwnedSdkApi =
IAppOwnedSdkApi.Stub.asInterface(appOwnedSdk.getInterface());
message = appOwnedSdkApi.getMessage();
런타임이 지원되지 않는 광고 SDK는 자체적으로 등록하지 못할 수 있으므로 등록을 처리하고 파트너 또는 앱 SDK를 직접 종속 항목으로 포함하는 미디에이터 SDK를 만드는 것이 좋습니다. 이 미디에이터 SDK는 런타임이 지원되지 않는 SDK 및 종속 항목과 어댑터 역할을 하는 런타임 지원 미디에이터 간 통신을 설정합니다.
활동 지원
런타임 지원 SDK는 활동 태그를 매니페스트 파일에 추가할 수 없으며 자체 활동을 직접 시작할 수 없습니다. 액세스는 SdkSandboxActivityHandler
를 등록하고 샌드박스 활동을 시작하여 Activity
객체에 제공됩니다.
1. SdkSandboxActivityHandler 등록
SdkSandboxController#registerSdkSandboxActivityHandler(SandboxedActivityHandler)
를 사용하여 SdkSandboxActivityHandler
인스턴스 등록
API는 이 객체를 등록하고, 전달된 SdkSandboxActivityHandler
를 식별하는 IBinder
객체를 반환합니다.
public interface SdkSandboxActivityHandler {
void onActivityCreated(Activity activity);
}
API는 이 객체를 등록하고, 전달된 SdkSandboxActivityHandler
를 식별하는 IBinder
객체를 반환합니다.
2. 샌드박스 활동 시작
SDK는 등록된 SdkSandboxActivityHandler
를 식별하는 반환된 토큰을 클라이언트 앱에 전달합니다. 그러면 클라이언트 앱은 SdkSandboxManager#startSdkSandboxActivity(Activity, Binder)
를 호출하여 샌드박스를 시작할 활동과 등록된 SdkSandboxActivityHandler
를 식별하는 토큰을 전달합니다.
이 단계에서는 요청하는 SDK와 동일한 SDK 런타임에서 실행되는 새 플랫폼 활동이 시작됩니다.
활동이 시작되면 SDK는 Activity#OnCreate(Bundle)
실행의 일부로 SdkSandboxActivityHandler#onActivityCreated(Activity)
호출을 통해 알림을 받습니다.
예를 들어 Activity
객체에 액세스할 수 있으므로 호출자는 Activity#setContentView(View)
를 호출하여 contentView
뷰를 설정할 수 있습니다.
수명 주기 콜백을 등록하려면 Activity#registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks)
를 사용하세요.
전달된 활동에 OnBackInvokedCallback
을 등록하려면 Activity#getOnBackInvokedDispatcher().registerOnBackInvokedCallback(Int,
OnBackInvokedCallback)
을 사용합니다.
SDK 런타임에서 동영상 플레이어 테스트
개인 정보 보호 샌드박스는 배너 광고뿐만 아니라 SDK 런타임 내에서 실행되는 동영상 플레이어도 지원하기 위해 노력하고 있습니다.
동영상 플레이어를 테스트하는 흐름은 배너 광고 테스트와 유사합니다. SDK 진입점의 getView()
메서드를 변경하여 반환된 View
객체에 동영상 플레이어를 포함합니다. 개인 정보 보호 샌드박스에서 지원할 것으로 예상되는 모든 동영상 플레이어 흐름을 테스트합니다. 동영상 수명 주기와 관련된 SDK와 클라이언트 앱 간의 통신은 범위를 벗어나므로 이 기능에 관한 의견은 아직 필요하지 않습니다.
테스트와 의견을 통해 SDK 런타임이 원하는 동영상 플레이어의 모든 사용 사례를 지원하도록 할 수 있습니다.
다음 코드 스니펫은 URL에서 로드되는 간단한 동영상 뷰를 반환하는 방법을 보여줍니다.
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에서 Storage API 사용
SDK 런타임에서 SDK는 더 이상 앱의 내부 저장소에 액세스하거나 이를 읽거나 쓸 수 없으며, 그 반대도 불가능합니다. SDK 런타임은 앱과 분리되도록 보장된 자체 내부 저장소 영역에 할당됩니다.
SDK는 SandboxedSdkProvider#getContext()
에서 반환하는 Context
객체의 File Storage API를 사용하여 별도의 이 내부 저장소에 액세스할 수 있습니다. SDK는 내부 저장소만 사용할 수 있으므로 Context.getFilesDir()
또는 Context.getCacheDir()
같은 Internal Storage API만 작동합니다. 더 많은 예는 내부 저장소에서 액세스를 참고하세요.
SDK 런타임에서는 외부 저장소에 액세스할 수 없습니다. 외부 저장소에 액세스하기 위해 API를 호출하면 예외가 발생하거나 null
이 반환됩니다. 몇 가지 예는 다음과 같습니다.
- 저장소 액세스 프레임워크를 사용하여 파일에 액세스하면
SecurityException
이 발생합니다. getExternalFilsDir()
은 항상null
을 반환합니다.
Android 13에서는 SDK 런타임의 모든 SDK가 SDK 런타임에 할당된 내부 저장소를 공유합니다. 저장소는 클라이언트 앱이 제거될 때까지 또는 클라이언트 앱 데이터가 삭제될 때까지 유지됩니다.
SandboxedSdkProvider.getContext()
에서 반환된 Context
를 사용하여 저장해야 합니다. 애플리케이션 컨텍스트와 같은 다른 Context
객체 인스턴스에 File Storage 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에서 사용 중인 저장용량을 확인하는 데 도움이 됩니다.
Android 13에서는 하나의 API만 SDK별 저장소 경로 Context#getDataDir()
을 반환합니다.
Android 14에서는 Context
객체의 모든 내부 저장소 API가 각 SDK의 저장소 경로를 반환합니다. 다음 adb 명령어를 실행하여 이 기능을 사용 설정해야 할 수도 있습니다.
adb shell device_config put adservices sdksandbox_customized_sdk_context_enabled true
클라이언트의 SharedPreferences
읽어오기
클라이언트 앱에서는 SharedPreferences
의 키 집합을 SdkSandbox
와 공유하도록 선택할 수 있습니다. SDK는 SdkSanboxController#getClientSharedPreferences()
API를 사용하여 클라이언트 앱에서 동기화된 데이터를 읽어올 수 있습니다. 이 API에서 반환하는 SharedPreferences
는 읽기 전용입니다. 여기에 쓰기 작업을 해서는 안 됩니다.
Google Play 서비스에서 제공하는 광고 ID에 액세스
SDK가 Google Play 서비스에서 제공하는 광고 ID에 액세스해야 하는 경우:
- SDK 매니페스트에서
android.permission.ACCESS_ADSERVICES_AD_ID
권한을 선언합니다. AdIdManager#getAdId()
를 사용하여 값을 비동기식으로 가져옵니다.
Google Play 서비스에서 제공하는 앱 세트 ID에 액세스
SDK가 Google Play 서비스에서 제공하는 앱 세트 ID에 액세스해야 하는 경우:
AppSetIdManager#getAppSetId()
를 사용하여 값을 비동기식으로 가져옵니다.
클라이언트 앱 업데이트
SDK 런타임에서 실행되는 SDK를 호출하려면 호출 클라이언트 앱을 다음과 같이 변경합니다.
INTERNET
및ACCESS_NETWORK_STATE
권한을 앱의 매니페스트에 추가합니다.<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
광고가 포함된 앱의 활동에서
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;
기기에서 SDK 런타임 프로세스를 사용할 수 있는지 확인합니다.
SdkSandboxState
상수(getSdkSandboxState()
)를 확인합니다.SDK_SANDBOX_STATE_ENABLED_PROCESS_ISOLATION
은 SDK 런타임을 사용할 수 있음을 의미합니다.loadSdk()
호출이 성공했는지 확인합니다. 발생한 예외가 없으며 수신기가SandboxedSdk
의 인스턴스인 경우 호출에 성공한 것입니다.포그라운드에서
loadSdk()
를 호출합니다. 이 항목이 백그라운드에서 호출되면SecurityException
이 발생합니다.OutcomeReceiver
에서SandboxedSdk
의 인스턴스가 있는지 확인하여LoadSdkException
이 발생했는지 확인합니다. 예외가 발생했으면 SDK 런타임을 사용하지 못할 수도 있다는 의미입니다.
SdkSandboxState
또는loadSdk
호출이 실패하면 SDK 런타임을 사용할 수 없으며, 호출이 기존 SDK로 대체됩니다.로드된 후 런타임 시 SDK와 상호작용하도록
OutcomeReceiver
를 구현하여 콜백 클래스를 정의합니다. 다음 예에서는 클라이언트가 콜백을 사용하여 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 } } 뷰 표시가 완료되면 다음을 호출하여
SurfacePackage
를 해제해야 합니다.surfacePackage.notifyDetachedFromWindow()
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); }
앱에서는 기본
SharedPreferences
의 특정 키를 샌드박스와 공유하도록 선택할 수 있습니다. 이를 위해서는SdkSandbox
관리자의 모든 인스턴스에서SdkSandboxManager#addSyncedSharedPreferencesKeys(Set<String>keys)
메서드를 호출하면 됩니다. 어떤 키를 동기화할지 앱이SdkSandboxManager
에 알리면SdkSandboxManager
는 그러한 키의 값을 샌드박스와 SDK에 동기화하고 그러면SdkSandboxController#getClientSharedPreferences
를 사용하여 키 값을 읽어올 수 있게 됩니다. 자세한 내용은 클라이언트의 SharedPreferences 읽어오기를 참고하세요.동기화 중인 키 집합은 앱을 다시 시작할 때 유지되지 않으며 샌드박스에 동기화된 데이터는 샌드박스를 다시 시작할 때 삭제됩니다. 따라서 앱이 시작될 때마다
addSyncedSharedPreferencesKeys
를 호출하여 앱이 동기화를 시작하는 것이 중요합니다.동기화 중인 키 집합을 수정하려면
SdkSandboxManager#removeSyncedSharedPreferencesKeys(Set<String>keys)
를 호출하여 키를 삭제하면 됩니다. 동기화 중인 현재 키 집합을 보려면SdkSandboxManager#getSyncedSharedPreferencesKeys()
를 사용합니다.키 집합은 가능한 한 작게 유지하고 필요한 경우에만 사용하는 것이 좋습니다. 일반적인 용도로 SDK에 정보를 전달하려면
SandboxedSdk
인터페이스를 사용하여 SDK와 직접 통신하세요. 앱에서 동의 관리 플랫폼(CMP) SDK를 사용할 경우 샌드박스 내부의 SDK가 기본SharedPreferences
의 데이터 CMP SDK 저장소를 읽어오고자 할 때 이러한 API를 사용할 수도 있습니다.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")); }
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와 계속 상호작용하려면 이러한 뷰를 다시 로드하여 새 샌드박스 프로세스가 시작되도록 해야 합니다.
클라이언트 앱의
build.gradle
에 SDK 모듈의 종속 항목을 추가합니다.dependencies { ... implementation project(':<your-sdk-module>') ... }
앱 테스트
클라이언트 앱을 실행하려면 Android 스튜디오 또는 명령줄을 사용하여 SDK 앱과 클라이언트 앱을 테스트 기기에 설치하세요.
Android 스튜디오를 통해 배포
Android 스튜디오를 통해 배포할 때는 다음 단계를 완료하세요.
- 클라이언트 앱의 Android 스튜디오 프로젝트를 엽니다.
- Run > Edit Configurations로 이동합니다. Run/Debug Configuration 창이 표시됩니다.
- Launch Options에서 Launch를 Specated Activity로 설정합니다.
- Activity 옆에 있는 점 3개로 된 메뉴를 클릭하고 클라이언트의 Main Activity을 선택합니다.
- Apply를 클릭한 후 OK를 클릭합니다.
- Run
을 클릭하여 테스트 기기에 클라이언트 앱과 SDK를 설치합니다.
명령줄에서 배포
명령줄을 사용하여 배포할 때는 다음 목록의 단계를 완료하세요.
이 섹션에서는 SDK 앱 모듈의 이름이 sdk-app
이고 클라이언트 앱 모듈의 이름이 client-app
이라고 가정합니다.
명령줄 터미널에서 개인 정보 보호 샌드박스 SDK APK를 빌드합니다.
./gradlew :client-app:buildPrivacySandboxSdkApksForDebug
이렇게 하면 생성된 APK의 위치가 출력됩니다. 이러한 APK는 로컬 디버그 키로 서명됩니다. 다음 명령어에 이 경로가 필요합니다.
기기에 APK를 설치합니다.
adb install -t /path/to/your/standalone.apk
Android 스튜디오에서 Run > Edit Configurations를 클릭합니다. Run/Debug Configuration 창이 표시됩니다.
Installation Options에서 Deploy를 Default APK로 설정합니다.
Apply를 클릭한 후 OK를 클릭합니다.
Run을 클릭하여 테스트 기기에 APK 번들을 설치합니다.
앱 디버그
클라이언트 앱을 디버그하려면 Android 스튜디오에서 Debug
버튼을 클릭합니다.
SDK 앱을 디버그하려면 Run > Attach to Process로 이동합니다. 그러면 팝업 화면 (아래 참고)이 표시됩니다. Show all processes 체크박스를 선택합니다. 표시되는 목록에서 CLIENT_APP_PROCESS_sdk_sandbox
라는 프로세스를 찾습니다. 이 옵션을 선택하고 SDK 앱의 코드에 중단점을 추가하여 SDK 디버깅을 시작합니다.
![SDK 앱 프로세스가 대화상자의 하단에 있는 목록 보기에 표시됩니다.](https://developers.google.cn/static/privacy-sandbox/assets/images/android/debug-sdk-app.png?authuser=5&hl=ko)
명령줄에서 SDK 런타임 시작 및 중지
앱의 SDK 런타임 프로세스를 시작하려면 다음 셸 명령어를 사용하세요.
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
내에서 getSandboxedSdks
기능을 사용하여 현재 로드된 SDK를 확인할 수 있습니다.
제한사항
SDK 런타임의 진행 중인 기능 목록은 출시 노트를 참고하세요.
코드 샘플
GitHub의 SDK 런타임 및 개인 정보 보호 API 저장소에는 SDK 런타임 초기화 및 호출 방법을 보여주는 샘플을 비롯하여 시작하는 데 도움이 되는 개별 Android 스튜디오 프로젝트 집합이 포함되어 있습니다.버그 및 문제 신고
여러분의 의견은 Android의 개인 정보 보호 샌드박스에서 매우 중요한 부분입니다. 발견한 문제나 Android의 개인 정보 보호 샌드박스 개선을 위한 아이디어가 있다면 Google에 알려 주세요.
추천 항목
- 참고: JavaScript가 사용 중지되어 있으면 링크 텍스트가 표시됩니다.
- SDK 런타임
- 출시 노트
- Android 개발자 가이드의 Protected Audience API