本页内容

使用 Play Feature Delivery

展示了在 Qt 上使用 Google Play 功能分发的示例。

带“加载地图”、“切换地图”和“删除地图”按钮的地图加载器应用

本文档介绍了“功能交付”示例的功能。该示例使用了自 Qt 6.11 起支持的功能。
虽然在较旧的 Qt 版本上也可以使用 Play 功能分发,但在创建应用时需要手动添加 Android 项目并复制 Qt 二进制文件。相关操作说明请参见“Qt 6.11 之前的版本中使用功能分发”一章。

什么是功能交付?

Play Feature Delivery 是 Google 提供的一项功能,它本质上允许开发者以特定方式组织项目结构,使 Google Play 商店能够将应用内容拆分为多个可下载的包。该功能还使开发者能够控制内容向用户的分发方式。这些拆分后的软件和内容包通过 Android 应用包(AAB)分发至 Google Play 商店。Google 的开发者文档对此功能进行了详细说明。

示例项目:支持“功能分发”的地图加载器

这款简单的应用使用 Play 功能分发功能,在用户请求时向其提供图片。该应用可以轻松修改,以创建一个超过 200MB 的应用,用于测试应用大小限制,并通过大型功能分发模块进行下载。

该应用

该应用由一个可拖动视图和四个按钮组成。

  • 启动时,“Load Map ”和“Show Map Info ”按钮处于启用状态。
  • 点击“Show Map Info ”按钮时,仅显示当前无可用信息。
  • Load Map 将启动功能模块的加载:
    1. 此时会显示下载弹窗,用户可通过该弹窗取消下载。
    2. 下载完成后,弹窗将消失,
    3. 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 函数卸载已安装模块的选项。

本示例的设计旨在让开发者能够轻松添加自己的内容来测试“功能分发”功能。若需突破 Google Play 商店的最大包大小限制,可将地图图片(不限于地图图片,但符合本示例的主题)添加到fdmaploader 和fdwintermapmodule 中的 images 文件夹内,同时必须将图片名称添加到images.qrc 文件中。

幕后原理

该 API 模块由两部分组成:Qt 接口(PlayStoreLoader 和PlayStoreLoaderHandler )以及处理调用Android:Google Split Install 接口的 Java 类。Qt 接口主要作为 Java 类的传递层。 Qt 接口简化了 API,因此当功能模块被加载时,Google 的SplitCompat 和SplitInstall 类以及监听器会自动创建和释放。在此示例中,部分 API 被省略,例如deferredInstall 以及语言支持功能。
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 将特定动态库作为动态特性添加到 Android 应用程序目标中。这需要启用QT_ANDROID_GRADLE_MULTI_MODULE。可以通过编译时标志实现,也可以在 CMakeLists 中设置。 通过 `qt_add_android_dynamic_feature_java_source_dir` 将包含接口 Java 部分的文件夹添加到构建中。

在示例应用中,模块是通过 `storeloader ` 接口加载的。Qt XML 的 `PlayStoreLoader ` 函数和 `PlayStoreLoaderHandler ` 类在示例代码与 Java 之间起着中介作用。

voidPlayStoreLoader::loadModule(constQString&callId,
    constQString&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/

配置:

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 文件可通过在bundletool 中添加--local-testing 参数进行本地测试。Android:BundleTool 文档BundleTool 的build-apks 命令会生成 APK 文件,随后可通过install-apks 命令将其安装到设备或模拟器上

使用的 Bundletool 命令

从 Bundle 生成 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
提交至 Google Play 商店

为了将创建的 AAB 包上传到 Google Play 商店,必须对该包进行签名。为此可以使用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 之前的版本不支持生成与 Google Play 商店兼容的包。如果您无法使用 Qt 6.11 或更高版本,仍有一种方法可以使用 Google Play 功能分发,但这需要手动创建 Android 项目,并将 Qt 二进制文件复制到该项目中。 本文档的其余部分提供了实现此功能的操作指南。这些指南可能无法与所有环境完全兼容,但应能为成功实现“功能分发”提供有用的提示。建议以示例为基础进行操作。

功能模块

功能模块的构建方式与普通库相同。

  • 使用 `Qt Creator ` 创建 C++ 共享库。
  • 实现功能并添加资源。
  • 进行构建以生成 .so 二进制文件。

“功能交付”将 C++ 库视为普通共享库,这些库在运行时可能可用,也可能不可用。在调用库之前,必须检查其可用性。

主应用程序(Qt)
  • 使用Qt Creator 创建应用程序(此处使用了Qt Quick 项目模板)。
  • 实现对 Feature Delivery 库的调用。Google Play Feature Delivery Java 库的核心类是Android: SplitInstallManager。
  • 可通过 QtCreatorProjects -> Build&Run -> [target ABI] -> Build Steps -> Build Android APK 上的“创建模板”按钮生成 Android 模板文件。 模板将创建在项目的“android”文件夹中。
  • 将 Java 文件添加到.../android/src/java/[package...] 文件夹中,并将文件路径添加到CMakeLists.txt :
    qt_add_executable...
    ...[path]/[java-filename.java]
    ...
  • 在本示例中,创建了一个 Java 类来处理调用和回调。随后,Qt 将通过 JNI 访问该 Java 类。Android文档中的“请求按需模块”一节简要介绍了如何请求模块。
  • 在项目中的 android 文件夹下添加 Java 文件时,必须将QT_ANDROID_PACKAGE_SOURCE_DIR属性添加到CMakeLists.txt 中:
    ...
    set_property(TARGET appFDMainApp APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
                 ${CMAKE_CURRENT_SOURCE_DIR}/android)
    ...
  • 此外,主应用build.gradle 必须包含对功能 API 的依赖:在 dependencies 代码块中,将
    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)();
        // 检查 wintermap 库是否存在
        mWintermapLibrary.setFileName("fdwintermapmodule");
        if(!mWintermapLibrary.load()) {
            qWarning() << Q_FUNC_INFO << "Failed to load library";
           returnQString();
        }
        LoadMapInfoFunc loadMapInfo= (LoadMapInfoFunc) mWintermapLibrary.resolve("loadMapInfo");
        if(loadMapInfo) {
            void*result=loadMapInfo();
            resultStr.reset(static_cast<QString*>(result));
        }else
            qWarning() << Q_FUNC_INFO << "Function loadMapInfo not loaded";
    
       return *resultStr.data();
    }
  • 实现主应用的用户界面及其他必要部分。
功能模块(Qt)
  • 使用Qt Creator 创建一个应用程序(使用了 Qt C++ 库项目模板)。
  • 实现该模块提供的功能。
Android 项目 (Android)

根据 Android 文档(主要是以下内容)创建用于构建 Feature Delivery Android 应用包的项目:

手动创建 Android 项目,或使用 Android Studio(选用“无 Activity”模板)。对项目进行修改,使其包含一个顶级项目和两个子项目:app 和feature-module 。Android Studio 模板会自动创建app 子项目,而feature-module 子项目可通过“File -> New -> New Module ”模板添加。

该模板项目需要进行若干修改:

  • 在主级build.gradle 中添加Feature Delivery插件:
    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)
app - 子项目
  • Android 项目需要来自“Main App”项目的 Qt 二进制文件:
    • 将 Qt 构建目录([build directory]/android-build/libs/[target ABI] )中的原生库复制到app/src/main/jniLibs/[target ABI]
    • 将[build directory]/android-build/libs/ 中的 JAR 文件复制到app/libs/
  • 此外,还将从 Qt 构建中复制res 文件夹、AndroidManifest.xml 以及local.properties 中的内容到 Android 项目的相应位置。
  • 在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>
  • 在app/src/main/res/raw 文件夹中添加文件keep.xml ,内容如下:
    <?xml version="1.0" encoding="utf-8"?>
    <resources xmlns:tools="http://schemas.android.com/tools"
        tools:keep="@string/feature_module_winter_map"
        tools:discard="" />
对 app 子项目的构建文件的修改

复制到 Android 项目中的构建文件需要进行一些修改。

app - 子项目
build.gradle
  • 删除buildScript 和repositories 代码块。
  • 主应用中build.gradle 文件内的Android代码块需要进行一些修改:
    • 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 模块中添加签名配置:

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 已在 `gradle.properties` 中添加了项目变量。如有需要,请修改 `androidPackageName ` 的值。

AndroidManifest.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=" ...
        />
    ...
功能模块 - 子项目

应用模块和功能模块作为顶级 Android 项目的子项目创建。其文件夹和文件结构与应用子项目类似。

  • Qt 构建生成的功能模块二进制文件会被复制到[name-of-feature-module]/src/main/jniLibs/
  • 与主应用类似,src/main/res/ 文件夹中应包含xml 和values 文件夹,其中分别存放qtprovider_paths.xml 和libs.xml 。这两个文件均可从应用项目中复制过来。
  • 如果src/main/res/ 文件夹中包含 drawable 或 mipmap 文件夹,而该功能并不需要它们,则可以将其删除。
  • 在功能模块中,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 {
            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/']
           }
        }
    
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_17
            targetCompatibility JavaVersion.VERSION_17
        }
    
        // Do not compress Qt binary resources file
        aaptOptions {
            noCompress 'rcc'
        }
    }
  • gradle.properties 该文件可从 app 子项目中复制过来,只需将androidPackageName 替换为功能模块的包名即可。
构建与部署

可通过 Gradle 包装器在命令行中构建 AAB 包:./gradlew bundle。生成的 AAB 包将位于build/outputs/bundle/release (或debug )文件夹中。随后可将该 AAB 包复制到 Google Play 商店并发布以供测试。也可通过在bundletool 中添加--local-testing 参数,在本地进行测试。

示例项目 @ code.qt.io

© 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.