Cómo vincular Gradle con tu biblioteca nativa

Para incluir tu proyecto de biblioteca nativa como una dependencia de compilación de Gradle, debes proporcionarle a Gradle la ruta de acceso al archivo de secuencia de comandos de CMake o ndk-build. Cuando compilas tu app, Gradle ejecuta CMake o ndk-build, y empaqueta las bibliotecas compartidas con tu app. Gradle también usa la secuencia de comandos de compilación a fin de determinar los archivos que se incluirán en tu proyecto de Android Studio para que puedas acceder a ellos desde la ventana Project. Si no dispones de una secuencia de comandos de compilación para tus fuentes nativas, debes crear una secuencia de comandos de compilación de CMake para proceder.

Cada módulo de tu proyecto de Android puede vincularse a un solo archivo de secuencia de comandos de CMake o ndk-build. Por ejemplo, si deseas compilar y empaquetar resultados de varios proyectos de CMake, debes usar un archivo CMakeLists.txt como secuencia de comandos de compilación de CMake de nivel superior (que luego se vincula a Gradle) y agregar otros proyectos de CMake como dependencias de esa secuencia de comandos de compilación. Del mismo modo, si usas ndk-build, puedes incluir otros Makefiles en el archivo de secuencia de comandos Android.mk de nivel superior.

Una vez que vinculas Gradle con el proyecto nativo, Android Studio actualiza el panel Project para mostrar tus archivos de origen y bibliotecas nativas en el grupo cpp y tus secuencias de comandos de compilación externas en el grupo External Build Files.

Nota: Cuando realices cambios en la configuración de Gradle, asegúrate de aplicarlos. Para ello, haz clic en Sync Project en la barra de herramientas. Además, cuando realices cambios en el archivo de secuencia de comandos de CMake o ndk-build después de haberlo vinculado con Gradle, selecciona Build > Refresh Linked C++ Projects en la barra de menú para sincronizar Android Studio con los cambios.

Puedes vincular Gradle con un proyecto externo de CMake o ndk-build usando la IU de Android Studio:

  1. Abre el panel Project del lado izquierdo del IDE y selecciona la vista de Android.
  2. Haz clic con el botón derecho en el módulo que desees vincular con tu biblioteca nativa (por ejemplo, el módulo de app) y selecciona Link C++ Project with Gradle en el menú. Verás un diálogo similar al que se muestra en la figura 4.
  3. En el menú desplegable, selecciona CMake o ndk-build.
    1. Si seleccionas CMake, usa el campo junto a Project Path para especificar el archivo de secuencia de comandos CMakeLists.txt de tu proyecto de CMake externo.
    2. Si seleccionas ndk-build, usa el campo junto a Project Path para especificar el archivo de secuencia de comandos Android.mk de tu proyecto de ndk-build externo. Android Studio también incluye el archivo Application.mk si se encuentra en el mismo directorio que tu archivo Android.mk.

    Figura 4: Vinculación de un proyecto externo de C ++ desde el diálogo de Android Studio

  4. Haz clic en OK.

Cómo configurar Gradle manualmente

Para establecer de forma manual que Gradle se vincule con tu biblioteca nativa, debes agregar el bloque externalNativeBuild al archivo de nivel de módulo build.gradle y configurarlo con el bloque cmake o ndkBuild:

Groovy

android {
  ...
  defaultConfig {...}
  buildTypes {...}

  // Encapsulates your external native build configurations.
  externalNativeBuild {

    // Encapsulates your CMake build configurations.
    cmake {

      // Provides a relative path to your CMake build script.
      path "CMakeLists.txt"
    }
  }
}

Kotlin

android {
  ...
  defaultConfig {...}
  buildTypes {...}

  // Encapsulates your external native build configurations.
  externalNativeBuild {

    // Encapsulates your CMake build configurations.
    cmake {

      // Provides a relative path to your CMake build script.
      path = file("CMakeLists.txt")
    }
  }
}

Nota: Si deseas vincular Gradle con un proyecto ndk-build existente, usa el bloque ndkBuild en lugar de cmake y proporciona una ruta de acceso relativa al archivo Android.mk. Gradle también incluye el archivo Application.mk si se encuentra en el mismo directorio que el archivo Android.mk.

Cómo especificar configuraciones opcionales

Puedes especificar argumentos y marcas opcionales para CMake o ndk-build configurando otro bloque externalNativeBuild en el bloque defaultConfig de tu archivo build.gradle de nivel de módulo. Como en el caso de otras propiedades del bloque defaultConfig, puedes anular estas propiedades para cada variante de producto de tu configuración de compilación.

Por ejemplo, si tu proyecto de CMake o ndk-build define varias bibliotecas nativas y ejecutables, puedes usar la propiedad targets a fin de compilar y empaquetar solamente un subconjunto de esos artefactos para una variante de producto específica. En la siguiente muestra de código, se describen algunas de las propiedades que puedes configurar:

Groovy

android {
  ...
  defaultConfig {
    ...
    // This block is different from the one you use to link Gradle
    // to your CMake or ndk-build script.
    externalNativeBuild {

      // For ndk-build, instead use the ndkBuild block.
      cmake {

        // Passes optional arguments to CMake.
        arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"

        // Sets a flag to enable format macro constants for the C compiler.
        cFlags "-D__STDC_FORMAT_MACROS"

        // Sets optional flags for the C++ compiler.
        cppFlags "-fexceptions", "-frtti"
      }
    }
  }

  buildTypes {...}

  productFlavors {
    ...
    demo {
      ...
      externalNativeBuild {
        cmake {
          ...
          // Specifies which native libraries or executables to build and package
          // for this product flavor. The following tells Gradle to build only the
          // "native-lib-demo" and "my-executible-demo" outputs from the linked
          // CMake project. If you don't configure this property, Gradle builds all
          // executables and shared object libraries that you define in your CMake
          // (or ndk-build) project. However, by default, Gradle packages only the
          // shared libraries in your app.
          targets "native-lib-demo",
                  // You need to specify this executable and its sources in your CMakeLists.txt
                  // using the add_executable() command. However, building executables from your
                  // native sources is optional, and building native libraries to package into
                  // your app satisfies most project requirements.
                  "my-executible-demo"
        }
      }
    }

    paid {
      ...
      externalNativeBuild {
        cmake {
          ...
          targets "native-lib-paid",
                  "my-executible-paid"
        }
      }
    }
  }

  // Use this block to link Gradle to your CMake or ndk-build script.
  externalNativeBuild {
    cmake {...}
    // or ndkBuild {...}
  }
}

Kotlin

android {
  ...
  defaultConfig {
    ...
    // This block is different from the one you use to link Gradle
    // to your CMake or ndk-build script.
    externalNativeBuild {

      // For ndk-build, instead use the ndkBuild block.
      cmake {

        // Passes optional arguments to CMake.
        arguments += listOf("-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang")

        // Sets a flag to enable format macro constants for the C compiler.
        cFlags += listOf("-D__STDC_FORMAT_MACROS")

        // Sets optional flags for the C++ compiler.
        cppFlags += listOf("-fexceptions", "-frtti")
      }
    }
  }

  buildTypes {...}

  productFlavors {
    ...
    create("demo") {
      ...
      externalNativeBuild {
        cmake {
          ...
          // Specifies which native libraries or executables to build and package
          // for this product flavor. The following tells Gradle to build only the
          // "native-lib-demo" and "my-executible-demo" outputs from the linked
          // CMake project. If you don't configure this property, Gradle builds all
          // executables and shared object libraries that you define in your CMake
          // (or ndk-build) project. However, by default, Gradle packages only the
          // shared libraries in your app.
          targets += listOf("native-lib-demo",
                  // You need to specify this executable and its sources in your CMakeLists.txt
                  // using the add_executable() command. However, building executables from your
                  // native sources is optional, and building native libraries to package into
                  // your app satisfies most project requirements.
                  "my-executible-demo")
        }
      }
    }

    create("paid") {
      ...
      externalNativeBuild {
        cmake {
          ...
          targets += listOf("native-lib-paid",
                  "my-executible-paid")
        }
      }
    }
  }

  // Use this block to link Gradle to your CMake or ndk-build script.
  externalNativeBuild {
    cmake {...}
    // or ndkBuild {...}
  }
}

Si quieres obtener más información sobre la configuración de variantes de productos y de compilación, consulta la sección Cómo configurar variantes de compilación. Si quieres obtener una lista de variables que puedes configurar para CMake con la propiedad arguments, consulta Cómo usar variables de CMake.

Cómo incluir bibliotecas nativas ya compiladas

Si quieres que Gradle empaquete las bibliotecas nativas compiladas previamente que no se usan en ninguna compilación nativa externa, agrégalas al directorio src/main/jniLibs/ABI de tu módulo.

Se requieren versiones del complemento de Gradle para Android anteriores a 4.0 que incluyan los objetivos IMPORTED de CMake en tu directorio jniLibs para que se incluyan en la app. Si migras desde una versión anterior del complemento, es posible que veas un error similar al siguiente:

* What went wrong:
Execution failed for task ':app:mergeDebugNativeLibs'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
   > More than one file was found with OS independent path 'lib/x86/libprebuilt.so'

Si usas el complemento de Android para Gradle 4.0, mueve las bibliotecas que usa IMPORTED de CMake fuera del directorio jniLibs a fin de evitar este error.

Cómo especificar ABI

De forma predeterminada, Gradle compila la biblioteca nativa en archivos .so separados para las interfaces binarias de la aplicación (ABI) que admite el NDK y los empaqueta en la app. Si deseas que Gradle compile y empaquete solamente ciertas configuraciones de ABI de las bibliotecas nativas, puedes marcarlas con el indicador ndk.abiFilters en el archivo build.gradle del nivel de módulo, tal como se muestra a continuación:

Groovy

android {
  ...
  defaultConfig {
    ...
    externalNativeBuild {
      cmake {...}
      // or ndkBuild {...}
    }

    // Similar to other properties in the defaultConfig block,
    // you can configure the ndk block for each product flavor
    // in your build configuration.
    ndk {
      // Specifies the ABI configurations of your native
      // libraries Gradle should build and package with your app.
      abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
                   'arm64-v8a'
    }
  }
  buildTypes {...}
  externalNativeBuild {...}
}

Kotlin

android {
  ...
  defaultConfig {
    ...
    externalNativeBuild {
      cmake {...}
      // or ndkBuild {...}
    }

    // Similar to other properties in the defaultConfig block,
    // you can configure the ndk block for each product flavor
    // in your build configuration.
    ndk {
      // Specifies the ABI configurations of your native
      // libraries Gradle should build and package with your app.
      abiFilters += listOf("x86", "x86_64", "armeabi", "armeabi-v7a",
                   "arm64-v8a")
    }
  }
  buildTypes {...}
  externalNativeBuild {...}
}

En la mayoría de los casos, solo es necesario especificar abiFilters en el bloque ndk, como se muestra más arriba, ya que le indica a Gradle que debe compilar y empaquetar esas versiones de tus bibliotecas nativas. Sin embargo, si deseas controlar lo que Gradle debe compilar, independientemente de lo que quieras que empaquete en tu app, configura otra marca abiFilters en el bloque defaultConfig.externalNativeBuild.cmake (o el bloque defaultConfig.externalNativeBuild.ndkBuild). Gradle compila esas configuraciones de ABI, pero solamente empaqueta las que especificas en el bloque defaultConfig.ndk.

Se recomienda publicarlas mediante Android App Bundle para reducir aún más el tamaño de la app, ya que, con la descarga, solo se ofrecerán las bibliotecas nativas que coincidan con la ABI del dispositivo de un usuario.

En el caso de las apps heredadas que se publican mediante APK (creadas antes de agosto de 2021), considera configurar varios APK basados en la ABI. En lugar de crear un APK de gran tamaño con todas las versiones de tus bibliotecas nativas, Gradle crea un APK independiente para cada ABI que desees admitir y solo empaqueta los archivos que necesita cada ABI. Si configuras varios APK por ABI sin especificar la marca abiFilters, como se indica en la muestra de código anterior, Gradle compila todas las versiones de ABI compatibles de tus bibliotecas nativas, pero solo empaqueta las que especificas en la configuración de varios APK. Para evitar compilar versiones de bibliotecas nativas que no deseas, proporciona la misma lista de ABI para la marca abiFilters y tu configuración de varios APK por ABI.