在本页

使用播放功能交付

展示在 Qt 上使用 Google Play 功能交付的情况。

本文档介绍了功能交付示例的功能。该示例使用 Qt 6.11 开始支持的功能。
Play Feature Delivery 可用于较旧的 Qt 版本,但创建应用程序需要手动添加 Android 项目并复制 Qt 二进制文件。有关说明请参阅 "Qt 6.11 之前的功能交付"一章。

什么是功能交付?

Play Feature Delivery 是 Google 提供的一种功能,主要是允许开发人员以 Google Play 商店可以将应用程序内容分割成多个可下载包的方式来构建项目。它还能让开发者控制向用户交付内容的方式。这些拆分的软件和内容包通过 Android 应用捆绑包(AAB)传送到 Google Play 商店。谷歌的开发者文档详细介绍了这一功能。

示例项目:功能交付地图加载器

这个简单的应用程序使用 Play Feature Delivery 在用户请求时为其提供图片。该应用程序可以轻松修改,创建一个超过 200MB 的应用程序,以测试应用程序大小限制和使用大型功能交付模块下载。

应用程序

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

  • 启动时,Load MapShow Map Info 按钮会启用。
  • 点击Show Map Info 按钮时,只显示没有可用信息。
  • Load Map 开始加载功能模块:
    1. 弹出下载窗口,可以取消下载。
    2. 下载完成后,弹出窗口将被移除,并启用
    3. Change MapRemove 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 Store 最大软件包大小,可在fdmaploaderfdwintermapmodule 的图片文件夹中添加地图图片(不一定是地图图片,但要符合示例的主题),图片名称也必须添加到images.qrc 文件中。

引擎盖下

API 模块由两部分组成。Qt XML 接口(PlayStoreLoaderPlayStoreLoaderHandler )和处理调用Android 的 java 类:Google 拆分安装接口。Qt 界面主要作为 java 类的直通。Qt 接口简化了应用程序接口,因此在加载功能模块时,GoogleSplitCompatSplitInstall 类和监听器会自动创建和释放。在本示例中,部分应用程序接口被省略,如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_USE_ANDROID_MODERN_BUNDLE 特性。这可以作为编译时标志或在 CMakeLists 中设置。界面 Java 部分所在的文件夹会通过qt_add_android_dynamic_feature_java_source_dir 添加到构建中。

在示例应用程序中,使用storeloader 接口加载模块。Qt XMLPlayStoreLoader 函数和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
测试

可使用bundletool--local-testing 参数在本地测试已构建的 AAB。Android:Bundletool 文档bundletoolbuild-apks 命令创建 apks 文件,然后可使用install-apks 命令将其安装到设备或模拟器上。

使用的 Bundletool 命令

从捆绑包生成 APK:s:

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 Store

为了能将创建的 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 商店兼容的软件包。如果无法使用 6.11 或更高版本的 Qt,有一种方法可以使用 Google Play Feature Delivery,但需要手动创建 Android 项目,并将 Qt 二进制文件复制到该项目中。本文档的其余部分说明了如何实现这一点。这些说明可能无法一对一地适用于所有环境,但可以为成功实现功能交付提供很好的提示。建议以该示例为基础。

功能模块

功能模块的构建与普通库类似。

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

功能交付会像处理普通共享库一样处理 C++ 库,这些库在运行时可能可用,也可能不可用。在调用库之前,必须检查库的可用性。

主应用程序(Qt)
  • 使用Qt Creator 创建应用程序(此处使用Qt Quick 项目模板)。
  • 实现对功能交付库的访问
  • Google Play 功能交付 Java 库的核心类是Android.SplitInstallManager:
  • Android 模板文件可通过 QtCreatorProjects -> 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 文档中的 "按需请求模块 "一节简单介绍了如何请求模块。
  • 在项目的 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 的依赖项:在依赖项块中,将
    implementation 'androidx.core:core:1.13.1'
    替换

    implementation("com.google.android.play:feature-delivery:2.1.0")
  • 实现对功能模块提供的库的访问。由于功能模块可能对主应用程序可用,也可能不可用,因此在构建时不会链接模块,必须在运行时解决对模块的调用。示例
    QStringMapLoader::loadMapInfo() { QScopedPointer<QString> resultStr;typedef  void*(*LoadMapInfoFunc)();//查找 wintermap 库是否存在mWintermapLibrary.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< *>(result); } else.QString*>(result)); }else        qWarning() << Q_FUNC_INFO << "Function loadMapInfo not loaded";
    
       return *resultStr.data(); }
  • 实现用户界面和主应用程序的其他必要部分。
功能模块(Qt)
  • 使用Qt Creator 创建应用程序(使用了 Qt XML C++ 库项目模板)。
  • 实现模块提供的功能。
安卓项目(安卓)

创建用于构建 Android 应用程序捆绑包以进行功能交付的项目主要基于 Android 文档:

手动或使用 Android Studio(使用 "无活动 "模板)创建一个 Android 项目。修改后的项目包含一个顶级项目和两个子项目:appfeature-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)
应用程序 - 子项目
  • Android 项目需要主应用程序项目中的 Qt 二进制文件:
    • 将 Qt build:[build directory]/android-build/libs/[target ABI] 中的本地库复制到app/src/main/jniLibs/[target ABI]
    • [build directory]/android-build/libs/ 中的 jar 复制到app/libs/
  • 从 Qt 构建中,还将res 文件夹、AndroidManifest.xmllocal.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>
  • 将文件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 项目的构建文件需要进行一些修改。

app - 子项目
build.gradle
  • 删除buildScriptrepositories 块。
  • 主应用程序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 XML 已在gradle.properties 中添加了项目变量。如有需要,请更改androidPackageName 的值。

AndroidManifest.xml
  • 移除package
    ...
    <manifest
    ...
      android:package... <--remove
    ...
    >
    ...
  • 根据需要更改labelandroid.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/ 文件夹中应包含xmlvalues 文件夹,其中分别包含qtprovider_paths.xmllibs.xml 。这两个文件都可以从应用程序项目中复制。
  • 如果src/main/res/ 文件夹包含 drawable 或 mipmap 文件夹,而功能不需要它们,则可将其删除。
  • 在功能模块src/main/res/values 中不应包含app_name 字段。在不需要 strings.xml 的简单项目中,可以删除 strings.xml。
  • libs.xml strings.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 改为功能模块包。
构建和部署

可使用 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.