SDK 런타임 개발자 가이드

안내가 다를 수 있으므로 Android의 개인 정보 보호 샌드박스 문서를 읽으면서 개발자 프리뷰 또는 베타 버튼을 사용하여 작업 중인 프로그램 버전을 선택하세요.


의견 보내기

SDK 런타임을 사용하면 SDK가 호출 앱과는 별도인 전용 샌드박스에서 실행될 수 있습니다. SDK 런타임은 사용자 데이터 수집에 관한 향상된 보호 장치와 보장을 제공합니다. 이는 데이터 액세스 권한과 허용된 권한 집합을 제한하는 수정된 실행 환경을 통해 이루어집니다. SDK 런타임에 관한 자세한 내용은 설계 제안을 참고하세요.

이 페이지의 단계는 호출 앱에 원격으로 렌더링될 수 있는 웹 기반 뷰를 정의하는 런타임 지원 SDK를 만드는 프로세스를 안내합니다.

알려진 제한사항

SDK 런타임의 진행 중인 기능 목록은 출시 노트를 참고하세요.

다음 제한사항은 다음 메이저 Android 플랫폼 버전에서 수정될 예정입니다.

  • 스크롤 가능한 뷰 내 광고 렌더링. 예를 들어 RecyclerView가 제대로 작동하지 않습니다.
    • 크기 조절 시 버벅거림이 발생할 수 있습니다.
    • 사용자 터치 스크롤 이벤트가 런타임에 제대로 전달되지 않습니다.
  • Storage API

다음 문제는 2023년에 수정될 예정입니다.

  • getAdIdgetAppSetId API를 위한 지원이 아직 활성화되지 않았으므로 이 두 API가 아직 제대로 작동하지 않습니다.

시작하기 전에

시작하기 전에 다음 단계를 완료하세요.

  1. Android의 개인 정보 보호 샌드박스의 개발 환경을 설정합니다. 도구를 사용하여 SDK 런타임을 지원하는 기능은 현재 개발 중이므로 이 가이드에서는 최신 Android 스튜디오 Canary 버전을 사용해야 합니다. 이 버전의 Android 스튜디오는 사용 중인 다른 버전과 동시에 실행할 수 있으므로 이 요구사항이 적합하지 않은 경우 Google에 알려주세요.

  2. 지원되는 기기에 시스템 이미지를 설치하거나 Android의 개인 정보 보호 샌드박스 지원이 포함된 에뮬레이터를 설정합니다.

Android 스튜디오에서 프로젝트 설정

SDK 런타임을 사용해 보려면 클라이언트-서버 모델과 유사한 모델을 사용하세요. 주요 차이점은 앱(클라이언트)과 SDK('서버')가 동일한 기기에서 실행된다는 점입니다.

  1. 프로젝트에 앱 모듈을 추가합니다. 이 모듈은 SDK를 구동하는 클라이언트 역할을 합니다.
  2. 앱 모듈에서 SDK 런타임을 사용 설정하고 필요한 권한을 선언하고 API별 광고 서비스를 구성합니다.
  3. 프로젝트에 라이브러리 모듈 1개를 추가합니다. 이 모듈에는 SDK 코드가 포함됩니다.
  4. SDK 모듈에서 필요한 권한을 선언합니다. 이 모듈에서는 API별 광고 서비스를 구성할 필요가 없습니다.
  5. SDK가 사용하지 않는 라이브러리 모듈의 build.gradle 파일에서 dependencies를 삭제합니다. 대부분의 경우 종속 항목을 모두 삭제할 수 있습니다. 그렇게 하려면 SDK에 해당하는 이름의 새 디렉터리를 만들면 됩니다.
  6. com.android.privacy-sandbox-sdk 유형을 사용하여 직접 새 모듈을 만듭니다. 이는 기기에 배포할 수 있는 APK를 만들기 위해 SDK 코드와 함께 번들로 제공됩니다. 그렇게 하려면 SDK에 해당하는 이름의 새 디렉터리를 만들면 됩니다. 빈 build.gradle 파일을 추가합니다. 이 파일의 콘텐츠는 이 가이드의 뒷부분에서 채워집니다.

  7. gradle.properties 파일에 다음 스니펫을 추가합니다.

    android.experimental.privacysandboxsdk.enable=true
    

  8. Tiramisu(확장 프로그램 수준 4) 에뮬레이터 이미지를 다운로드하고 Play 스토어가 포함된 이 이미지로 에뮬레이터를 만듭니다.

SDK 개발자인지 앱 개발자인지에 따라 최종 설정이 이전 단락에서 설명한 것과 다를 수 있습니다.

앱을 설치하는 방법과 유사하게 Android 스튜디오나 Android 디버그 브리지(adb)를 사용하여 테스트 기기에 SDK를 설치합니다. 시작하는 데 도움이 되도록 Kotlin 및 자바 프로그래밍 언어로 샘플 앱을 만들었으며 이 GitHub 저장소에서 확인할 수 있습니다. 리드미 및 매니페스트 파일에는 안정화 버전의 Android 스튜디오에서 샘플을 실행하려면 무엇을 변경해야 하는지 설명하는 주석이 있습니다.

SDK 준비

  1. 수동으로 모듈 수준 디렉터리를 만듭니다. 이는 SDK APK를 빌드하기 위한 구현 코드의 래퍼 역할을 합니다. 새 디렉터리에서 build.gradle 파일을 추가하고 다음 스니펫으로 채웁니다. 런타임 지원 SDK(RE-SDK)에 고유한 이름을 사용하고 버전을 제공합니다. 라이브러리 모듈을 dependencies 섹션에 포함합니다.

    plugins {
        id 'com.android.privacy-sandbox-sdk'
    }
    
    android {
        compileSdk 33
        compileSdkExtension 4
        minSdk 33
        targetSdk 33
        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의 진입점은 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 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 런타임에서 동영상 플레이어 테스트

개인 정보 보호 샌드박스는 배너 광고뿐만 아니라 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이 반환됩니다. 몇 가지 예는 다음과 같습니다.

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

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를 호출하려면 호출 클라이언트 앱을 다음과 같이 변경합니다.

  1. INTERNETACCESS_NETWORK_STATE 권한을 앱의 매니페스트에 추가합니다.

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    
  2. 광고가 포함된 앱의 활동에서 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 런타임을 사용하지 못할 수도 있다는 의미입니다.

    SdkSandboxState 또는 loadSdk 호출이 실패하면 SDK 런타임을 사용할 수 없으며, 호출이 기존 SDK로 대체됩니다.

  4. 로드된 후 런타임 시 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()
    
  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. 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와 계속 상호작용하려면 이러한 뷰를 다시 로드하여 새 샌드박스 프로세스가 시작되도록 해야 합니다.

  7. 클라이언트 앱의 build.gradle에 SDK 모듈의 종속 항목을 추가합니다.

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

앱 테스트

클라이언트 앱을 실행하려면 Android 스튜디오 또는 명령줄을 사용하여 SDK 앱과 클라이언트 앱을 테스트 기기에 설치하세요.

Android 스튜디오를 통해 배포

Android 스튜디오를 통해 배포할 때는 다음 단계를 완료하세요.

  1. 클라이언트 앱의 Android 스튜디오 프로젝트를 엽니다.
  2. Run > Edit Configurations로 이동합니다. Run/Debug Configuration 창이 표시됩니다.
  3. Launch Options에서 LaunchSpecated Activity로 설정합니다.
  4. Activity 옆에 있는 점 3개로 된 메뉴를 클릭하고 클라이언트의 Main Activity을 선택합니다.
  5. Apply를 클릭한 후 OK를 클릭합니다.
  6. Run 을 클릭하여 테스트 기기에 클라이언트 앱과 SDK를 설치합니다.

명령줄에서 배포

명령줄을 사용하여 배포할 때는 다음 목록의 단계를 완료하세요. 이 섹션에서는 SDK 앱 모듈의 이름이 sdk-app이고 클라이언트 앱 모듈의 이름이 client-app이라고 가정합니다.

  1. 명령줄 터미널에서 개인 정보 보호 샌드박스 SDK APK를 빌드합니다.

    ./gradlew :client-app:buildPrivacySandboxSdkApksForDebug
    

    이렇게 하면 생성된 APK의 위치가 출력됩니다. 이러한 APK는 로컬 디버그 키로 서명됩니다. 다음 명령어에 이 경로가 필요합니다.

  2. 기기에 APK를 설치합니다.

    adb install -t /path/to/your/standalone.apk
    
  3. Android 스튜디오에서 Run > Edit Configurations를 클릭합니다. Run/Debug Configuration 창이 표시됩니다.

  4. Installation Options에서 DeployDefault APK로 설정합니다.

  5. Apply를 클릭한 후 OK를 클릭합니다.

  6. Run을 클릭하여 테스트 기기에 APK 번들을 설치합니다.

앱 디버그

클라이언트 앱을 디버그하려면 Android 스튜디오에서 Debug 버튼을 클릭합니다.

SDK 앱을 디버그하려면 Run > Attach to Process로 이동합니다. 그러면 팝업 화면이 표시됩니다(그림 1). Show all processes 체크박스를 선택합니다. 표시되는 목록에서 CLIENT_APP_PROCESS_sdk_sandbox라는 프로세스를 찾습니다. 이 옵션을 선택하고 SDK 앱의 코드에 중단점을 추가하여 SDK 디버깅을 시작합니다.

SDK 앱 프로세스가 대화상자의 하단에 있는 목록 보기에 표시됩니다.
그림 1. 디버그할 SDK 앱을 선택할 수 있는 Choose process 화면

명령줄에서 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 런타임의 진행 중인 기능 목록은 출시 노트를 참고하세요.

코드 샘플

GitHub의 SDK 런타임 및 개인 정보 보호 API 저장소에는 SDK 런타임 초기화 및 호출 방법을 보여주는 샘플을 비롯하여 시작하는 데 도움이 되는 개별 Android 스튜디오 프로젝트 집합이 포함되어 있습니다.

버그 및 문제 신고

여러분의 의견은 Android의 개인 정보 보호 샌드박스에서 매우 중요한 부분입니다. 발견한 문제나 Android의 개인 정보 보호 샌드박스 개선을 위한 아이디어가 있다면 Google에 알려 주세요.