주문형 제공 구성

기능 모듈을 사용하면 앱의 기본 모듈에서 특정 기능 및 리소스를 분리하여 App Bundle에 포함할 수 있습니다. 예를 들어 사용자는 앱의 기본 APK를 설치한 후 나중에 Play Feature Delivery를 통해 특정 구성요소를 주문형으로 다운로드하고 설치할 수 있습니다.

사진 메시지를 캡처하고 보내는 기능이 있지만 소수의 사용자만 사진 메시지를 보내는 문자 메시지 앱을 예로 들면, 사진 메시지를 다운로드 가능한 기능 모듈로 포함하는 것이 타당할 것입니다. 이렇게 하면 모든 사용자가 처음에 다운로드하는 앱의 크기는 작아지고 사진 메시지를 보내는 사용자만 추가 구성요소를 다운로드하면 됩니다.

이런 유형의 모듈화는 더 많은 작업이 필요하고 앱의 기존 코드를 리팩터링해야 할 수 있으므로 사용자가 주문형으로 이용함으로써 얻을 수 있는 이점이 가장 큰 앱 기능이 어떤 것인지 신중하게 고려해야 합니다. 주문형 기능에 관한 최적의 사용 사례와 가이드라인을 더 정확히 이해하려면 주문형 제공의 UX 권장사항을 참고하세요.

주문형 제공과 같은 고급 제공 옵션을 사용 설정하지 않고 시간 경과에 따라 앱 기능을 점차 모듈화하려면 대신 설치 시 제공을 구성합니다.

이 페이지에서는 기능 모듈을 앱 프로젝트에 추가하고 주문형 제공을 위해 구성하는 방법을 설명합니다. 시작하기 전에 Android 스튜디오 3.5 이상 및 Android Gradle 플러그인 3.5.0 이상을 사용하고 있는지 확인하세요.

주문형 제공을 위한 새 모듈 구성

새 기능 모듈을 만드는 가장 쉬운 방법은 Android 스튜디오 3.5 이상을 사용하는 것입니다. 기능 모듈은 본질적으로 기본 앱 모듈에 종속되기 때문에 기존 앱 프로젝트에만 추가할 수 있습니다.

Android 스튜디오를 사용하여 앱 프로젝트에 기능 모듈을 추가하려면 다음 단계를 따르세요.

  1. 아직 앱 프로젝트를 열지 않았다면 IDE에서 앱 프로젝트를 엽니다.
  2. 메뉴 바에서 File > New > New Module을 선택합니다.
  3. Create New Module 대화상자에서 Dynamic Feature Module을 선택하고 Next를 클릭합니다.
  4. Configure your new module 섹션에서 다음을 완료합니다.
    1. 드롭다운 메뉴에서 앱 프로젝트의 Base application module을 선택합니다.
    2. Module name을 지정합니다. IDE는 이 이름을 사용하여 Gradle 설정 파일에서 모듈을 Gradle 하위 프로젝트로 식별합니다. App Bundle을 빌드할 때 Gradle은 하위 프로젝트 이름의 마지막 요소를 사용하여 기능 모듈의 매니페스트<manifest split> 속성을 삽입합니다.
    3. 모듈의 package name을 지정합니다. 기본적으로 Android 스튜디오는 기본 모듈의 루트 패키지 이름과 이전 단계에서 지정한 모듈 이름을 결합한 패키지 이름을 제안합니다.
    4. 모듈에서 지원할 Minimum API level을 선택합니다. 이 값은 기본 모듈의 값과 일치해야 합니다.
  5. Next를 클릭합니다.
  6. Module Download Options 섹션에서 다음을 완료합니다.

    1. 최대 50자(영문 기준)를 사용하여 Module title을 지정합니다. 예를 들어 사용자가 모듈을 다운로드하려는지 여부를 확인할 때 플랫폼은 이 제목을 사용하여 모듈을 식별합니다. 이런 이유로 앱의 기본 모듈은 번역이 가능한 문자열 리소스로 된 모듈 제목을 포함해야 합니다. Android 스튜디오를 사용하여 모듈을 만들 때 IDE는 문자열 리소스를 기본 모듈에 추가하고 다음 항목을 기능 모듈의 매니페스트에 삽입합니다.

      <dist:module
          ...
          dist:title="@string/feature_title">
      </dist:module>
      
    2. Install-time inclusion 아래의 드롭다운 메뉴에서 Do not include module at install-time을 선택합니다. Android 스튜디오는 모듈의 매니페스트에 다음을 삽입하여 선택사항을 반영합니다.

      <dist:module ... >
        <dist:delivery>
            <dist:on-demand/>
        </dist:delivery>
      </dist:module>
      
    3. 이 모듈을 Android 4.4(API 수준 20) 이하를 실행하는 기기에서 사용하고 다중 APK에 포함하려면 Fusing 옆에 있는 체크박스를 선택합니다. 즉, 이 모듈에 대해 주문형 동작을 사용 설정하고 융합을 중지하여 분할 APK의 다운로드 및 설치를 지원하지 않는 기기에서는 모듈을 제외할 수 있습니다. Android 스튜디오는 모듈의 매니페스트에 다음을 삽입하여 선택사항을 반영합니다.

      <dist:module ...>
          <dist:fusing dist:include="true | false" />
      </dist:module>
      
  7. Finish를 클릭합니다.

Android 스튜디오에서 모듈 생성을 완료한 후에는 Project 창에서 직접 콘텐츠를 검사합니다(메뉴 바에서 View > Tool Windows > Project 선택). 기본 코드, 리소스, 구성은 표준 앱 모듈과 비슷해야 합니다.

다음으로 Play Feature Delivery 라이브러리를 사용하여 주문형 설치 기능을 구현해야 합니다.

프로젝트에 Play Feature Delivery 라이브러리 포함

시작하려면 먼저 프로젝트에 Play Feature Delivery 라이브러리를 추가해야 합니다.

주문형 모듈 요청

앱이 기능 모듈을 사용해야 하는 경우 앱이 포그라운드에 있는 동안 SplitInstallManager 클래스를 통해 기능 모듈을 요청할 수 있습니다. 요청할 때 앱은 타겟 모듈 매니페스트의 split 요소에서 정의한 대로 모듈의 이름을 지정해야 합니다. Android 스튜디오를 사용하여 기능 모듈을 생성할 때 빌드 시스템은 개발자가 제공한 모듈 이름을 사용하여 컴파일 시 모듈의 매니페스트에 이 속성을 삽입합니다. 자세한 내용은 기능 모듈 매니페스트를 참고하세요.

예를 들어 기기의 카메라를 사용하여 사진 메시지를 캡처하고 전송하는 주문형 모듈이 있는 앱을 생각해 보세요. 이 주문형 모듈은 매니페스트에 split="pictureMessages"를 명시한다고 가정해 보겠습니다. 다음 샘플은 SplitInstallManager를 사용하여 일부 프로모션 필터의 추가 모듈과 함께 pictureMessages 모듈을 요청합니다.

Kotlin

// Creates an instance of SplitInstallManager.
val splitInstallManager = SplitInstallManagerFactory.create(context)

// Creates a request to install a module.
val request =
    SplitInstallRequest
        .newBuilder()
        // You can download multiple on demand modules per
        // request by invoking the following method for each
        // module you want to install.
        .addModule("pictureMessages")
        .addModule("promotionalFilters")
        .build()

splitInstallManager
    // Submits the request to install the module through the
    // asynchronous startInstall() task. Your app needs to be
    // in the foreground to submit the request.
    .startInstall(request)
    // You should also be able to gracefully handle
    // request state changes and errors. To learn more, go to
    // the section about how to Monitor the request state.
    .addOnSuccessListener { sessionId -> ... }
    .addOnFailureListener { exception ->  ... }

자바

// Creates an instance of SplitInstallManager.
SplitInstallManager splitInstallManager =
    SplitInstallManagerFactory.create(context);

// Creates a request to install a module.
SplitInstallRequest request =
    SplitInstallRequest
        .newBuilder()
        // You can download multiple on demand modules per
        // request by invoking the following method for each
        // module you want to install.
        .addModule("pictureMessages")
        .addModule("promotionalFilters")
        .build();

splitInstallManager
    // Submits the request to install the module through the
    // asynchronous startInstall() task. Your app needs to be
    // in the foreground to submit the request.
    .startInstall(request)
    // You should also be able to gracefully handle
    // request state changes and errors. To learn more, go to
    // the section about how to Monitor the request state.
    .addOnSuccessListener(sessionId -> { ... })
    .addOnFailureListener(exception -> { ... });

앱이 주문형 모듈을 요청하면 Play Feature Delivery 라이브러리는 'fire-and-forget' 전략을 사용합니다. 즉, 모듈을 플랫폼에 다운로드하라는 요청을 전송하지만 성공적으로 설치되었는지는 모니터링하지 않습니다. 설치 후 사용자가 계속 진행하게 하거나 적절하게 오류를 처리하려면 요청 상태를 모니터링해야 합니다.

참고: 기기에 이미 설치된 기능 모듈을 요청해도 괜찮습니다. API는 모듈이 이미 설치되어 있음을 감지하면 즉시 요청이 완료된 것으로 간주합니다. 또한 모듈을 설치한 후에는 Google Play에서 자동으로 업데이트합니다. 즉, App Bundle의 새 버전을 업로드하면 플랫폼은 앱에 속한 설치된 APK를 모두 업데이트합니다. 자세한 내용은 앱 업데이트 관리를 참고하세요.

모듈의 코드 및 리소스에 액세스할 수 있으려면 앱에서 SplitCompat을 사용 설정해야 합니다. Android 인스턴트 앱에는 SplitCompat이 필요하지 않습니다.

주문형 모듈의 설치 연기

앱에서 주문형 모듈을 즉시 다운로드하고 설치할 필요가 없다면 앱이 백그라운드에 있는 동안 설치를 연기할 수 있습니다. 예를 들어 앱의 향후 출시를 위해 일부 프로모션 자료를 미리 로드하려는 경우입니다.

아래와 같이 deferredInstall() 메서드를 사용하여 나중에 다운로드될 모듈을 지정할 수 있습니다. 그리고 SplitInstallManager.startInstall()과 달리 지연 설치 요청을 시작하기 위해 앱이 포그라운드에 있지 않아도 됩니다.

Kotlin

// Requests an on demand module to be downloaded when the app enters
// the background. You can specify more than one module at a time.
splitInstallManager.deferredInstall(listOf("promotionalFilters"))

Java

// Requests an on demand module to be downloaded when the app enters
// the background. You can specify more than one module at a time.
splitInstallManager.deferredInstall(Arrays.asList("promotionalFilters"));

지연 설치 요청의 결과는 보장되지 않으며 진행 상황을 추적할 수 없습니다. 따라서 지연 설치를 지정한 모듈에 액세스하기 전에 먼저 해당 모듈이 설치되어 있는지 확인해야 합니다. 모듈을 즉시 사용할 수 있게 하려면 이전 섹션에 설명한 대로 대신 SplitInstallManager.startInstall()을 사용하여 설치를 요청하면 됩니다.

요청 상태 모니터링

진행률 표시줄을 업데이트하거나 설치 후 인텐트를 실행하거나 요청 오류를 적절하게 처리하려면 비동기 SplitInstallManager.startInstall() 작업에서 상태 업데이트를 수신 대기해야 합니다. 설치 요청의 업데이트 수신을 시작하려면 먼저, 아래와 같이 리스너를 등록하고 요청의 세션 ID를 가져와야 합니다.

Kotlin

// Initializes a variable to later track the session ID for a given request.
var mySessionId = 0

// Creates a listener for request status updates.
val listener = SplitInstallStateUpdatedListener { state ->
    if (state.sessionId() == mySessionId) {
      // Read the status of the request to handle the state update.
    }
}

// Registers the listener.
splitInstallManager.registerListener(listener)

...

splitInstallManager
    .startInstall(request)
    // When the platform accepts your request to download
    // an on demand module, it binds it to the following session ID.
    // You use this ID to track further status updates for the request.
    .addOnSuccessListener { sessionId -> mySessionId = sessionId }
    // You should also add the following listener to handle any errors
    // processing the request.
    .addOnFailureListener { exception ->
        // Handle request errors.
    }

// When your app no longer requires further updates, unregister the listener.
splitInstallManager.unregisterListener(listener)

Java

// Initializes a variable to later track the session ID for a given request.
int mySessionId = 0;

// Creates a listener for request status updates.
SplitInstallStateUpdatedListener listener = state -> {
    if (state.sessionId() == mySessionId) {
      // Read the status of the request to handle the state update.
    }
};

// Registers the listener.
splitInstallManager.registerListener(listener);

...

splitInstallManager
    .startInstall(request)
    // When the platform accepts your request to download
    // an on demand module, it binds it to the following session ID.
    // You use this ID to track further status updates for the request.
    .addOnSuccessListener(sessionId -> { mySessionId = sessionId; })
    // You should also add the following listener to handle any errors
    // processing the request.
    .addOnFailureListener(exception -> {
        // Handle request errors.
    });

// When your app no longer requires further updates, unregister the listener.
splitInstallManager.unregisterListener(listener);

요청 오류 처리

앱 설치가 항상 성공하는 것은 아닌 것처럼 기능 모듈의 주문형 설치도 실패하는 경우가 있습니다. 설치 실패는 기기 저장용량 부족, 네트워크 연결 없음 또는 사용자가 Google Play 스토어에 로그인하지 않음 등의 문제로 발생할 수 있습니다. 사용자 관점에서 이 상황을 적절하게 처리하는 방법에 관한 제안은 주문형 제공의 UX 가이드라인을 참고하세요.

모듈 다운로드 또는 설치를 실패한 경우 아래와 같이 코드 측면에서 addOnFailureListener()를 사용하여 적절하게 처리해야 합니다.

Kotlin

splitInstallManager
    .startInstall(request)
    .addOnFailureListener { exception ->
        when ((exception as SplitInstallException).errorCode) {
            SplitInstallErrorCode.NETWORK_ERROR -> {
                // Display a message that requests the user to establish a
                // network connection.
            }
            SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED -> checkForActiveDownloads()
            ...
        }
    }

fun checkForActiveDownloads() {
    splitInstallManager
        // Returns a SplitInstallSessionState object for each active session as a List.
        .sessionStates
        .addOnCompleteListener { task ->
            if (task.isSuccessful) {
                // Check for active sessions.
                for (state in task.result) {
                    if (state.status() == SplitInstallSessionStatus.DOWNLOADING) {
                        // Cancel the request, or request a deferred installation.
                    }
                }
            }
        }
}

Java

splitInstallManager
    .startInstall(request)
    .addOnFailureListener(exception -> {
        switch (((SplitInstallException) exception).getErrorCode()) {
            case SplitInstallErrorCode.NETWORK_ERROR:
                // Display a message that requests the user to establish a
                // network connection.
                break;
            case SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED:
                checkForActiveDownloads();
            ...
    });

void checkForActiveDownloads() {
    splitInstallManager
        // Returns a SplitInstallSessionState object for each active session as a List.
        .getSessionStates()
        .addOnCompleteListener( task -> {
            if (task.isSuccessful()) {
                // Check for active sessions.
                for (SplitInstallSessionState state : task.getResult()) {
                    if (state.status() == SplitInstallSessionStatus.DOWNLOADING) {
                        // Cancel the request, or request a deferred installation.
                    }
                }
            }
        });
}

아래 표에서는 앱이 처리해야 하는 오류 상태를 설명합니다.

오류 코드 설명 추천 작업
ACTIVE_SESSIONS_LIMIT_EXCEEDED 현재 다운로드 중인 기존 요청이 하나 이상 있으므로 요청이 거부되었습니다. 위 샘플과 같이 계속 다운로드 중인 요청이 있는지 확인합니다.
MODULE_UNAVAILABLE Google Play가 현재 설치된 버전의 앱, 기기 및 사용자 Google Play 계정을 기반으로 요청된 모듈을 찾을 수 없습니다. 사용자가 모듈에 액세스할 수 없는 경우 사용자에게 알립니다.
INVALID_REQUEST Google Play에서 요청을 수신했지만, 요청이 유효하지 않습니다. 요청에 포함된 정보가 완전하고 정확한지 확인합니다.
SESSION_NOT_FOUND 지정된 세션 ID의 세션을 찾을 수 없습니다. 세션 ID로 요청 상태를 모니터링하려면 세션 ID가 올바른지 확인합니다.
API_NOT_AVAILABLE Play Feature Delivery 라이브러리는 현재 기기에서 지원되지 않습니다. 즉, 기기는 주문형 기능을 다운로드하고 설치할 수 없습니다. Android 4.4(API 수준 20) 이하를 실행하는 기기의 경우 dist:fusing 매니페스트 속성을 사용하여 설치 시 기능 모듈을 포함해야 합니다. 자세한 내용은 기능 모듈 매니페스트를 참고하세요.
NETWORK_ERROR 네트워크 오류로 인해 요청이 실패했습니다. 사용자에게 네트워크 연결을 설정하거나 다른 네트워크로 변경하라는 메시지를 표시합니다.
ACCESS_DENIED 권한이 부족하여 앱에서 요청을 등록할 수 없습니다. 일반적으로 앱이 백그라운드에 있을 때 발생합니다. 앱이 포그라운드로 돌아올 때 요청을 시도합니다.
INCOMPATIBLE_WITH_EXISTING_SESSION 이미 요청되었지만 아직 설치되지 않은 모듈이 하나 이상 요청에 포함되어 있습니다. 앱에서 이미 요청한 모듈을 포함하지 않는 새 요청을 만들거나 요청을 다시 시도하기 전에 현재 요청된 모든 모듈이 설치 완료될 때까지 기다립니다.

이미 설치된 모듈을 요청하는 것이 오류로 확인되지는 않습니다.

SERVICE_DIED 요청을 처리하는 서비스가 중단되었습니다. 요청을 다시 시도합니다.

SplitInstallStateUpdatedListener에서 이 오류 코드, FAILED 상태, 세션 ID -1과 함께 SplitInstallSessionState를 수신합니다.

INSUFFICIENT_STORAGE 기기에 기능 모듈을 설치할 수 있는 여유 공간이 없습니다. 사용자에게 저장용량이 부족하여 이 기능을 설치할 수 없다고 알립니다.
SPLITCOMPAT_VERIFICATION_ERROR, SPLITCOMPAT_EMULATION_ERROR, SPLITCOMPAT_COPY_ERROR SplitCompat이 기능 모듈을 로드할 수 없습니다. 이 오류는 다음 앱이 다시 시작된 후 자동으로 해결됩니다.
PLAY_STORE_NOT_FOUND Play 스토어 앱이 기기에 설치되어 있지 않습니다. 이 기능을 다운로드하려면 Play 스토어 앱이 필요하다고 사용자에게 알려주세요.
APP_NOT_OWNED Google Play에서 앱을 설치하지 않았으며 기능을 다운로드할 수 없습니다. 이 오류는 지연 설치에서만 발생합니다. 사용자가 Google Play에서 앱을 받도록 하려면 필요한 사용자 확인을 가져올 수 있는 startInstall()을 사용하세요.
INTERNAL_ERROR Play 스토어에서 내부 오류가 발생했습니다. 요청을 다시 시도하세요.

사용자가 주문형 모듈의 다운로드를 요청했는데 오류가 발생하면 사용자에게 두 가지 옵션, 즉 다시 시도(요청을 다시 시도) 및 취소(요청을 취소) 옵션을 제공하는 대화상자를 표시하는 방법을 고려하세요. 추가 지원을 위해 도움말 링크를 제공하여 Google Play 고객센터로 사용자를 안내할 수도 있습니다.

상태 업데이트 처리

리스너를 등록하고 요청의 세션 ID를 기록한 후 아래와 같이 StateUpdatedListener.onStateUpdate()를 사용하여 상태 변경사항을 처리합니다.

Kotlin

override fun onStateUpdate(state : SplitInstallSessionState) {
    if (state.status() == SplitInstallSessionStatus.FAILED
        && state.errorCode() == SplitInstallErrorCode.SERVICE_DIED) {
       // Retry the request.
       return
    }
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            SplitInstallSessionStatus.DOWNLOADING -> {
              val totalBytes = state.totalBytesToDownload()
              val progress = state.bytesDownloaded()
              // Update progress bar.
            }
            SplitInstallSessionStatus.INSTALLED -> {

              // After a module is installed, you can start accessing its content or
              // fire an intent to start an activity in the installed module.
              // For other use cases, see access code and resources from installed modules.

              // If the request is an on demand module for an Android Instant App
              // running on Android 8.0 (API level 26) or higher, you need to
              // update the app context using the SplitInstallHelper API.
            }
        }
    }
}

자바

@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.status() == SplitInstallSessionStatus.FAILED
        && state.errorCode() == SplitInstallErrorCode.SERVICE_DIES) {
       // Retry the request.
       return;
    }
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            case SplitInstallSessionStatus.DOWNLOADING:
              int totalBytes = state.totalBytesToDownload();
              int progress = state.bytesDownloaded();
              // Update progress bar.
              break;

            case SplitInstallSessionStatus.INSTALLED:

              // After a module is installed, you can start accessing its content or
              // fire an intent to start an activity in the installed module.
              // For other use cases, see access code and resources from installed modules.

              // If the request is an on demand module for an Android Instant App
              // running on Android 8.0 (API level 26) or higher, you need to
              // update the app context using the SplitInstallHelper API.
        }
    }
}

가능한 설치 요청 상태는 아래 표에 설명되어 있습니다.

요청 상태 설명 추천 작업
PENDING 요청이 수락되었으며 다운로드가 곧 시작됩니다. 사용자에게 다운로드에 관한 정보를 제공하기 위해 진행률 표시줄 같은 UI 구성요소를 초기화합니다.
REQUIRES_USER_CONFIRMATION 다운로드하려면 사용자 확인이 필요합니다. 이 상태는 앱이 Google Play를 통해 설치되지 않은 경우에 주로 나타납니다. 사용자에게 Google Play를 통해 기능을 다운로드했는지 확인하라는 메시지를 표시합니다. 자세한 내용은 사용자 확인을 받는 방법에 관한 섹션을 참고하세요.
DOWNLOADING 다운로드가 진행 중입니다. 다운로드 진행률 표시줄을 제공한다면 SplitInstallSessionState.bytesDownloaded()SplitInstallSessionState.totalBytesToDownload() 메서드를 사용하여 UI를 업데이트합니다(이 표 위에 있는 코드 샘플 참고).
다운로드됨 기기에서 모듈을 다운로드했지만 설치가 아직 시작되지 않았습니다. 앱에서 SplitCompat을 사용 설정하여 다운로드된 모듈에 액세스하고 이 상태가 표시되지 않도록 해야 합니다. 기능 모듈의 코드 및 리소스에 액세스하려면 이렇게 해야 합니다.
INSTALLING 현재 기기가 모듈을 설치 중입니다. 진행률 표시줄을 업데이트합니다. 일반적으로 이 상태는 짧습니다.
INSTALLED 모듈이 기기에 설치되었습니다. 모듈의 코드 및 리소스에 액세스하여 사용자가 계속 진행하도록 합니다.

모듈이 Android 8.0(API 수준 26) 이상에서 실행되는 Android 인스턴트 앱용 모듈이라면 splitInstallHelper를 사용하여 새 모듈로 앱 구성요소를 업데이트해야 합니다.

FAILED 모듈이 기기에 설치되기 전에 요청이 실패했습니다. 요청을 다시 시도하거나 취소하도록 사용자에게 메시지를 표시합니다.
CANCELING 기기가 요청을 취소하는 중입니다. 자세히 알아보려면 설치 요청 취소 방법에 관한 섹션을 참고하세요.
CANCELED 요청이 취소되었습니다.

사용자 확인 받기

일부의 경우 Google Play에서 다운로드 요청을 처리하기 전에 사용자 확인을 요구할 수 있습니다. 예를 들어 Google Play에서 앱을 설치하지 않았거나 모바일 데이터를 통해 대용량 다운로드를 시도하는 경우가 있습니다. 이 경우 요청 상태는 REQUIRES_USER_CONFIRMATION을 보고하며 먼저 앱에서 사용자 확인을 받아야 기기가 요청된 모듈을 다운로드하고 설치할 수 있습니다. 확인을 받으려면 앱에서 사용자에게 다음과 같이 메시지를 표시해야 합니다.

Kotlin

override fun onSessionStateUpdate(state: SplitInstallSessionState) {
    if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
        // Displays a confirmation for the user to confirm the request.
        splitInstallManager.startConfirmationDialogForResult(
          state,
          // an activity result launcher registered via registerForActivityResult
          activityResultLauncher)
    }
    ...
 }

Java

@Override void onSessionStateUpdate(SplitInstallSessionState state) {
    if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
        // Displays a confirmation for the user to confirm the request.
        splitInstallManager.startConfirmationDialogForResult(
          state,
          // an activity result launcher registered via registerForActivityResult
          activityResultLauncher);
    }
    ...
 }

내장된 ActivityResultContracts.StartIntentSenderForResult 계약을 사용하여 활동 결과 런처를 등록할 수 있습니다. Activity Result API를 참고하세요.

요청 상태는 사용자 응답에 따라 다음과 같이 업데이트됩니다.

  • 사용자가 확인을 수락하면 요청 상태가 PENDING으로 변경되고 다운로드가 진행됩니다.
  • 사용자가 확인을 거부하면 요청 상태가 CANCELED로 변경됩니다.
  • 대화상자가 사라지기 전에 사용자가 아무 선택도 하지 않으면 요청 상태는 REQUIRES_USER_CONFIRMATION으로 유지됩니다. 앱은 요청을 완료하기 위해 사용자에게 다시 메시지를 표시할 수 있습니다.

사용자의 응답으로 콜백을 수신하려면 아래와 같이 ActivityResultCallback을 재정의하면 됩니다.

Kotlin

registerForActivityResult(StartIntentSenderForResult()) { result: ActivityResult -> {
        // Handle the user's decision. For example, if the user selects "Cancel",
        // you may want to disable certain functionality that depends on the module.
    }
}

Java

registerForActivityResult(
    new ActivityResultContracts.StartIntentSenderForResult(),
    new ActivityResultCallback<ActivityResult>() {
        @Override
        public void onActivityResult(ActivityResult result) {
            // Handle the user's decision. For example, if the user selects "Cancel",
            // you may want to disable certain functionality that depends on the module.
        }
    });

설치 요청 취소

모듈이 설치되기 전에 앱에서 요청을 취소해야 한다면 아래와 같이 요청의 세션 ID를 사용하여 cancelInstall() 메서드를 호출하면 됩니다.

Kotlin

splitInstallManager
    // Cancels the request for the given session ID.
    .cancelInstall(mySessionId)

Java

splitInstallManager
    // Cancels the request for the given session ID.
    .cancelInstall(mySessionId);

모듈 액세스

모듈 다운로드 이후 다운로드한 모듈의 코드 및 리소스에 액세스하려면 앱은 다운로드한 기능 모듈의 각 활동과 앱 모두에 대해 SplitCompat 라이브러리를 사용 설정해야 합니다.

그러나 플랫폼은 모듈을 다운로드한 후 일정 시간(일부 경우에는 며칠) 동안 모듈의 콘텐츠에 액세스하는 데 다음과 같은 제한사항이 적용됩니다.

  • 플랫폼은 모듈에서 도입한 새 매니페스트 항목을 적용할 수 없습니다.
  • 플랫폼은 알림과 같은 시스템 UI 구성요소의 모듈 리소스에 액세스할 수 없습니다. 이러한 리소스를 즉시 사용해야 한다면 리소스를 앱의 기본 모듈에 포함하는 것이 좋습니다.

SplitCompat 사용 설정

앱이 다운로드한 모듈의 코드 및 리소스에 액세스하려면 다음 섹션에서 설명하는 메서드 중 하나만 사용하여 SplitCompat을 사용 설정해야 합니다.

앱에 SplitCompat을 사용 설정한 이후에는 앱이 액세스하도록 할 기능 모듈의 각 활동에도 SplitCompat을 사용 설정해야 합니다.

매니페스트에 SplitCompatApplication 선언

SplitCompat을 사용 설정하는 가장 간단한 방법은 아래와 같이 앱 매니페스트에서 SplitCompatApplicationApplication 서브클래스로 선언하는 것입니다.

<application
    ...
    android:name="com.google.android.play.core.splitcompat.SplitCompatApplication">
</application>

앱이 기기에 설치된 후에는 다운로드한 기능 모듈의 코드 및 리소스에 자동으로 액세스할 수 있습니다.

런타임 시 SplitCompat 호출

런타임 시 특정 활동 또는 서비스에서 SplitCompat을 사용 설정할 수도 있습니다. 기능 모듈에 포함된 활동을 실행하려면 이런 방법으로 SplitCompat을 사용 설정해야 합니다. 이렇게 하려면 아래와 같이 attachBaseContext를 재정의합니다.

맞춤 Application 클래스가 있다면 앱에서 SplitCompat을 사용 설정하기 위해 아래와 같이 SplitCompatApplication을 확장합니다.

Kotlin

class MyApplication : SplitCompatApplication() {
    ...
}

자바

public class MyApplication extends SplitCompatApplication {
    ...
}

SplitCompatApplication은 단순히 ContextWrapper.attachBaseContext()를 재정의하여 SplitCompat.install(Context applicationContext)을 포함합니다. Application 클래스에서 SplitCompatApplication을 확장하지 않도록 하려면 다음과 같이 직접 attachBaseContext()를 재정의할 수 있습니다.

Kotlin

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    // Emulates installation of future on demand modules using SplitCompat.
    SplitCompat.install(this)
}

자바

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    // Emulates installation of future on demand modules using SplitCompat.
    SplitCompat.install(this);
}

주문형 모듈이 인스턴트 앱 및 설치된 앱과 모두 호환된다면 다음과 같이 SplitCompat을 조건부로 호출할 수 있습니다.

Kotlin

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    if (!InstantApps.isInstantApp(this)) {
        SplitCompat.install(this)
    }
}

Java

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    if (!InstantApps.isInstantApp(this)) {
        SplitCompat.install(this);
    }
}

모듈 활동에 SplitCompat 사용 설정

기본 앱에 SplitCompat을 사용 설정한 이후에는 앱이 기능 모듈에서 다운로드하는 각 활동에 SplitCompat을 사용 설정해야 합니다. 이렇게 하려면 다음과 같이 SplitCompat.installActivity() 메서드를 사용합니다.

Kotlin

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    // Emulates installation of on demand modules using SplitCompat.
    SplitCompat.installActivity(this)
}

Java

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    // Emulates installation of on demand modules using SplitCompat.
    SplitCompat.installActivity(this);
}

기능 모듈에 정의된 구성요소에 액세스

기능 모듈에 정의된 활동 시작

SplitCompat을 사용 설정한 후 startActivity()를 사용하여 기능 모듈에 정의된 활동을 실행할 수 있습니다.

Kotlin

startActivity(Intent()
  .setClassName("com.package", "com.package.module.MyActivity")
  .setFlags(...))

자바

startActivity(new Intent()
  .setClassName("com.package", "com.package.module.MyActivity")
  .setFlags(...));

setClassName의 첫 번째 매개변수는 앱 패키지 이름이고 두 번째 매개변수는 활동의 전체 클래스 이름입니다.

주문형으로 다운로드한 기능 모듈에 활동이 있는 경우 활동에서 SplitCompat을 사용 설정해야 합니다.

기능 모듈에 정의된 서비스 시작

SplitCompat을 사용 설정한 후 startService()를 사용하여 기능 모듈에 정의된 서비스를 실행할 수 있습니다.

Kotlin

startService(Intent()
  .setClassName("com.package", "com.package.module.MyService")
  .setFlags(...))

Java

startService(new Intent()
  .setClassName("com.package", "com.package.module.MyService")
  .setFlags(...));

기능 모듈에 정의된 구성요소 내보내기

선택적 모듈 안에 내보낸 Android 구성요소가 포함되면 안 됩니다.

선택적 모듈에 내보낸 구성요소가 포함된 경우 빌드 시스템은 모든 모듈의 매니페스트 항목을 기본 모듈로 병합합니다. 선택적 모듈이 설치되기 전에도 내보낸 구성요소에 액세스할 수 있으며 다른 앱에서 누락된 코드를 호출하여 비정상 종료가 발생할 수 있기 때문입니다.

이는 내부 구성요소에는 문제가 되지 않습니다. 내부 구성요소는 앱에서만 액세스하므로 앱은 구성요소를 액세스하기 전에 모듈이 설치되어 있는지 확인할 수 있습니다.

내보낸 구성요소가 필요하고 구성요소의 콘텐츠가 선택적 모듈에 포함되도록 하려면 프록시 패턴 구현을 고려해보세요. 이렇게 하려면 내보낸 구성요소의 프록시를 기본에 추가하면 됩니다. 그러면 모듈에 액세스할 때 프록시 구성요소가 콘텐츠를 포함하는 모듈이 존재하는지 확인할 수 있습니다. 모듈이 존재하면 프록시 구성요소는 호출자 앱에서 인텐트를 중계해주는 Intent를 통해 모듈에서 내부 구성요소를 시작할 수 있습니다. 모듈이 존재하지 않는다면 구성요소는 모듈을 다운로드하거나 호출자 앱에 적절한 오류 메시지를 반환할 수 있습니다.

설치된 모듈의 코드 및 리소스 액세스

기본 애플리케이션 컨텍스트와 기능 모듈의 활동에 SplitCompat을 사용 설정하면 선택적 모듈이 설치된 후 기본 APK의 일부인 것처럼 기능 모듈의 코드와 리소스를 사용할 수 있습니다.

다른 모듈에서 코드에 액세스

모듈에서 기본 코드에 액세스

기본 모듈 내부에 있는 코드는 다른 모듈에서 직접 사용할 수 있습니다. 별도로 취해야 할 조치는 없으며 필요한 클래스를 가져와서 사용하면 됩니다.

다른 모듈에서 모듈 코드에 액세스

모듈 내부의 객체나 클래스는 다른 모듈에서 직접 정적으로 액세스할 수 없지만, 리플렉션을 사용하여 간접적으로 액세스할 수 있습니다.

다만, 리플렉션의 성능 비용이 발생하므로 이러한 경우가 얼마나 자주 발생하는지 고려해야 합니다. 복잡한 사용 사례의 경우 Dagger 2와 같은 종속 항목 삽입 프레임워크를 사용하여 애플리케이션 전체 기간 동안 단일 리플렉션 호출을 보장해야 합니다.

인스턴스화한 후 객체와의 상호작용을 간소화하려면 인터페이스는 기본 모듈에 정의하고 구현은 기능 모듈에 정의하는 것이 좋습니다. 예:

Kotlin

// In the base module
interface MyInterface {
  fun hello(): String
}

// In the feature module
object MyInterfaceImpl : MyInterface {
  override fun hello() = "Hello"
}

// In the base module, where we want to access the feature module code
val stringFromModule = (Class.forName("com.package.module.MyInterfaceImpl")
    .kotlin.objectInstance as MyInterface).hello();

Java

// In the base module
public interface MyInterface {
  String hello();
}

// In the feature module
public class MyInterfaceImpl implements MyInterface {
  @Override
  public String hello() {
    return "Hello";
  }
}

// In the base module, where we want to access the feature module code
String stringFromModule =
   ((MyInterface) Class.forName("com.package.module.MyInterfaceImpl").getConstructor().newInstance()).hello();

다른 모듈에서 리소스 및 애셋 액세스

모듈이 설치되면 표준 방식으로 모듈 내부에 있는 리소스와 애셋에 액세스할 수 있으며, 여기에는 두 가지 주의사항이 있습니다.

  • 다른 모듈에서 리소스에 액세스하고 있다면 모듈은 리소스 식별자에 액세스 권한이 없지만, 이름으로 리소스에 액세스할 수 있습니다. 리소스를 참조하는 데 사용할 패키지는 리소스가 정의된 모듈의 패키지입니다.
  • 앱에 설치된 다른 모듈에서 새로 설치된 모듈에 있는 애셋이나 리소스에 액세스하려면 애플리케이션 컨텍스트를 사용하여 액세스해야 합니다. 리소스에 액세스하려는 구성요소의 컨텍스트는 아직 업데이트되지 않습니다. 또는, 기능 모듈이 설치된 후에 구성요소를 다시 만들거나(예: Activity.recreate() 호출) 구성요소에 SplitCompat을 재설치하면 됩니다.

주문형 전송을 사용하여 앱에 네이티브 코드 로드

기능 모듈의 주문형 전송을 사용할 때는 ReLinker를 사용하여 모든 네이티브 라이브러리를 로드하는 것이 좋습니다. ReLinker는 기능 모듈 설치 후 네이티브 라이브러리를 로드할 때 발생하는 문제를 해결합니다. ReLinker에 관한 자세한 내용은 Android JNI 도움말을 참고하세요.

선택적 모듈에서 네이티브 코드 로드

분할이 설치되면 ReLinker를 통해 네이티브 코드를 로드하는 것이 좋습니다. 인스턴트 앱의 경우 이 특별한 메서드를 사용해야 합니다.

System.loadLibrary()를 사용하여 네이티브 코드를 로드하고 네이티브 라이브러리에 모듈의 다른 라이브러리에 종속된 항목이 있는 경우 먼저 다른 라이브러리를 수동으로 로드해야 합니다. ReLinker를 사용하는 경우 동등한 작업은 Relinker.recursively().loadLibrary()입니다.

네이티브 코드에서 dlopen()을 사용하여 선택적 모듈에 정의된 라이브러리를 로드하고 있다면 라이브러리의 상대 경로로는 작동하지 않습니다. 가장 좋은 방법은 ClassLoader.findLibrary()를 통해 자바 코드에서 라이브러리의 절대 경로를 가져온 다음 dlopen() 호출에 사용하는 것입니다. 네이티브 코드를 입력하기 전에 이 작업을 실행하거나 네이티브 코드에서 자바로 JNI 호출을 사용하세요.

설치된 Android 인스턴트 앱에 액세스

Android 인스턴트 앱 모듈이 INSTALLED로 보고된 후에는 새로고침한 앱 Context를 사용하여 모듈의 코드와 리소스에 액세스할 수 있습니다. 모듈을 설치하기 에 앱에서 만든 컨텍스트(예: 이미 변수에 저장된 컨텍스트)는 새 모듈의 콘텐츠를 포함하지 않습니다. 하지만 새로운 컨텍스트는 새 모듈의 콘텐츠를 포함하며, 예를 들어 createPackageContext를 사용하여 얻을 수 있습니다.

Kotlin

// Generate a new context as soon as a request for a new module
// reports as INSTALLED.
override fun onStateUpdate(state: SplitInstallSessionState ) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            ...
            SplitInstallSessionStatus.INSTALLED -> {
                val newContext = context.createPackageContext(context.packageName, 0)
                // If you use AssetManager to access your app’s raw asset files, you’ll need
                // to generate a new AssetManager instance from the updated context.
                val am = newContext.assets
            }
        }
    }
}

Java

// Generate a new context as soon as a request for a new module
// reports as INSTALLED.
@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            ...
            case SplitInstallSessionStatus.INSTALLED:
                Context newContext = context.createPackageContext(context.getPackageName(), 0);
                // If you use AssetManager to access your app’s raw asset files, you’ll need
                // to generate a new AssetManager instance from the updated context.
                AssetManager am = newContext.getAssets();
        }
    }
}

Android 8.0 이상의 Android 인스턴트 앱

Android 8.0(API 수준 26) 이상에서 Android 인스턴트 앱의 주문형 모듈을 요청할 때 설치 요청이 INSTALLED로 보고된 후 SplitInstallHelper.updateAppInfo(Context context)를 호출하여 새 모듈의 컨텍스트로 앱을 업데이트해야 합니다. 그렇게 하지 않으면 앱이 모듈의 코드와 리소스를 인식할 수 없습니다. 앱의 메타데이터를 업데이트한 후에는 아래와 같이 새 Handler를 호출하여 다음 기본 스레드 이벤트 동안 모듈의 콘텐츠를 로드해야 합니다.

Kotlin

override fun onStateUpdate(state: SplitInstallSessionState ) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            ...
            SplitInstallSessionStatus.INSTALLED -> {
                // You need to perform the following only for Android Instant Apps
                // running on Android 8.0 (API level 26) and higher.
                if (BuildCompat.isAtLeastO()) {
                    // Updates the app’s context with the code and resources of the
                    // installed module.
                    SplitInstallHelper.updateAppInfo(context)
                    Handler().post {
                        // Loads contents from the module using AssetManager
                        val am = context.assets
                        ...
                    }
                }
            }
        }
    }
}

Java

@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            ...
            case SplitInstallSessionStatus.INSTALLED:
            // You need to perform the following only for Android Instant Apps
            // running on Android 8.0 (API level 26) and higher.
            if (BuildCompat.isAtLeastO()) {
                // Updates the app’s context with the code and resources of the
                // installed module.
                SplitInstallHelper.updateAppInfo(context);
                new Handler().post(new Runnable() {
                    @Override public void run() {
                        // Loads contents from the module using AssetManager
                        AssetManager am = context.getAssets();
                        ...
                    }
                });
            }
        }
    }
}

C/C++ 라이브러리 로드

기기가 인스턴트 앱에 이미 다운로드한 모듈에서 C/C++ 라이브러리를 로드하려면 아래와 같이 SplitInstallHelper.loadLibrary(Context context, String libName)를 사용합니다.

Kotlin

override fun onStateUpdate(state: SplitInstallSessionState) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            SplitInstallSessionStatus.INSTALLED -> {
                // Updates the app’s context as soon as a module is installed.
                val newContext = context.createPackageContext(context.packageName, 0)
                // To load C/C++ libraries from an installed module, use the following API
                // instead of System.load().
                SplitInstallHelper.loadLibrary(newContext, my-cpp-lib)
                ...
            }
        }
    }
}

Java

public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            case SplitInstallSessionStatus.INSTALLED:
                // Updates the app’s context as soon as a module is installed.
                Context newContext = context.createPackageContext(context.getPackageName(), 0);
                // To load C/C++ libraries from an installed module, use the following API
                // instead of System.load().
                SplitInstallHelper.loadLibrary(newContext, my-cpp-lib);
                ...
        }
    }
}

알려진 제한사항

  • 선택적 모듈의 리소스나 애셋에 액세스하는 활동에서는 Android WebView를 사용할 수 없습니다. 이는 Android API 수준 28 이하에서 WebView와 SplitCompat이 호환되지 않기 때문입니다.
  • Android ApplicationInfo 객체, 객체의 콘텐츠 또는 이러한 객체를 포함하는 객체를 앱 내부에 캐시할 수 없습니다. 이러한 객체는 항상 필요에 따라 앱 컨텍스트에서 가져와야 합니다. 이러한 객체를 캐싱하면 기능 모듈을 설치할 때 앱이 다운될 수 있습니다.

설치된 모듈 관리

현재 기기에 설치된 기능 모듈을 확인하려면 아래와 같이 설치된 모듈 이름의 Set<String>을 반환하는 SplitInstallManager.getInstalledModules()를 호출하면 됩니다.

Kotlin

val installedModules: Set<String> = splitInstallManager.installedModules

Java

Set<String> installedModules = splitInstallManager.getInstalledModules();

모듈 제거

아래와 같이 SplitInstallManager.deferredUninstall(List<String> moduleNames)을 호출하여 모듈을 제거하도록 기기에 요청할 수 있습니다.

Kotlin

// Specifies two feature modules for deferred uninstall.
splitInstallManager.deferredUninstall(listOf("pictureMessages", "promotionalFilters"))

자바

// Specifies two feature modules for deferred uninstall.
splitInstallManager.deferredUninstall(Arrays.asList("pictureMessages", "promotionalFilters"));

모듈 제거는 즉시 실행되지 않습니다. 즉, 저장공간을 절약하기 위해 기기는 필요에 따라 백그라운드에서 모듈을 제거합니다. 기기에서 모듈을 삭제했는지 확인하려면 이전 섹션에 설명한 대로 SplitInstallManager.getInstalledModules()를 호출하여 그 결과를 검사하면 됩니다.

추가 언어 리소스 다운로드

App Bundle을 통해 기기는 앱을 실행하는 데 필요한 코드와 리소스만 다운로드합니다. 따라서 언어 리소스의 경우 사용자 기기는 기기의 설정에 현재 선택된 하나 이상의 언어와 일치하는 앱의 언어 리소스만 다운로드합니다.

앱에서 추가 언어 리소스에 액세스하도록 하려면(예: 인앱 언어 선택 도구 구현) Play Feature Delivery 라이브러리를 사용하여 리소스를 주문형으로 다운로드하면 됩니다. 이 프로세스는 아래와 같이 기능 모듈을 다운로드하는 것과 비슷합니다.

Kotlin

// Captures the user’s preferred language and persists it
// through the app’s SharedPreferences.
sharedPrefs.edit().putString(LANGUAGE_SELECTION, "fr").apply()
...

// Creates a request to download and install additional language resources.
val request = SplitInstallRequest.newBuilder()
        // Uses the addLanguage() method to include French language resources in the request.
        // Note that country codes are ignored. That is, if your app
        // includes resources for “fr-FR” and “fr-CA”, resources for both
        // country codes are downloaded when requesting resources for "fr".
        .addLanguage(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
        .build()

// Submits the request to install the additional language resources.
splitInstallManager.startInstall(request)

자바

// Captures the user’s preferred language and persists it
// through the app’s SharedPreferences.
sharedPrefs.edit().putString(LANGUAGE_SELECTION, "fr").apply();
...

// Creates a request to download and install additional language resources.
SplitInstallRequest request =
    SplitInstallRequest.newBuilder()
        // Uses the addLanguage() method to include French language resources in the request.
        // Note that country codes are ignored. That is, if your app
        // includes resources for “fr-FR” and “fr-CA”, resources for both
        // country codes are downloaded when requesting resources for "fr".
        .addLanguage(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
        .build();

// Submits the request to install the additional language resources.
splitInstallManager.startInstall(request);

요청은 기능 모듈의 요청인 것처럼 처리됩니다. 즉, 평소와 같이 요청 상태를 모니터링할 수 있습니다.

앱에 추가 언어 리소스가 당장 필요하지 않다면 아래와 같이 앱이 백그라운드에 있는 동안 설치를 연기할 수 있습니다.

Kotlin

splitInstallManager.deferredLanguageInstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))

Java

splitInstallManager.deferredLanguageInstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));

다운로드한 언어 리소스 액세스

다운로드한 언어 리소스에 액세스하려면 앱은 아래와 같이 리소스에 액세스해야 하는 각 활동의 attachBaseContext() 메서드 내에서 SplitCompat.installActivity() 메서드를 실행해야 합니다.

Kotlin

override fun attachBaseContext(base: Context) {
  super.attachBaseContext(base)
  SplitCompat.installActivity(this)
}

자바

@Override
protected void attachBaseContext(Context base) {
  super.attachBaseContext(base);
  SplitCompat.installActivity(this);
}

앱에서 다운로드한 언어 리소스를 사용하려는 각 활동에 대해 기본 컨텍스트를 업데이트하고 Configuration을 통해 새 언어를 설정해야 합니다.

Kotlin

override fun attachBaseContext(base: Context) {
  val configuration = Configuration()
  configuration.setLocale(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
  val context = base.createConfigurationContext(configuration)
  super.attachBaseContext(context)
  SplitCompat.install(this)
}

자바

@Override
protected void attachBaseContext(Context base) {
  Configuration configuration = new Configuration();
  configuration.setLocale(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));
  Context context = base.createConfigurationContext(configuration);
  super.attachBaseContext(context);
  SplitCompat.install(this);
}

이러한 변경사항을 적용하려면 새 언어가 설치되고 사용할 준비가 된 후에 활동을 다시 만들어야 합니다. Activity#recreate() 메서드를 사용하면 됩니다.

Kotlin

when (state.status()) {
  SplitInstallSessionStatus.INSTALLED -> {
      // Recreates the activity to load resources for the new language
      // preference.
      activity.recreate()
  }
  ...
}

Java

switch (state.status()) {
  case SplitInstallSessionStatus.INSTALLED:
      // Recreates the activity to load resources for the new language
      // preference.
      activity.recreate();
  ...
}

추가 언어 리소스 제거

기능 모듈과 마찬가지로 언제든지 추가 리소스를 제거할 수 있습니다. 제거를 요청하기 전에 다음과 같이 먼저 현재 설치된 언어를 확인해야 합니다.

Kotlin

val installedLanguages: Set<String> = splitInstallManager.installedLanguages

Java

Set<String> installedLanguages = splitInstallManager.getInstalledLanguages();

그런 다음, 아래와 같이 deferredLanguageUninstall() 메서드를 사용하여 제거할 언어를 결정하면 됩니다.

Kotlin

splitInstallManager.deferredLanguageUninstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))

Java

splitInstallManager.deferredLanguageUninstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));

모듈 설치 로컬 테스트

Play Feature Delivery 라이브러리를 사용하면 Play 스토어에 연결하지 않고도 앱이 다음을 실행할 수 있는 기능을 로컬에서 테스트할 수 있습니다.

이 페이지에서는 Play Feature Delivery에서 앱의 분할 APK를 자동으로 사용하여 Play 스토어에서 모듈의 요청, 다운로드 및 설치를 시뮬레이션할 수 있도록 앱의 분할 APK를 테스트 기기에 배포하는 방법에 관해 설명합니다.

앱의 로직을 변경할 필요는 없지만 다음 요구사항을 충족해야 합니다.

  • 최신 버전의 bundletool을 다운로드하여 설치합니다. 앱의 번들에서 설치 가능한 새 APK 세트를 빌드하려면 bundletool이 필요합니다.

APK 세트 빌드

앱의 분할 APK를 아직 빌드하지 않았다면 다음과 같이 빌드합니다.

  1. 다음 방법 중 하나를 사용하여 앱의 App Bundle을 빌드합니다.
  2. bundletool을 사용하여 다음 명령어로 모든 기기 구성을 위한 APK 세트를 생성합니다.

    bundletool build-apks --local-testing
      --bundle my_app.aab
      --output my_app.apks
    

--local-testing 플래그는 Play 스토어에 연결하지 않고도 Play Feature Delivery 라이브러리가 로컬 분할 APK를 사용하여 기능 모듈 설치를 테스트하는 것을 알 수 있도록 하는 메타데이터를 APK의 매니페스트에 포함합니다.

기기에 앱 배포

--local-testing 플래그를 사용하여 APK 세트를 빌드한 후 bundletool을 사용하여 앱의 기본 버전을 설치하고 추가 APK를 기기의 로컬 저장소로 전송합니다. 다음 명령어를 사용하여 두 작업을 모두 실행할 수 있습니다.

bundletool install-apks --apks my_app.apks

이제 앱을 시작하고 사용자 플로우를 완료하여 기능 모듈을 다운로드하고 설치할 때 Play Feature Delivery 라이브러리는 bundletool이 기기의 로컬 저장소로 전송한 APK를 사용합니다.

네트워크 오류 시뮬레이션

Play 스토어에서 모듈 설치를 시뮬레이션하기 위해 Play Feature Delivery 라이브러리는 FakeSplitInstallManager라는 SplitInstallManager의 대안을 사용하여 모듈을 요청합니다. --local-testing 플래그와 함께 bundletool을 사용해 APK 세트를 빌드하여 테스트 기기에 배포할 때 앱의 API 호출을 자동으로 전환하여 SplitInstallManager 대신 FakeSplitInstallManager를 호출하도록 Play Feature Delivery 라이브러리에 지시하는 메타데이터가 포함됩니다.

FakeSplitInstallManager에는 다음에 앱이 모듈 설치를 요청할 때 네트워크 오류를 시뮬레이션할 수 있는 부울 플래그가 포함되어 있습니다. 테스트에서 FakeSplitInstallManager에 액세스하려면 아래와 같이 FakeSplitInstallManagerFactory를 사용하여 그 인스턴스를 가져옵니다.

Kotlin

// Creates an instance of FakeSplitInstallManager with the app's context.
val fakeSplitInstallManager = FakeSplitInstallManagerFactory.create(context)
// Tells Play Feature Delivery Library to force the next module request to
// result in a network error.
fakeSplitInstallManager.setShouldNetworkError(true)

Java

// Creates an instance of FakeSplitInstallManager with the app's context.
FakeSplitInstallManager fakeSplitInstallManager =
    FakeSplitInstallManagerFactory.create(context);
// Tells Play Feature Delivery Library to force the next module request to
// result in a network error.
fakeSplitInstallManager.setShouldNetworkError(true);