플레이 기능 제공 사용
기능 전달이란 무엇인가요?
Google 개발자 문서에는 이 기능이 자세히 설명되어 있으며, 기본적으로 개발자는 Google Play 스토어에서 앱 콘텐츠를 여러 개의 다운로드 가능한 패키지로 분할할 수 있는 방식으로 프로젝트를 구성할 수 있습니다. 또한 개발자는 콘텐츠가 사용자에게 전달되는 방식을 제어할 수 있습니다. 이렇게 분할된 소프트웨어 및 콘텐츠 패키지는 Android 앱 번들(AAB)을 사용하여 Google Play 스토어에 전달됩니다.
예제 프로젝트: FDMapLoader
이 앱은 플레이 기능 전달을 사용하여 사용자가 요청할 때 이미지를 제공합니다. 이 앱을 쉽게 수정하여 앱 크기 제한을 테스트하고 대용량 기능 전달 모듈을 사용하여 다운로드할 수 있도록 200MB가 넘는 앱을 만들 수 있습니다.
빌드 설정
- fdwintermapmodule: 피처 모듈 프로젝트 Qt Creator
- fdmaploader: 메인 앱 프로젝트 Qt Creator
- fdmaploader-android-project: 안드로이드 앱 번들 컴파일을 위한 안드로이드 프로젝트.
FDWintermapModule 컴파일 - 기능 모듈
fdwintermapmodule 프로젝트를 Qt Creator 에 로드하고 대상 플랫폼에 맞게 구성한 후 빌드합니다.
FDMapLoader 컴파일 - 메인 앱
fdmaploader를 Qt Creator 에 로드하고 대상 플랫폼에 맞게 구성한 후 빌드합니다.
안드로이드 프로젝트 수정
- 빌드된 바이너리(.so) 파일을 기능 모듈 및 메인 앱 빌드 디렉터리에서 Android 프로젝트의 해당 라이브러리 디렉터리로 복사합니다. (각각
app/src/main/jniLibs/[target ABI]/
및fdwintermapmodule/src/main/jniLibs/[target ABI]/
) - 리소스 파일 및 Java 클래스 등 기타 변경된 파일을 복사합니다. (
.../res/...
및.../src/main/java/...
폴더) - 안드로이드 스튜디오를 사용하거나 Gradle 래퍼(
./gradlew
번들)를 사용하여 명령줄에서 안드로이드 프로젝트를 빌드합니다. - 테스트를 위해 번들에서 APK를 생성하고
bundletool
를 사용하여 로컬 테스트 버전을 기기에 설치할 수 있습니다. 번들 도구는 Google에서 제공하는 도구이며 이 문서의 뒷부분에서 사용법을 다룰 것입니다. 테스트 및 릴리스를 위해 번들을 Google Play 콘솔에 업로드할 수 있습니다.
나만의 콘텐츠 추가
이 예제는 개발자가 기능 전달을 테스트하기 위해 자체 콘텐츠를 쉽게 추가할 수 있도록 설계되었습니다. Play 스토어 최대 패키지 크기를 초과하려면 지도 이미지(지도 이미지일 필요는 없지만 예제의 테마에 맞으면 됩니다.)를 FDMapLoader 및 FDWintermapModule의 이미지 폴더에 추가할 수 있으며, 이미지 이름도 images.qrc
파일에 추가해야 합니다.
나만의 이미지 만들기
다음 섹션에서는 플레이 기능 전달을 사용하는 프로젝트를 직접 만드는 방법을 설명합니다. 예제를 기초로 사용하는 것이 좋습니다. 이 프로젝트에서는 Qt 6.7.2가 사용되었습니다.
기능 모듈
기능 전달은 런타임에 사용할 수 있거나 사용할 수 없는 일반 공유 라이브러리처럼 C++ 라이브러리를 처리합니다. 라이브러리를 호출하기 전에 라이브러리의 사용 가능 여부를 확인해야 합니다.
- Qt Creator 을 사용하여 C++ 공유 라이브러리를 생성하세요.
- 기능을 구현하고 리소스를 추가합니다.
- 빌드하여 .so 바이너리를 만듭니다.
메인 앱(Qt)
- Qt Creator 을 사용하여 앱을 만듭니다(여기서는Qt Quick 프로젝트 템플릿이 사용됨).
- 기능 전달 라이브러리에 대한 액세스를 구현합니다. 구글플레이 피처 딜리버리 자바 라이브러리의 중심 클래스는
SplitInstallManager
. - 안드로이드 템플릿 파일은 QtCreator Projects -> Build&Run -> [target ABI] -> Build Steps -> Build Android APK 에서 '템플릿 생성' 버튼을 사용하여 생성할 수 있습니다. 템플릿은 프로젝트의 'android' 폴더에 생성됩니다.
- 폴더에 Java 파일을
.../android/src/java/[package...]
에 추가하고 파일 경로를CMakeLists.txt
에 추가합니다:qt_add_executable... ...[path]/[java-filename.java] ...
- 이 예제에서는 호출과 콜백을 처리하기 위해 Java 클래스를 만들었습니다. 그런 다음 JNI를 사용하여 Qt에서 Java 클래스에 액세스합니다. Android 문서에는 모듈을 요청하는 방법에 대한 간단한 설명이 있습니다.
- 프로젝트의 android 폴더 아래에 Java 파일을 추가할 때는
CMakeLists.txt
에 QT_ANDROID_PACKAGE_SOURCE_DIR 속성을 추가해야 합니다:... set_property(TARGET appFDMainApp APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android) ...
- 또한 메인 앱
build.gradle
에 기능 API에 대한 종속성이 있어야 합니다. 종속성 블록에서 다음을 대체합니다.implementation 'androidx.core:core:1.13.1'
를
implementation("com.google.android.play:feature-delivery:2.1.0")
- 기능 모듈에서 제공하는 라이브러리에 대한 액세스를 구현합니다. 기능 모듈은 기본 앱에서 사용할 수 있거나 사용할 수 없으므로 모듈은 빌드 시점에 연결되지 않으며 모듈에 대한 호출은 런타임에 해결해야 합니다. 예시:
typedef void* (*LoadModuleRulesFunc)(); LoadModuleRulesFunc loadModuleRules = (LoadModuleRulesFunc) mFDModuleLibrary.resolve("loadModuleRules"); if (loadModuleRules) { void* result = loadModuleRules(); QScopedPointer<QString> resultStr{static_cast<QString*>(result)}; }
- 기본 앱의 사용자 인터페이스 및 기타 필수 부분을 구현합니다.
기능 모듈(Qt)
- Qt Creator 을 사용하여 앱을 만듭니다(Qt C++ 라이브러리 프로젝트 템플릿이 사용됨).
- 모듈이 제공하는 기능을 구현합니다.
안드로이드 프로젝트(안드로이드)
기능 제공을 위한 안드로이드 앱 번들을 빌드하기 위한 프로젝트 생성은 주로 안드로이드의 문서를 기반으로 합니다:
수동으로 또는 안드로이드 스튜디오를 사용하여 안드로이드 프로젝트를 만듭니다('활동 없음' 템플릿 사용). 프로젝트는 최상위 프로젝트와 두 개의 하위 프로젝트( app
및 feature-module
)를 포함하도록 수정됩니다. Android Studio 템플릿은 app
하위 프로젝트를 생성하고 feature-module
은 File -> New -> New Module 템플릿을 사용하여 추가할 수 있습니다.
템플릿 프로젝트는 몇 가지 수정이 필요합니다:
- 메인 레벨
build.gradle
에 기능 전달 플러그인을 추가합니다:plugins { id 'com.android.application' version '8.5.2' apply false id 'com.android.dynamic-feature' version '8.5.2' apply false id 'com.android.library' version '8.5.2' apply false }
settings.gradle
에 기능 모듈을 추가하고 필요한 경우rootProject.name
을 변경합니다:... rootProject.name = "name-of-the-root-project" include(:app) include(:name-of-the-feature-module)
앱 - 하위 프로젝트
- 안드로이드 프로젝트에는 메인 앱 프로젝트의 Qt 바이너리가 필요합니다:
- Qt 빌드에서 네이티브 라이브러리 복사:
[build directory]/android-build/libs/[target ABI]
에서app/src/main/jniLibs/[target ABI]
[build directory]/android-build/libs/
에서 jars를 복사합니다.app/libs/
- Qt 빌드에서 네이티브 라이브러리 복사:
- Qt 빌드에서
res
폴더,AndroidManifest.xml
,local.properties
의 내용도 안드로이드 프로젝트의 각 위치로 복사합니다. app/src/main/res/values
폴더에 기능 모듈에 대한 문자열이 포함된feature_names.xml
파일을 추가합니다:<?xml version="1.0" encoding="utf-8"?> <resources> <string name="feature_module_name">name-of-the-feature-module-here</string> </resources>
keep.xml
파일을app/src/main/res/raw
폴더에 추가합니다:<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@string/feature_module_winter_map" tools:discard="" />
앱 하위 프로젝트 빌드 파일 수정
Android 프로젝트에 복사한 빌드 파일은 약간의 수정이 필요합니다.
앱 - 하위 프로젝트
build.gradle
buildScript
및repositories
블록을 제거합니다.- 메인 앱의
build.gradle
에 있는 안드로이드 블록은 일부 수정이 필요합니다:defaultConfig
packagingOptions
dynamicFeatures
sourceSets
aaptOptions
dependencies
android { ... defaultConfig { ... applicationId "your-project-name-here" ... } packagingOptions.jniLibs.useLegacyPackaging true dynamicFeatures = [":your-dynamic-feature-name-here"] sourceSets { main { manifest.srcFile 'src/main/AndroidManifest.xml' java.srcDirs = [qtAndroidDir + '/src', 'src', 'java'] aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl'] res.srcDirs = [qtAndroidDir + '/res', 'res'] resources.srcDirs = ['resources'] renderscript.srcDirs = ['src'] assets.srcDirs = ['assets'] jniLibs.srcDirs = ['src/main/jniLibs/'] } } // Do not compress Qt binary resources file aaptOptions { noCompress 'rcc' } ... } dependencies { ... implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) implementation 'com.google.android.play:feature-delivery:2.1.0' implementation libs.material ... }
또한 안드로이드 블록에 서명 구성을 추가합니다:
android { ... signingConfigs { release { store \code android { ... signingConfigs { release { storeFile file("/absolute/path/to/the/keystore.jks") storePassword "myStorePassword" keyAlias "myKeyAlias" keyPassword "myKeyPassword" } } buildTypes { release { signingConfig signingConfigs.release ... } } ... }
gradle.properties
Qt XML에 프로젝트 변수를 gradle.properties
에 추가했습니다. 필요한 경우 androidPackageName
의 값을 변경합니다.
안드로이드 매니페스트.xml
package
을 제거합니다:... <manifest ... android:package... <--remove ... > ...
- 필요한 경우
label
및android.app.lib_name
을 변경합니다:... <application ... android:label=" ... <activity ... > <meta-data android:name="android.app.lib_name" android:value=" ... /> ...
기능 모듈 - 하위 프로젝트
앱 및 기능 모듈은 최상위 안드로이드 프로젝트의 하위 프로젝트로 만들어집니다. 폴더 및 파일 구조는 앱 하위 프로젝트와 유사합니다.
- Qt 빌드의 기능 모듈 바이너리는 메인 앱과 마찬가지로
[name-of-feature-module]/src/main/jniLibs/
- 메인 앱에서와 마찬가지로
src/main/res/
폴더에는xml
및values
폴더에 각각qtprovider_paths.xml
및libs.xml
파일이 포함되어 있어야 합니다. 두 파일 모두 앱 프로젝트에서 복사할 수 있습니다. src/main/res/
폴더에 드로어블 또는 밉맵 폴더가 포함되어 있고 기능에 필요하지 않은 경우 해당 폴더를 제거할 수 있습니다.- 기능 모듈에서
src/main/res/values
에는app_name
필드가 포함되어서는 안 됩니다. 다른 용도로 사용하지 않는 간단한 프로젝트에서는 strings.xml을 제거할 수 있습니다. libs.xml
에는 기능 모듈의 이름만 포함됩니다:... <array name="load_local_libs"> <item>name-of-the-feature-module-here</item> </array> <string name="static_init_classes"></string> <string name="use_local_qt_libs">0</string> <string name="bundle_local_qt_libs">0</string> ...
AndroidManifest.xml
는src/main/
디렉터리에 추가됩니다:<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:dist="http://schemas.android.com/apk/distribution"> <dist:module dist:instant="false" dist:title="@string/feature_module_title_string"> <dist:delivery> <dist:on-demand /> </dist:delivery> <dist:fusing dist:include="false" /> </dist:module> <!-- This feature module does contain code. --> <application android:hasCode="true"/> </manifest>
- 기능 모듈
build.gradle
은 앱 프로젝트의 것과 매우 유사하지만 몇 가지 변경 사항이 있습니다. 다음은 예제 프로젝트의 예시입니다:plugins { id 'com.android.dynamic-feature' } dependencies { implementation project(':app') implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) implementation 'com.google.android.play:feature-delivery:2.1.0' } android { namespace = androidPackageName compileSdk = androidCompileSdkVersion ndkVersion androidNdkVersion // Extract native libraries from the APK packagingOptions.jniLibs.useLegacyPackaging true defaultConfig { resConfig "en" minSdkVersion qtMinSdkVersion targetSdkVersion qtTargetSdkVersion } sourceSets { main { manifest.srcFile 'src/main/AndroidManifest.xml' resources.srcDirs = ['resources'] renderscript.srcDirs = ['src'] assets.srcDirs = ['assets'] jniLibs.srcDirs = ['src/main/jniLibs/'] } } tasks.withType(JavaCompile) { options.incremental = true } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } lintOptions { abortOnError false } // Do not compress Qt binary resources file aaptOptions { noCompress 'rcc' } }
gradle.properties
파일을 앱 하위 프로젝트에서 복사하여androidPackageName
을 기능 모듈 패키지로 변경할 수 있습니다.
빌드 및 배포
그레이들 래퍼를 사용하여 명령줄에서 AAB 번들을 빌드할 수 있습니다: ./gradlew
번들 생성된 AAB는 build/outputs/bundle/release
(또는 debug
) 폴더에 있습니다. 그런 다음 AAB를 Google Play 스토어에 복사하여 테스트를 위해 배포할 수 있습니다. --local-testing
매개변수와 함께 bundletool
을 사용하여 로컬에서 테스트할 수도 있습니다. 번들 도구 문서
사용되는 번들 도구 명령
- 번들에서 APK:를 생성합니다:
bundletool build-apks --bundle=/path/to/bundle.aab --output=/path/to/apk/package.apks --local-testing
- 디바이스에 앱을 설치합니다:
bundletool install-apks --apks=/path/to/apk/package.apks
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.