En esta página

Uso de la función Play Delivery

Muestra el uso de Google Play Feature Delivery en Qt.

Este documento describe la funcionalidad del ejemplo de Feature Delivery. El ejemplo utiliza características soportadas desde Qt 6.11.
Play Feature Delivery puede usarse en versiones anteriores de Qt, pero crear la aplicación requiere añadir manualmente el proyecto Android y copiar los binarios Qt. Las instrucciones para ello se encuentran en el capítulo Feature Delivery en pre Qt 6.11.

¿Qué es Feature Delivery?

Play Feature Delivery es una capacidad ofrecida por Google que esencialmente permite a los desarrolladores estructurar sus proyectos de manera que la tienda Google Play pueda dividir el contenido de su aplicación en varios paquetes descargables. También permite a los desarrolladores controlar cómo se entrega el contenido a los usuarios. Estos paquetes divididos de software y contenidos se entregan en la tienda Google Play mediante Android App Bundles (AAB). La documentación para desarrolladores de Google detalla esta función.

Proyecto de ejemplo: Feature Delivery Map Loader

Esta sencilla aplicación utiliza Play Feature Delivery para servir imágenes a un usuario cuando éste las solicita. La aplicación se puede modificar fácilmente para crear una aplicación de más de 200 MB para probar los límites de tamaño de la aplicación y descargar utilizando un módulo grande de Feature Delivery.

La aplicación

La aplicación consiste en una vista que se puede arrastrar y cuatro botones.

  • Al inicio se activan los botones Load Map y Show Map Info.
  • Cuando se pulsa el botón Show Map Info sólo muestra que no hay información disponible.
  • Load Map inicia la carga del módulo de características:
    1. Se muestra un pop-up de descarga, desde el pop-up se puede cancelar la descarga.
    2. Cuando finaliza la descarga, se elimina la ventana emergente y se activan los botones
    3. Change Map y Remove Map se activan.
  • Remove Map solicita la desinstalación del módulo de características, deshabilitando los botones habilitados.
  • Cuando se pulsa Change Map se abre una vista en la que se puede cambiar el mapa mostrado. En este ejemplo, el módulo de características consiste en una sola imagen de mapa de temática invernal.
Configuración de la carpeta de origen
  • fdwintermapmodule: Módulo de características
  • fdmaploader: La aplicación principal
  • fdmaploader/storeloader: Interfaz JNI de entrega de funciones
Interfaz de Feature Delivery

La carpeta fdmaploader/storeloader contiene la clase de interfaz PlayStoreLoader para la API Feature Delivery. La API no está completa, pero contiene funciones relevantes para cargar y eliminar módulos de características. La carga de módulos se inicia con una llamada a PlayStoreLoader::loadModule. El estado del proceso puede supervisarse con las señales proporcionadas por PlayStoreLoaderHandler. Con la función PlayStoreLoader::getHandler se puede obtener un "handle" de las llamadas de retorno. La API de ejemplo también proporciona una forma de comprobar los módulos ya instalados con la función PlayStoreLoader::getInstalledModules y una opción para eliminar los módulos instalados con PlayStoreLoader::uninstallModules.

Este ejemplo está diseñado para que un desarrollador pueda añadir fácilmente su propio contenido para probar la entrega de funciones. Para superar el tamaño máximo de los paquetes de Play Store, se pueden añadir imágenes de mapas (no es necesario que sean imágenes de mapas, pero encaja con el tema del ejemplo.) a las carpetas de imágenes en fdmaploader y fdwintermapmodule, los nombres de las imágenes también deben añadirse al archivo images.qrc.

Bajo el capó

El módulo API consta de dos partes. La interfaz Qt (PlayStoreLoader y PlayStoreLoaderHandler) y las clases java que se encargan de llamar a Android: Interfaces de instalación dividida de Google. La interfaz Qt funciona principalmente como un passthrough para las clases java. La interfaz Qt simplifica la API de modo que, cuando se carga el módulo de características, las clases Google SplitCompat y SplitInstall y los oyentes se crean y liberan automáticamente. En este ejemplo se omiten partes de la API, como deferredInstall y el soporte de idiomas.
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 función qt6_add_android_dynamic_features añade la biblioteca dinámica específica como la característica dinámica para el objetivo de la aplicación Android. requiere QT_USE_ANDROID_MODERN_BUNDLE característica que se habilite. Eso se puede hacer ya sea como una bandera en tiempo de compilación o establecer en CMakeLists. La carpeta donde reside la parte Java de la interfaz se añade a la compilación utilizando qt_add_android_dynamic_feature_java_source_dir.

En la aplicación de ejemplo, los módulos se cargan utilizando la interfaz storeloader. Las funciones Qt PlayStoreLoader y la clase PlayStoreLoaderHandler funcionan como un intermediario entre el código de ejemplo y 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); }

Las clases Java gestionan las llamadas a la API Split Install.

        m_splitInstallManager.startInstall(request)
                .addOnSuccessListener(sessionId -> {
                    PlayStoreLoaderListener listener = m_listeners.get(callId);
                    if (listener != null)
                        listener.setSessionId(sessionId);
                })
Creación de binarios

Utilizando la línea de comandos se puede construir un paquete AAB localmente comprobable.

Cree e introduzca un directorio de construcción al mismo nivel que el directorio fuente:

mkdir build-feature-delivery/ ; cd build-feature-delivery/

Configurar:

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

Construir:

ninja aab
Pruebas

La AAB construida puede ser probada localmente usando bundletool con el parámetro --local-testing. Android: Documentación Bundletool El comando bundletool build-apks crea un archivo apks que puede ser instalado en un dispositivo o emulador con el comando install-apks.

Comandos Bundletool utilizados

Generar APK:s a partir de un bundle:

bundletool build-apks --bundle=/path/to/bundle.aab --output=/path/to/apk/package.apks --local-testing

Instalar la aplicación en el dispositivo:

bundletool install-apks --apks=/path/to/apk/package.apks
Entregar a Play Store

Para poder subir el paquete AAB creado a Google Play Store el paquete debe estar firmado. Para ello se puede utilizar jarsigner. A continuación se muestra un ejemplo de comando jarsigner para firmar el paquete AAB. Ver Android: Documentación de Jarsigner

jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 -keystore [path-to-keystore-file].keystore [path-to-aab-file].aab [alias]

Entrega de características en pre Qt 6.11

Versiones anteriores a Qt 6.11 no soportan la generación de paquetes compatibles con Google Play Store. Si no puedes usar una versión Qt 6.11 o posterior hay una forma de usar Google Play Feature Delivery, pero requiere la creación manual del proyecto Android, y copiar los binarios Qt en él. El resto del documento tiene instrucciones de cómo lograrlo. Las instrucciones pueden no ser compatibles 1 a 1 para todos los entornos, pero deberían dar buenas pistas para lograr una implementación exitosa de Feature Delivery. Se aconseja utilizar el ejemplo como base.

Módulo de características

Los módulos de características se construyen como bibliotecas normales.

  • Utilice Qt Creator para crear una biblioteca compartida C++.
  • Implementa características y añade recursos.
  • Construir para crear binarios .so.

Feature Delivery maneja las bibliotecas C++ como bibliotecas compartidas normales que pueden o no estar disponibles en tiempo de ejecución. Antes de llamar a una biblioteca, se debe comprobar su disponibilidad.

Aplicación principal (Qt)
  • Utilice Qt Creator para crear una aplicación (aquí se utilizó la plantilla de proyectoQt Quick ).
  • Implemente el acceso a la biblioteca Feature Delivery. La clase central de la librería Java Google Play Feature Delivery es Android: SplitInstallManager.
  • Los archivos de plantilla de Android pueden ser creados usando el botón 'Create Templates' en QtCreator Projects -> Build&Run -> [target ABI] -> Build Steps -> Build Android APK. Las plantillas se crean en la carpeta 'android' del proyecto.
  • Añade los archivos Java a la carpeta .../android/src/java/[package...] y las rutas de los archivos a CMakeLists.txt
  • :
  • qt_add_executable...
    ...[path]/[java-filename.java]
    ...
  • En el ejemplo, se ha creado una clase Java para gestionar las llamadas y los callbacks. La clase Java sería accedida desde Qt usando JNI. Android: Request an on demand module section en la documentación de Android tiene una descripción sencilla de cómo solicitar un módulo.
  • Cuando se añaden archivos Java bajo la carpeta android en el proyecto, la propiedad QT_ANDROID_PACKAGE_SOURCE_DIR debe añadirse a CMakeLists.txt:
    ...
    set_property(TARGET appFDMainApp APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
                 ${CMAKE_CURRENT_SOURCE_DIR}/android)
    ...
  • Además, la app principal build.gradle debe tener dependencias para la API de características: en el bloque de dependencias, sustituir
    implementation 'androidx.core:core:1.13.1'

    por

    implementation("com.google.android.play:feature-delivery:2.1.0")
  • Implementar el acceso a la librería proporcionada por el módulo de características. Dado que el módulo de funciones puede o no estar disponible para la aplicación principal, los módulos no se enlazan en tiempo de compilación y las llamadas al módulo deben resolverse en tiempo de ejecución. Ejemplo:
    QString MapLoader::loadMapInfo() { QScopedPointer<QString> resultStr; typedef  void*(*LoadMapInfoFunc)(); //Buscar si existe la biblioteca wintermapmWintermapLibrary.setFileName("fdwintermapmodule"); if (!mWintermapLibrary.load()) {        qWarning() << Q_FUNC_INFO << "Failed to load library";
           return QString(); } LoadMapInfoFunc loadMapInfo = (LoadMapInfoFunc) mWintermapLibrary.resolve("loadMapInfo"); if (loadMapInfo) {  void* result = loadMapInfo(); resultStr.reset(static_cast<QString*>(resultado)); } else        qWarning() << Q_FUNC_INFO << "Function loadMapInfo not loaded";
    
       return *resultStr.data(); }
  • Implementa la interfaz de usuario y otras partes necesarias de la aplicación principal.
Módulo de funciones (Qt)
  • Utiliza Qt Creator para crear una app (se ha utilizado la plantilla de proyecto Qt C++ Library).
  • Implementa las características que ofrece el módulo.
Proyecto Android (Android)

La creación de un proyecto para crear un paquete de aplicaciones Android para la entrega de funciones se basa principalmente en la documentación de Android:

Crear un proyecto Android manualmente o usando Android Studio (Usando la plantilla 'No Activity'). El proyecto se modifica para que contenga un proyecto de nivel superior y dos subproyectos, app y feature-module. La plantilla de Android Studio crea el sub-proyecto app y el feature-module puede ser añadido usando la plantilla File -> New -> New Module.

El proyecto de plantilla requiere varias modificaciones:

  • Añadir el plugin Feature Delivery al nivel principal 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
    }
  • Añadir módulo de características a la settings.gradle, cambiar rootProject.name si es necesario:
    ...
    rootProject.name = "name-of-the-root-project"
    include(:app)
    include(:name-of-the-feature-module)
app - Sub-proyecto
  • El proyecto Android requiere los binarios Qt del proyecto App principal:
    • Copie las bibliotecas nativas en Qt build: [build directory]/android-build/libs/[target ABI] a app/src/main/jniLibs/[target ABI]
    • Copie los jars en [build directory]/android-build/libs/ a app/libs/
  • Desde la compilación de Qt, también se copian los contenidos de la carpeta res, AndroidManifest.xml, y local.properties a sus respectivos lugares en el proyecto Android.
  • Añade el archivo feature_names.xml a la carpeta app/src/main/res/values que contiene una cadena para el módulo de características:
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="feature_module_name">name-of-the-feature-module-here</string>
    </resources>
  • Añade el archivo keep.xml a la carpeta app/src/main/res/raw que contiene:
    <?xml version="1.0" encoding="utf-8"?>
    <resources xmlns:tools="http://schemas.android.com/tools"
        tools:keep="@string/feature_module_winter_map"
        tools:discard="" />
Modificaciones a los archivos de compilación del subproyecto de la aplicación

Los archivos de compilación copiados al proyecto Android necesitan algunas modificaciones.

app - Sub-proyecto
build.gradle
  • Eliminar los bloques buildScript y repositories.
  • El bloque Android en la aplicación principal build.gradle requiere algunas modificaciones:
    • 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
...
}

Añade también la configuración de firma al bloque 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 ha añadido variables de proyecto a gradle.properties. Cambia el valor de androidPackageName si es necesario.

AndroidManifest.xml
  • Elimine package:
    ...
    <manifest
    ...
      android:package... <--remove
    ...
    >
    ...
  • Cambie label y android.app.lib_name si es necesario:
    ...
    <application ...
      android:label=" ...
      <activity ... >
        <meta-data android:name="android.app.lib_name" android:value=" ...
        />
    ...
feature-module - Subproyecto

Los módulos app y feature se crean como subproyectos para el proyecto Android de nivel superior. La estructura de carpetas y archivos es similar a la del subproyecto app.

  • Los binarios del módulo de características de la compilación de Qt se copian en la carpeta [name-of-feature-module]/src/main/jniLibs/
  • Como en la app principal, la carpeta src/main/res/ debería tener las carpetas xml y values que contienen qtprovider_paths.xml y libs.xml respectivamente. Ambos archivos pueden copiarse desde el proyecto de la aplicación.
  • Si la carpeta src/main/res/ contiene carpetas drawable o mipmap y la característica no las requiere, pueden ser eliminadas.
  • En el módulo de características src/main/res/values no debe contener el campo app_name. En proyectos sencillos en los que strings.xml no sea necesario para otros usos, puede eliminarse.
  • libs.xml contiene únicamente el nombre del módulo de características:
    ...
        <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 se añade al directorio 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>
  • El módulo de características build.gradle es bastante similar al del proyecto app, con algunos cambios. He aquí un ejemplo:
    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 el archivo puede ser copiado desde el sub-proyecto app, cambia el androidPackageName por el paquete feature module.
Construcción y despliegue

El paquete AAB se puede construir desde la línea de comandos utilizando gradle wrapper: ./gradlew bundle El AAB producido estará en la carpeta build/outputs/bundle/release (o debug). A continuación, la AAB puede copiarse en Google Play Store y liberarse para su prueba. Las pruebas también se puede hacer a nivel local mediante el uso de bundletool con --local-testing parámetro.

Proyecto de ejemplo @ 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.