Asset Delivery 통합 (Kotlin 및 자바)

이 가이드의 단계에 따라 Java 코드에서 앱의 애셋 팩에 액세스할 수 있습니다.

Kotlin 및 Java용 빌드

프로젝트의 Android App Bundle에 Play Asset Delivery를 빌드하려면 다음 단계를 따르세요. 이러한 단계를 진행하는 데 Android 스튜디오를 사용할 필요는 없습니다.

  1. 프로젝트의 build.gradle 파일에서 Android Gradle 플러그인 버전을 4.0.0 이상으로 업데이트합니다.

  2. 프로젝트의 최상위 디렉터리에 애셋 팩용 디렉터리를 생성합니다. 이 디렉터리 이름은 애셋 팩 이름으로 사용됩니다. 애셋 팩 이름은 문자로 시작되어야 하며 문자, 숫자 및 밑줄만 포함할 수 있습니다.

  3. 애셋 팩 디렉터리에서 build.gradle 파일을 만들고 다음 코드를 추가합니다. 애셋 팩 이름과 하나의 전송 유형만 지정해야 합니다.

    Groovy

    // In the asset pack's build.gradle file:
    plugins {
      id 'com.android.asset-pack'
    }
    
    assetPack {
        packName = "asset-pack-name" // Directory name for the asset pack
        dynamicDelivery {
            deliveryType = "[ install-time | fast-follow | on-demand ]"
        }
    }
    

    Kotlin

    // In the asset pack's build.gradle.kts file:
    plugins {
      id("com.android.asset-pack")
    }
    
    assetPack {
      packName.set("asset-pack-name") // Directory name for the asset pack
      dynamicDelivery {
        deliveryType.set("[ install-time | fast-follow | on-demand ]")
      }
    }
    
  4. 프로젝트의 앱 build.gradle 파일에서 아래와 같이 프로젝트의 모든 애셋 팩 이름을 추가합니다.

    Groovy

    // In the app build.gradle file:
    android {
        ...
        assetPacks = [":asset-pack-name", ":asset-pack2-name"]
    }
    

    Kotlin

    // In the app build.gradle.kts file:
    android {
        ...
        assetPacks += listOf(":asset-pack-name", ":asset-pack2-name")
    }
    
  5. 프로젝트의 settings.gradle 파일에서 아래와 같이 프로젝트의 모든 애셋 팩을 포함합니다.

    Groovy

    // In the settings.gradle file:
    include ':app'
    include ':asset-pack-name'
    include ':asset-pack2-name'
    

    Kotlin

    // In the settings.gradle.kts file:
    include(":app")
    include(":asset-pack-name")
    include(":asset-pack2-name")
    
  6. 애셋 팩 디렉터리에 하위 디렉터리(src/main/assets)를 만듭니다.

  7. 애셋을 src/main/assets 디렉터리에 배치합니다. 여기에도 하위 디렉터리를 만들 수 있습니다. 이제 앱의 디렉터리 구조는 다음과 같습니다.

    • build.gradle
    • settings.gradle
    • app/
    • asset-pack-name/build.gradle
    • asset-pack-name/src/main/assets/your-asset-directories
  8. Gradle로 Android App Bundle을 빌드합니다. 생성된 App Bundle에서 루트 수준 디렉터리에는 이제 다음이 포함됩니다.

    • asset-pack-name/manifest/AndroidManifest.xml: 애셋 팩의 식별자 및 전송 모드를 구성합니다.
    • asset-pack-name/assets/your-asset-directories: 애셋 팩의 일부로 제공되는 모든 애셋이 포함된 디렉터리

    Gradle은 각 애셋 팩의 매니페스트를 생성하고 assets/ 디렉터리를 자동으로 출력합니다.

  9. (선택사항) 빠른 추적 및 주문형 제공을 사용할 계획이면 Play Asset Delivery 라이브러리를 포함합니다.

    Groovy

    implementation "com.google.android.play:asset-delivery:2.2.1"
    // For Kotlin use asset-delivery-ktx
    implementation "com.google.android.play:asset-delivery-ktx:2.2.1"
    

    Kotlin

    implementation("com.google.android.play:asset-delivery:2.2.1")
    // For Kotlin use core-ktx
    implementation("com.google.android.play:asset-delivery-ktx:2.2.1")
    

  10. (선택사항) 다양한 텍스처 압축 형식을 지원하도록 App Bundle을 구성합니다.

Play Asset Delivery API와 통합

Play Asset Delivery Java API는 애셋 팩을 요청하고 다운로드를 관리하며 애셋에 액세스하기 위한 AssetPackManager 클래스를 제공합니다. 먼저 프로젝트에 Play Asset Delivery 라이브러리를 추가해야 합니다.

액세스하려는 애셋 팩의 전송 유형에 따라 이 API를 구현합니다. 이러한 단계는 다음 플로우 차트에 나와 있습니다.

자바 프로그래밍 언어의 애셋 팩 흐름 다이어그램

그림 1. 애셋 팩 액세스 흐름 다이어그램

설치 시 제공

install-time으로 구성된 애셋 팩은 앱 실행 시 즉시 사용할 수 있습니다. 이 모드에서 제공되는 애셋에 액세스하려면 다음과 같이 Java AssetManager API를 사용하세요.

Kotlin

import android.content.res.AssetManager
...
val context: Context = createPackageContext("com.example.app", 0)
val assetManager: AssetManager = context.assets
val stream: InputStream = assetManager.open("asset-name")

Java

import android.content.res.AssetManager;
...
Context context = createPackageContext("com.example.app", 0);
AssetManager assetManager = context.getAssets();
InputStream is = assetManager.open("asset-name");

빠른 추적 및 주문형 제공

다음 섹션에서는 애셋 팩을 다운로드하기 전에 애셋 팩에 관한 정보를 확인하는 방법, API를 호출하여 다운로드를 시작하는 방법 및 다운로드한 팩에 액세스하는 방법을 보여줍니다. 이 섹션은 fast-followon-demand 애셋 팩에 적용됩니다.

상태 확인

각 애셋 팩은 앱의 내부 저장소에 있는 별도의 폴더에 저장됩니다. getPackLocation() 메서드를 사용하여 애셋 팩의 루트 폴더를 확인할 수 있습니다. 이 메서드는 다음 값을 반환합니다.

반환 값 상태
유효한 AssetPackLocation 객체 애셋 팩 루트 폴더는 assetsPath()에서 즉시 액세스할 수 있도록 준비되어 있습니다.
null 알 수 없는 애셋 팩 또는 애셋은 사용할 수 없습니다.

애셋 팩에 관한 다운로드 정보 확인

앱은 애셋 팩을 가져오기 전에 다운로드 크기를 공개해야 합니다. requestPackStates() 또는 getPackStates() 메서드를 사용하여 다운로드 크기를 확인하고 팩이 이미 다운로드 중인지 여부를 확인합니다.

Kotlin

suspend fun requestPackStates(packNames: List<String>): AssetPackStates

Java

Task<AssetPackStates> getPackStates(List<String> packNames)

requestPackStates()AssetPackStates 객체를 반환하는 정지 함수이고, getPackStates()Task<AssetPackStates>를 반환하는 비동기 메서드입니다. AssetPackStates 객체의 packStates() 메서드는 Map<String, AssetPackState>를 반환합니다. 다음과 같이 이 맵에는 요청된 각 애셋 팩의 상태가 포함되며 애셋 팩의 이름으로 키가 지정됩니다.

Kotlin

AssetPackStates#packStates(): Map<String, AssetPackState>

Java

Map<String, AssetPackState> AssetPackStates#packStates()

최종 요청은 다음으로 표시됩니다.

Kotlin

const val assetPackName = "assetPackName"
coroutineScope.launch {
  try {
    val assetPackStates: AssetPackStates =
      manager.requestPackStates(listOf(assetPackName))
    val assetPackState: AssetPackState =
      assetPackStates.packStates()[assetPackName]
  } catch (e: RuntimeExecutionException) {
    Log.d("MainActivity", e.message)
  }
}

Java

final String assetPackName = "myasset";

assetPackManager
    .getPackStates(Collections.singletonList(assetPackName))
    .addOnCompleteListener(new OnCompleteListener<AssetPackStates>() {
        @Override
        public void onComplete(Task<AssetPackStates> task) {
            AssetPackStates assetPackStates;
            try {
                assetPackStates = task.getResult();
                AssetPackState assetPackState =
                    assetPackStates.packStates().get(assetPackName);
            } catch (RuntimeExecutionException e) {
                Log.d("MainActivity", e.getMessage());
                return;
            })

다음 AssetPackState 메서드는 애셋 팩 크기, 지금까지 다운로드한 용량(요청된 경우) 및 이미 앱으로 전송된 용량을 제공합니다.

애셋 팩의 상태를 가져오려면 status() 메서드를 사용합니다. 이 메서드는 AssetPackStatus 클래스의 상수 필드에 해당하는 정수로 상태를 반환합니다. 아직 설치되지 않은 애셋 팩의 상태는 AssetPackStatus.NOT_INSTALLED입니다.

요청이 실패하면 반환 값이 AssetPackErrorCode 클래스의 상수 필드에 상응하는 errorCode() 메서드를 사용해야 합니다.

설치

다음과 같이 requestFetch() 또는 fetch() 메서드를 사용하여 애셋 팩을 처음 다운로드하거나 애셋 팩 업데이트를 호출하여 완료할 수 있습니다.

Kotlin

suspend fun AssetPackManager.requestFetch(packs: List<String>): AssetPackStates

Java

Task<AssetPackStates> fetch(List<String> packNames)

이 메서드는 팩 목록과 최초 다운로드 상태 및 크기가 포함된 AssetPackStates 객체를 반환합니다. requestFetch() 또는 fetch()를 통해 요청된 애셋 팩을 이미 다운로드 중이면 다운로드 상태가 반환되고 추가 다운로드가 시작되지 않습니다.

다운로드 상태 모니터링

애셋 팩의 설치 진행률을 추적하려면 AssetPackStateUpdatedListener를 구현해야 합니다. 상태 업데이트는 팩별로 분류되어 개별 애셋 팩의 상태 추적을 지원합니다. 요청과 관련한 다른 모든 다운로드가 완료되기 전에 사용 가능한 애셋 팩을 사용할 수 있습니다.

Kotlin

fun registerListener(listener: AssetPackStateUpdatedListener)
fun unregisterListener(listener: AssetPackStateUpdatedListener)

Java

void registerListener(AssetPackStateUpdatedListener listener)
void unregisterListener(AssetPackStateUpdatedListener listener)

대용량 다운로드

다운로드 용량이 200MB보다 크고 사용자가 Wi-Fi에 연결되어 있지 않은 경우 사용자가 모바일 데이터 연결을 사용하여 다운로드를 진행하는 데 명시적으로 동의할 때까지 다운로드가 시작되지 않습니다. 마찬가지로 다운로드 용량이 크고 사용자가 Wi-Fi를 사용할 수 없게 되는 경우 다운로드가 일시중지되며 모바일 데이터 연결을 사용하여 계속 진행하려면 명시적인 동의가 필요합니다. 일시중지된 팩의 상태는 WAITING_FOR_WIFI입니다. 사용자에게 동의를 요청하는 메시지를 표시하는 UI 흐름을 트리거하려면 showConfirmationDialog() 메서드를 사용합니다.

앱이 이 메서드를 호출하지 않으면 다운로드가 일시중지되며 사용자가 Wi-Fi에 다시 연결될 때만 자동으로 다운로드가 다시 시작됩니다.

필수 사용자 확인

팩이 REQUIRES_USER_CONFIRMATION 상태이면 사용자가 showConfirmationDialog()로 표시되는 대화상자를 수락할 때까지 다운로드가 진행되지 않습니다. 이 상태는 Play에서 앱을 인식하지 못하는 경우(예: 앱이 사이드로드된 경우) 발생할 수 있습니다. 이 경우 showConfirmationDialog()를 호출하면 앱이 업데이트됩니다. 업데이트 후에는 애셋을 다시 요청해야 합니다.

다음은 리스너 구현의 예입니다.

Kotlin

private val activityResultLauncher = registerForActivityResult(
    ActivityResultContracts.StartIntentSenderForResult()
) { result ->
    if (result.resultCode == RESULT_OK) {
        Log.d(TAG, "Confirmation dialog has been accepted.")
    } else if (result.resultCode == RESULT_CANCELED) {
        Log.d(TAG, "Confirmation dialog has been denied by the user.")
    }
}

assetPackManager.registerListener { assetPackState ->
  when(assetPackState.status()) {
    AssetPackStatus.PENDING -> {
      Log.i(TAG, "Pending")
    }
    AssetPackStatus.DOWNLOADING -> {
      val downloaded = assetPackState.bytesDownloaded()
      val totalSize = assetPackState.totalBytesToDownload()
      val percent = 100.0 * downloaded / totalSize

      Log.i(TAG, "PercentDone=" + String.format("%.2f", percent))
    }
    AssetPackStatus.TRANSFERRING -> {
      // 100% downloaded and assets are being transferred.
      // Notify user to wait until transfer is complete.
    }
    AssetPackStatus.COMPLETED -> {
      // Asset pack is ready to use. Start the game.
    }
    AssetPackStatus.FAILED -> {
      // Request failed. Notify user.
      Log.e(TAG, assetPackState.errorCode())
    }
    AssetPackStatus.CANCELED -> {
      // Request canceled. Notify user.
    }
    AssetPackStatus.WAITING_FOR_WIFI,
    AssetPackStatus.REQUIRES_USER_CONFIRMATION -> {
      if (!confirmationDialogShown) {
        assetPackManager.showConfirmationDialog(activityResultLauncher);
        confirmationDialogShown = true
      }
    }
    AssetPackStatus.NOT_INSTALLED -> {
      // Asset pack is not downloaded yet.
    }
    AssetPackStatus.UNKNOWN -> {
      Log.wtf(TAG, "Asset pack status unknown")
    }
  }
}

Java

assetPackStateUpdateListener = new AssetPackStateUpdateListener() {
    private final ActivityResultLauncher<IntentSenderRequest> activityResultLauncher =
      registerForActivityResult(
          new ActivityResultContracts.StartIntentSenderForResult(),
          new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult result) {
              if (result.getResultCode() == RESULT_OK) {
                Log.d(TAG, "Confirmation dialog has been accepted.");
              } else if (result.getResultCode() == RESULT_CANCELED) {
                Log.d(TAG, "Confirmation dialog has been denied by the user.");
              }
            }
          });

    @Override
    public void onStateUpdate(AssetPackState assetPackState) {
      switch (assetPackState.status()) {
        case AssetPackStatus.PENDING:
          Log.i(TAG, "Pending");
          break;

        case AssetPackStatus.DOWNLOADING:
          long downloaded = assetPackState.bytesDownloaded();
          long totalSize = assetPackState.totalBytesToDownload();
          double percent = 100.0 * downloaded / totalSize;

          Log.i(TAG, "PercentDone=" + String.format("%.2f", percent));
          break;

        case AssetPackStatus.TRANSFERRING:
          // 100% downloaded and assets are being transferred.
          // Notify user to wait until transfer is complete.
          break;

        case AssetPackStatus.COMPLETED:
          // Asset pack is ready to use. Start the game.
          break;

        case AssetPackStatus.FAILED:
          // Request failed. Notify user.
          Log.e(TAG, assetPackState.errorCode());
          break;

        case AssetPackStatus.CANCELED:
          // Request canceled. Notify user.
          break;

        case AssetPackStatus.WAITING_FOR_WIFI:
        case AssetPackStatus.REQUIRES_USER_CONFIRMATION:
          if (!confirmationDialogShown) {
            assetPackManager.showConfirmationDialog(activityResultLauncher);
            confirmationDialogShown = true;
          }
          break;

        case AssetPackStatus.NOT_INSTALLED:
          // Asset pack is not downloaded yet.
          break;
        case AssetPackStatus.UNKNOWN:
          Log.wtf(TAG, "Asset pack status unknown")
          break;
      }
    }
}

또는 getPackStates() 메서드를 사용하여 현재 다운로드 상태를 가져올 수 있습니다. AssetPackStates에는 다운로드 진행률, 다운로드 상태 및 실패 오류 코드가 포함됩니다.

애셋 팩 액세스

다운로드 요청이 COMPLETED 상태에 도달하면 파일 시스템 호출을 사용하여 애셋 팩에 액세스할 수 있습니다. getPackLocation() 메서드를 사용하여 애셋 팩의 루트 폴더를 가져옵니다.

애셋은 애셋 팩 루트 디렉터리 내의 assets 디렉터리에 저장됩니다. 편의 메서드 assetsPath()를 사용하여 assets 디렉터리 경로를 가져올 수 있습니다. 다음 메서드를 사용하여 특정 애셋 경로를 가져올 수 있습니다.

Kotlin

private fun getAbsoluteAssetPath(assetPack: String, relativeAssetPath: String): String? {
    val assetPackPath: AssetPackLocation =
      assetPackManager.getPackLocation(assetPack)
      // asset pack is not ready
      ?: return null

    val assetsFolderPath = assetPackPath.assetsPath()
    // equivalent to: FilenameUtils.concat(assetPackPath.path(), "assets")
    return FilenameUtils.concat(assetsFolderPath, relativeAssetPath)
}

Java

private String getAbsoluteAssetPath(String assetPack, String relativeAssetPath) {
    AssetPackLocation assetPackPath = assetPackManager.getPackLocation(assetPack);

    if (assetPackPath == null) {
        // asset pack is not ready
        return null;
    }

    String assetsFolderPath = assetPackPath.assetsPath();
    // equivalent to: FilenameUtils.concat(assetPackPath.path(), "assets");
    String assetPath = FilenameUtils.concat(assetsFolderPath, relativeAssetPath);
    return assetPath;
}

기타 Play Asset Delivery API 메서드

다음은 앱에서 사용해 볼 수 있는 추가 API 메서드입니다.

요청 취소

cancel()를 사용하여 활성 상태의 애셋 팩 요청을 취소할 수 있습니다. 그러나 이 요청 작업의 결과는 보장되지 않습니다.

애셋 팩 삭제

requestRemovePack() 또는 removePack()을 사용하여 애셋 팩 삭제를 예약합니다.

여러 애셋 팩의 위치 가져오기

getPackLocations()를 사용하여 여러 애셋 팩의 상태를 일괄적으로 쿼리하면 애셋 팩 및 그 위치 맵이 반환됩니다. getPackLocations()에서 반환한 맵에는 현재 다운로드되고 최신 상태인 각 팩의 항목이 포함되어 있습니다.

다음 단계

로컬 및 Google Play에서 Play Asset Delivery를 테스트합니다.