Google's developer documentation details this feature which essentially allows developers to structure their projects in a way that the Google Play store can split their app content into several downloadable packages. It also gives developers control over how the content is delivered to users. These split sofware and content packages are delivered to the Google Play store using Android App Bundles (AAB).
This app uses play feature delivery to serve images to a user when requested. The app can be easily modified to create an app that is over 200MB to test the app size limits and download using a large Feature Delivery module.
加载 fdwintermapmodule project to Qt Creator, configure it for your target platform, and build.
加载 fdmaploader to Qt Creator, configure it for your target platform, and build.
app/src/main/jniLibs/[target ABI]/
and
fdwintermapmodule/src/main/jniLibs/[target ABI]/
respectively)
.../res/...
and
.../src/main/java/...
folders)
./gradlew
bundle)
bundletool
. Bundletool is a tool provided by Google and we'll cover using it later in this documentation. For testing and releasing, you can upload the bundle to the Google Play Console.
This example is designed so that developer can easily add their own content to test the Feature Delivery. To exceed the Play Store maximum package size, map images (It does not need to be map images, but it fits the theme of the example.) can be added to the images folder in FDMapLoader and FDWintermapModule, the image names must alse be added to the
images.qrc
文件。
The following sections describe how to create your own project that uses play feature delivery. It is advised to use the example as a basis. In the project, Qt 6.7.2 was used.
Feature delivery handles C++ libraries like normal shared libraries that may or may not be available at runtime. Before calls to a library, the availability of one must be checked.
SplitInstallManager
.
.../android/src/java/[package...]
and file paths to
CMakeLists.txt
:
qt_add_executable... . . . [ path ] / [ java - filename . java ] . . .
CMakeLists.txt
:
... set_property(TARGET appFDMainApp APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} / android) . . .
build.gradle
must have dependencies for the feature API: in the dependencies block, replace
implementation 'androidx.core:core:1.13.1'
with
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)}; }
Creating a project for building an Android app bundle for feature delivery is based on Android's documentation, mainly:
Create an Android project manually or using Android Studio (Using the 'No Activity' template). The project is modified so that it contains a top-level project and two sub-projects,
app
and
feature-module
. The Android Studio template creates the
app
sub-project and the
feature-module
can be added using
File -> New -> New Module
template.
The template project requires several modifications:
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
, change
rootProject.name
if needed:
... rootProject . 名称 = "name-of-the-root-project" 包括 (:app) 包括 (:name - of - the - feature - 模块)
[build directory]/android-build/libs/[target ABI]
to
app/src/main/jniLibs/[target ABI]
[build directory]/android-build/libs/
to
app/libs/
res
folder,
AndroidManifest.xml
,和
local.properties
are copied to respective places in the Android project.
feature_names.xml
到
app/src/main/res/values
folder containing a string for the feature module:
<?xml version="1.0" encoding="utf-8"?> < resources > < string name = "feature_module_name" > 名称 - of - the - feature - 模块 - here < / string > < / resources >
keep.xml
to
app/src/main/res/raw
folder containing:
<?xml version="1.0" encoding="utf-8"?> < resources xmlns:tools = "http://schemas.android.com/tools" tools:keep = "@string/feature_module_winter_map" tools:discard = "" / >
Build files copied to the Android project need some modifications.
buildScript
and
repositories
blocks.
build.gradle
requires some modifications:
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
...
}
Also add signing configuration to android block:
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
...
}
}
...
}
Qt has added project variables to
gradle.properties
. Change the value of
androidPackageName
if needed.
package
:
... < manifest . . . android:package . . . < - - remove . . . > . . .
label
and
android.app.lib_name
if needed:
... < application . . . android:label = " ... <activity ... > <meta-data android:name=" android . app . lib_name " android:value=" . . . / > . . .
App and feature modules are created as sub-projects for the top-level Android project. The folder and file structure is similar to the app sub-project.
[name-of-feature-module]/src/main/jniLibs/
src/main/res/
folder should have
xml
and
values
folders containing
qtprovider_paths.xml
and
libs.xml
respectively. Both files can be copied from the app project.
src/main/res/
folder contains drawable or mipmap folders and the feature does not require them, they can be removed.
src/main/res/values
should not contain
app_name
field. In simple projects where strings.xml is not needed for other uses, it can be removed.
libs.xml
contains only the name of the feature module:
... < array name = "load_local_libs" > < item > 名称 - of - the - feature - 模块 - 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
is added to the
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: 包括 = "false" / > < / dist:module > < ! - - This feature module does contain code . - - > < application android:hasCode = "true" / > < / manifest >
build.gradle
is quite similar to the one from the app project, with some changes. Here's an example from the example project:
plugins {
id
'com.android.dynamic-feature'
}
dependencies {
implementation project(
':app'
)
implementation fileTree(dir:
'libs'
,
包括
:
[
'*.jar'
,
'*.aar'
]
)
实现
'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
file can be copied over from the app sub-project, change the
androidPackageName
to the feature module package.
AAB bundle can be built from the command line using gradle wrapper:
./gradlew
bundle The produced AAB will be in
build/outputs/bundle/release
(或
debug
) folder. The AAB can then be copied to the Google Play Store and released for testing. Testing can also be done locally by using
bundletool
with
--local-testing
参数。
Bundletool documentation
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