플레이 기능 전달 사용
Qt에서 구글 플레이 피처 딜리버리를 사용하는 방법을 보여줍니다.

이 문서에서는 기능 전달 예제의 기능을 설명합니다. 이 예제에서는 Qt 6.11부터 지원되는 기능을 사용합니다.
이전 Qt 버전에서도 Play 기능 전달을 사용할 수 있지만 애플리케이션을 생성하려면 안드로이드 프로젝트를 수동으로 추가하고 Qt 바이너리를 복사해야 합니다. 이에 대한 지침은 Qt 6.11 이전 버전의 기능 전달 장에서 확인할 수 있습니다.
기능 전달이란 무엇인가요?
Play 기능 제공은 기본적으로 개발자가 구글 플레이 스토어에서 앱 콘텐츠를 여러 개의 다운로드 가능한 패키지로 분할하는 방식으로 프로젝트를 구성할 수 있도록 하는 구글에서 제공하는 기능입니다. 또한 개발자는 콘텐츠가 사용자에게 전달되는 방식을 제어할 수 있습니다. 이렇게 분할된 소프트웨어 및 콘텐츠 패키지는 Android 앱 번들(AAB)을 사용하여 Google Play 스토어에 전달됩니다. 이 기능은 Google 개발자 문서에 자세히 설명되어 있습니다.
예제 프로젝트: 기능 전달 맵 로더
이 간단한 앱은 Play 피처 딜리버리를 사용하여 사용자가 요청할 때 이미지를 제공합니다. 이 앱을 쉽게 수정하여 200MB가 넘는 앱을 만들어 앱 크기 제한을 테스트하고 대용량 피처 딜리버리 모듈을 사용하여 다운로드할 수 있습니다.
앱
이 앱은 드래그 가능한 보기와 4개의 버튼으로 구성되어 있습니다.
- 시작 시 Load Map 및 Show Map Info 버튼이 활성화됩니다.
- Show Map Info 버튼을 클릭하면 사용 가능한 정보가 없음을 표시합니다.
- Load Map 기능 모듈의 로딩을 시작합니다:
- 다운로드 팝업이 표시되며, 이 팝업에서 다운로드를 취소할 수 있습니다.
- 다운로드가 완료되면 팝업이 제거되고
- Change Map 및 Remove Map 버튼이 활성화됩니다.
- Remove Map 버튼은 기능 모듈을 제거하도록 요청하여 활성화된 버튼을 비활성화합니다.
- Change Map 을 클릭하면 표시된 지도를 변경할 수 있는 보기가 열립니다. 이 예제에서 기능 모듈은 겨울 테마의 지도 이미지 하나로만 구성되어 있습니다.
소스 폴더 설정
- fdwintermapmodule: 피처 모듈
- fdmaploader: 메인 앱
- fdmaploader/storeloader: 기능 전달 JNI 인터페이스
기능 전달 인터페이스
fdmaploader/storeloader 폴더에는 기능 전달 API를 위한 인터페이스 클래스 PlayStoreLoader 가 포함되어 있습니다. 이 API는 완전하지는 않지만 기능 모듈을 로드하고 제거하기 위한 관련 함수가 포함되어 있습니다. 모듈 로드는 PlayStoreLoader::loadModule 호출로 시작됩니다. 프로세스 상태는 PlayStoreLoaderHandler 에서 제공하는 신호로 모니터링할 수 있습니다. 콜백에 대한 핸들은 PlayStoreLoader::getHandler 함수를 사용하여 얻을 수 있습니다. 예제 API는 PlayStoreLoader::getInstalledModules 함수를 사용하여 이미 설치된 모듈을 확인하는 방법과 PlayStoreLoader::uninstallModules 함수를 사용하여 설치된 모듈을 제거하는 옵션도 제공합니다.
이 예제는 개발자가 기능 전달을 테스트하기 위해 자체 콘텐츠를 쉽게 추가할 수 있도록 설계되었습니다. Play 스토어 최대 패키지 크기를 초과하려면 fdmaploader 및 fdwintermapmodule 의 이미지 폴더에 지도 이미지(지도 이미지일 필요는 없지만 예제의 주제에 맞으면 됩니다.)를 추가할 수 있으며, 이미지 이름도 images.qrc 파일에 추가해야 합니다.
내부 정보
API 모듈은 두 부분으로 구성됩니다. Qt 인터페이스(PlayStoreLoader 및 PlayStoreLoaderHandler)와 안드로이드 호출을 처리하는 자바 클래스 : 구글 분할 설치 인터페이스. Qt 인터페이스는 대부분 자바 클래스를 위한 패스스루로 작동합니다. Qt 인터페이스는 API를 단순화하여 기능 모듈이 로드될 때 Google SplitCompat 및 SplitInstall 클래스와 리스너가 자동으로 생성 및 릴리스되도록 합니다. 이 예제에서는 deferredInstall 및 언어 지원과 같은 API의 일부가 생략되어 있습니다.
Qt creates and builds package suitable for Google Play deployment using qt6_add_android_dynamic_features when it is defined CMakeLists.
qt6_add_android_dynamic_features(${target_name}
FEATURE_TARGETS fdwintermapmodule)CMake 함수 qt6_add_android_dynamic_features 는 특정 동적 라이브러리를 안드로이드 애플리케이션 타겟의 동적 기능으로 추가합니다. 이를 위해서는 QT_USE_ANDROID_MODERN_BUNDLE 기능이 활성화되어 있어야 합니다. 이는 컴파일 시 플래그로 설정하거나 CMakeLists에서 설정할 수 있습니다. 인터페이스의 Java 부분이 있는 폴더는 qt_add_android_dynamic_feature_java_source_dir 을 사용하여 빌드에 추가합니다.
예제 앱 모듈은 storeloader 인터페이스를 사용하여 로드됩니다. Qt PlayStoreLoader 함수와 PlayStoreLoaderHandler 클래스는 예제 코드와 Java 사이의 중간 역할을 합니다.
void PlayStoreLoader::loadModule(const QString & callId, const QString &moduleName) { if (callId.isEmpty() || moduleName.isEmpty()) return; if (!loaderInstance->registerNatives()) return; if (!loaderInstance->loader().isValid()) { qCritical("StoreLoader not constructed"); return; } loaderInstance->loader().callMethod<void>("installModuleFromStore", moduleName, callId); }
Java 클래스는 Split Install API에 대한 호출을 처리합니다.
m_splitInstallManager.startInstall(request)
.addOnSuccessListener(sessionId -> {
PlayStoreLoaderListener listener = m_listeners.get(callId);
if (listener != null)
listener.setSessionId(sessionId);
})바이너리 생성
명령줄을 사용하여 로컬에서 테스트 가능한 AAB 패키지를 빌드할 수 있습니다.
소스 디렉터리와 같은 레벨에 빌드 디렉터리를 생성하고 입력합니다:
mkdir build-feature-delivery/ ; cd build-feature-delivery/
Configure:
path-to-qt-version/path-to-abi/bin/qt-cmake -GNinja -B . -S ../feature-delivery/ -DQT_USE_TARGET_ANDROID_BUILD_DIR=ON -DCMAKE_BUILD_TYPE=Debug
빌드:
ninja aab
빌드: 테스트
빌드된 AAB는 --local-testing 매개변수와 함께 bundletool 을 사용하여 로컬에서 테스트할 수 있습니다. 안드로이드: 번들 도구 문서 b undletool build-apks 명령은 apks 파일을 생성한 다음 install-apks 명령으로 디바이스 또는 에뮬레이터에 설치할 수 있습니다.
사용되는 번들 도구 명령
번들에서 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
Play 스토어에 배포
생성한 AAB 패키지를 구글 플레이 스토어에 업로드하려면 패키지에 서명을 해야 합니다. 이를 위해 jarsigner 을 사용할 수 있습니다. 다음은 AAB 패키지에 서명하는 jarsigner 명령의 예입니다. Android를 참조하세요 : Jarsigner 문서
jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 -keystore [path-to-keystore-file].keystore [path-to-aab-file].aab [alias]
Qt 6.11 이전 버전에서 기능 제공
Qt 6.11 이전 버전은 구글 플레이 스토어 호환 패키지 생성을 지원하지 않습니다. Qt 버전 6.11 이상을 사용할 수 없는 경우 Google Play 기능 전달을 사용하는 방법이 있지만 Android 프로젝트를 수동으로 생성하고 Qt 바이너리를 여기에 복사해야 합니다. 이 문서의 나머지 부분에는 이를 수행하는 방법에 대한 지침이 있습니다. 이 지침이 모든 환경에 일대일로 호환되는 것은 아니지만 성공적인 기능 전달 구현을 위한 좋은 힌트를 제공할 것입니다. 이 예제를 기초로 사용하는 것이 좋습니다.
기능 모듈
기능 모듈은 일반 라이브러리처럼 빌드됩니다.
- Qt Creator 을 사용하여 C++ 공유 라이브러리를 만듭니다.
- 기능을 구현하고 리소스를 추가합니다.
- 빌드하여 .so 바이너리를 만듭니다.
기능 전달은 런타임에 사용할 수 있거나 사용할 수 없는 일반 공유 라이브러리처럼 C++ 라이브러리를 처리합니다. 라이브러리를 호출하기 전에 라이브러리의 사용 가능 여부를 확인해야 합니다.
기본 앱(Qt)
- Qt Creator 을 사용하여 앱을 만듭니다(여기서는Qt Quick 프로젝트 템플릿이 사용됨).
- Feature Delivery 라이브러리에 대한 액세스를 구현합니다. 구글플레이 피처 딜리버리 자바 라이브러리의 중심 클래스는 Android: SplitInstallManager입니다.
- 안드로이드 템플릿 파일은 QtCreator Projects -> Build&Run -> [target ABI] -> Build Steps -> Build Android APK 에서 '템플릿 만들기' 버튼을 사용하여 만들 수 있습니다. 템플릿은 프로젝트의 'android' 폴더에 생성됩니다.
-
.../android/src/java/[package...]폴더에 Java 파일을 추가하고 파일 경로는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") 으로
바꾸고 기능 모듈이 제공하는 라이브러리에 대한 액세스를 구현합니다. 기본 앱에서 기능 모듈을 사용할 수 있거나 사용할 수 없으므로 모듈은 빌드 시점에 연결되지 않으며 모듈에 대한 호출은 런타임에 해결해야 합니다. 예시:- 메인 앱의 사용자 인터페이스 및 기타 필수 부분을 구현합니다.
QString MapLoader::loadMapInfo() { QScopedPointer<QString> resultStr; typedef void*(*LoadMapInfoFunc)(); //Find if wintermap library existsmWintermapLibrary.setFileName("fdwintermapmodule"); if (!mWintermapLibrary.load()) { qWarning() << Q_FUNC_INFO << "Failed to load library"; 반환 QString(); } LoadMapInfoFunc loadMapInfo = (LoadMapInfoFunc) mWintermapLibrary.resolve("loadMapInfo"); if (loadMapInfo) { void* result = loadMapInfo(); resultStr.reset(static_cast<QString*>(결과)); } else qWarning() << Q_FUNC_INFO << "Function loadMapInfo not loaded"; return *resultStr.data(); }
기능 모듈(Qt)
- Qt Creator 을 사용하여 앱을 만듭니다(Qt C++ 라이브러리 프로젝트 템플릿이 사용됨).
- 모듈이 제공하는 기능을 구현합니다.
안드로이드 프로젝트(안드로이드)
기능 전달을 위한 안드로이드 앱 번들을 빌드하기 위한 프로젝트 생성은 주로 안드로이드 문서를 기반으로 합니다:
수동으로 또는 안드로이드 스튜디오를 사용하여 안드로이드 프로젝트를 만듭니다('활동 없음' 템플릿 사용). 프로젝트는 최상위 프로젝트와 두 개의 하위 프로젝트( app 및 feature-module)를 포함하도록 수정됩니다. 안드로이드 스튜디오 템플릿은 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 프로젝트에 복사한 빌드 파일은 약간의 수정이 필요합니다.
앱 - 하위 프로젝트
buildScript및repositories블록을 제거합니다.- 메인 앱의
build.gradle에 있는 안드로이드 블록을 일부 수정해야 합니다:defaultConfigpackagingOptionsdynamicFeaturessourceSetsaaptOptionsdependencies
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 {
storeFile file("/absolute/path/to/the/keystore.jks")
storePassword "myStorePassword"
keyAlias "myKeyAlias"
keyPassword "myKeyPassword"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
...
}
}
...
}Qt XML에 프로젝트 변수를 gradle.properties 에 추가했습니다. 필요한 경우 androidPackageName 의 값을 변경하세요.
package을 제거합니다:... <manifest ... android:package... <--remove ... > ...
- 필요한 경우
label및android.app.lib_name을 변경합니다:... <application ... android:label=" ... <activity ... > <meta-data android:name="android.app.lib_name" android:value=" ... /> ...
기능 모듈 - 하위 프로젝트
앱 및 기능 모듈은 최상위 Android 프로젝트의 하위 프로젝트로 생성됩니다. 폴더 및 파일 구조는 앱 하위 프로젝트와 유사합니다.
- 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 을 사용하여 로컬에서 테스트할 수도 있습니다.
© 2026 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.