Cómo configurar NDK con Android Gradle plugin 0.7


El nuevo complemento de Android gradle (0.7) parece incluir nuevo soporte para el NDK, pero en la documentación hay poca o ninguna mención de él (la única referencia que encontré es una prueba llamada ndkSanAngeles).

Parece que gradle está buscando el NDK, que he incluido en mi CAMINO. Sin embargo, la construcción del proyecto falla con

  • Lo que salió mal: Falló la ejecución de la tarea': OGLTests: compileDefaultFlavorDebugNdk'. NDK no configurado

¿Cómo puedo configurar el NDK en gradle?

Mi versión actual.gradle se ve así:

task nativeLibsToJar(type: Zip, description: 'create a jar with native libs') {
    destinationDir file("$buildDir/native-libs")
    baseName 'native-libs'
    extension 'jar'
    from fileTree(dir: 'src/main/libs', include: '**/*.so')
    from fileTree(dir: 'src/main/libs', include: '**/gdb*')
    into 'lib/'
}

tasks.withType(JavaCompile) {
    compileTask -> compileTask.dependsOn nativeLibsToJar
}

dependencies {
    compile fileTree(dir: "$buildDir/native-libs", include: '*.jar')
}

android {
    compileSdkVersion 19
    buildToolsVersion '19.0.0'

    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 19
        versionCode 1
        versionName "0.1"

    }
    buildTypes {
        release {
            runProguard false
        }
        debug {
           // jniDebugBuild true
            runProguard false
            debuggable true
        }
    }
    productFlavors {
        defaultFlavor {
            proguardFile 'proguard-rules.txt'
        }
    }
}

Gracias.

Author: user1906, 2013-12-19

6 answers

Mirando a través del código del complemento de gradle, encontré lo siguiente que me ayudó a usar tanto NDK como bibliotecas nativas prediseñadas:

Para simplemente Enlazar en Bibliotecas Nativas Prediseñadas, simplemente agregue una sección ndk a su tarea. Por ejemplo, lo agregué a continuación en productFlavors. El abiFilter es el nombre de la carpeta en la que se almacenan las libs. abiFilters significa que ambas bibliotecas de la lista separada por comas se agregarán a su APK final (por lo que teóricamente podría tener "armeabi", " armeabi-v7a", "x86", y" mips " todo en un APK, y el O / S elegiría la biblioteca de arquitectura compatible en la instalación):

productFlavors {
    arm {
        ndk {
            abiFilters "armeabi", "armeabi-v7a"
        }
    }
    x86 {
        ndk {
            abiFilter "x86"
        }
    }
}

En este ejemplo, la compilación arm creará un APK con las librerías arm V5 y V7A en él, y la compilación x86 creará un APK con solo librerías x86 en él. Esto buscará las bibliotecas nativas en el directorio jniLibs de su proyecto. El directorio jniLibs debe ser estructuras como el antiguo directorio jni, es decir:

[project]/[app]/src/main/jniLibs/armeabi/libmyNative.so
[project]/[app]/src/main/jniLibs/armeabi-v7a/libmyNative.so
[project]/[app]/src/main/jniLibs/x86/libmyNative.so

Entonces puedes cargarlo en Java como sigue:

static
{
    loadLibrary("myNative");
}

Ahora, digamos que una biblioteca nativa depende de otra. Debe (si configura su API min a API 17 o inferior) cargar primero las bibliotecas dependientes:

static
{
    loadLibrary("myDependency");
    loadLibrary("myNative");
}

También puede colocar la sección ndk {} en su defaultConfig o un buildType (como debug o release o cualquier otra cosa que pueda usar). Por ejemplo:

buildTypes {
    debug {
        ndk {
            abiFilters "armeabi", "armeabi-v7a"
        }
    }
}

Por prebuilt, me refiero a 3rd party libs que descargó o una biblioteca que construyó utilizando la cadena de herramientas NDK o su propia cadena de herramientas ARM (no el ndk-build script en sí).

En la API 18, arreglaron un problema de arquitectura de larga data que impedía que el cargador de lib nativo cargara dependencias "automáticamente" porque no sabía sobre el directorio de lib de la aplicación (razones de seguridad, etc.). En API 18 y superiores, si myNative depende de myDependency anterior, solo puede llamar a LoadLibrary ("myNative") y el sistema operativo se encargará de cargar myDependency. No CONFÍE en esto, sin embargo, hasta la penetración en el mercado de los dispositivos que ejecutan API 17 y abajo está en un número bajo aceptable para usted.


Para construir explícitamente Bibliotecas NDK Desde el Origen en la versión actual de Android Studio, puede hacer lo siguiente:

Establece el ndk.valor dir en su local.propiedades para apuntar a NDK home como se mencionó anteriormente. ¿Alguien sabe si puede usar env vars directamente en local?propiedades? :)

En tu compilación.gradle archivo, añadir algo como esto a su tarea (de nuevo, puede ser defaultConfig, depurar, liberación, un sabor de producto, etc.):

ndk {
    moduleName "myNDKModule"
    stl "stlport_shared"
    ldLibs "log", "z", "m"
    cFlags "-I/some/include/path"
}

Esta es la estructura básica con tipos soportados actualmente (moduleName, stl, ldLibs y cFlags). Miré y no encontré más que esto. Hay un problema que creo con ldLibs porque automáticamente agrega "-l" al frente de cada campo de arriba. Sin embargo, puedes engañarlo (tuve que hacerlo) diciendo: ldLibs "log-lz-lm-Wl,-whole-archive-l/path/to/someOtherLib-Wl,-no-whole-archive"

En esta línea, solo estás etiquetando hasta el final del primer parámetro para agregar parámetros que no comienzan con-l, por lo que puede sobrevivir por ahora. En el caso anterior, estoy vinculando toda una biblioteca estática en mi módulo NDK para su uso desde dentro de Java. Le he pedido al desarrollador de Google que agregue características adicionales para permitir esto o incluso la capacidad de fusionar el suyo propio Android.mk archivar en el proceso de compilación de NDK, pero como todo esto es nuevo, puede tardar un tiempo.

Actualmente, lo que sea que pongas en build.gradle elimina el directorio de compilación temporal y lo recrea cada vez, así que a menos que quieras descargar y modificar el código fuente del plugin de android de gradle (lo cual sería divertido), hay algunos "make due"como este necesarios para que tus cosas se copien en la compilación. El script de Android gradle que proporciona este soporte ndk en esencia genera un Android.mk archivos y compilaciones usando el sistema NDK en un directorio temporal.

Desviado por un segundo. El nombre del módulo debe coincidir con un archivo c o cpp en su proyecto bajo el directorio jni como:

[project]/[app]/src/main/jni/myNDKModule.cpp

El valor stl debe establecerse en un valor de "stlport_shared" o "stlport_static" si desea usar las bibliotecas stlport para C++. Puede dejar stl fuera si no necesita soporte extendido para C++. Recuerde que Android proporciona soporte C++ muy básico de forma predeterminada. Para otras bibliotecas de C++ compatibles, consulte las directrices de documentación de NDK en el NDK que descargó. Tenga en cuenta que al configurarlo en stlport_shared aquí, gradle copia el libstlport_shared.so lib de tu NDK sources/cxx-stl / stlport / libs directorio a los directorios lib de tu APK. También maneja la ruta de inclusión en el compilador (técnicamente gradle no hace todo esto, sino el sistema de compilación NDK de Android). Así que no ponga su propia copia de stlport en su directorio jniLibs.

Por último, creo que cFlags es bastante obvio.

No se puede establecer ANDROID_NDK_HOME en el Mac OSX (ver a continuación), pero de alguna investigación que he hecho parece tal vez esto todavía funciona en otros OSs. Se eliminará aunque.

Quería comentar pero no tengo la reputación todavía. Dennis, las variables de entorno son ignoradas completamente, no solo anuladas. De hecho, no obtienes ninguna de tus variables de entorno. Por lo que puedo decir, el IDE de Android Studio crea su propio entorno con solo unas pocas variables de entorno específicas (check System.getenv () e imprimirlo desde un script de gradle).

Escribí esto como un error aquí porque el uso de env vars construye bien desde la línea cmd:
https://code.google.com/p/android/issues/detail?id=65213

Pero como se puede ver, Google decidió que no quería que las variables de entorno se utilizan por el IDE en absoluto; todavía estoy en la valla en esa decisión. Me duele la vida tener que actualizar local.propiedades para apuntar a rutas absolutas que se pueden cargar en mis scripts de gradle, que todavía no he descubierto cómo hacerlo (pero no he buscado tan duro). Eso significa que o bien fuerzo a los miembros de mi equipo a usar el la misma ruta que yo, juega con enlaces, haz que todos los escriban cada vez que extraigan el repositorio o agrega un script de automatización. Creo que es una mala decisión que le costará tiempo a cualquier desarrollador que confíe en env vars, que puede ser pequeño a nivel micro pero enorme a nivel macro.

Groundloop, creo que el IDE se actualizará pronto con la capacidad de agregar la ruta de la carpeta NDK a su proyecto, y generará automáticamente el local.archivo de propiedades de eso (al menos no tendría sentido si no hubieran pensado en esto).

Para ejemplos más detallados de Google, aquí están los últimos ejemplos (buscar jni o ndk): https://docs.google.com/viewer?a=v&pid=sites&srcid=YW5kcm9pZC5jb218dG9vbHN8Z3g6NDYzNTVjMjNmM2YwMjhhNA


apk fat multiplataforma usando NDK:

Por último, hay un inconveniente al usar gradle y no poder proporcionar su propio Android.mk archivo para que solo pueda enlazar en bibliotecas nativas de terceros desde un arquitectura única a su NDK. Nota: dije "enlazar". Puede construir los módulos NDK (moduleName arriba) en varias arquitecturas con el comando "abiFilters", y se colocarán en su aplicación de manera que el mismo APK pueda usarse en múltiples arquitecturas. Si necesita enlazar en sus propias librerías de terceros o incluso tener diferentes valores para cFlags dependiendo de su arquitectura, no hay una manera simple.

Probé lo siguiente y parecía funcionar al principio, pero luego encontré fue simplemente construir el NDK anexando todo junto de las dos secciones de ndk (o algo así, de alguna manera construyó múltiples bibliotecas de arquitectura):

android {
    compileSdkVersion 23
    buildToolsVersion '23.0.1'
    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 28
        versionName "3.0"
    }
    buildTypes {
        def commonLibs = " -lfoo -lbar -lwhatever"
        def armV7LibsDir = "/whatever/armv7a/libs"
        def armX86LibsDir = "/whatever/x86/libs"
        def armV7IncDir = "/whatever/armv7a/include"
        def x86IncDir = "/whatever/x86/include"
        debug {
            ndk {
                cFlags = "-I" + armV7IncDir
                moduleName "myNativeCPPModule"
                stl "stlport_shared"
                abiFilter "armeabi-v7a"
                ldLibs "log -L" + armV7LibsDir + commonLibs
            }
            ndk {
                cFlags = "-I" + armX86IncDir
                moduleName "myNativeCPPModule"
                stl "stlport_shared"
                abiFilter "x86"
                ldLibs "log -L" + armX86LibsDir + commonLibs
            }
        }
    }
}

Después de mucho dolor tratando de crear un binario gordo en una mansión limpia con gradle y bibliotecas nativas de 3rd party, finalmente llegué a la conclusión de que el soporte multiarquitectura integrado de Google Play para APK es realmente la mejor ruta a seguir de todos modos, así que crea APK individuales para cada arquitectura.

Así que creé varios tipos de compilación, sin tipos de producto, y agregué el siguiente código para generar el código de versión para cada tipo.

// This is somewhat nasty, but we need to put a "2" in front of all ARMEABI-V7A builds, a "3" in front of 64-bit ARM, etc.
// Google Play chooses the best APK based on version code, so if a device supports both X86 and
// ARM, it will choose the X86 APK (preferred because Inky ARM running on an X86 with Houdini ARM Emulator crashes in our case)
android.applicationVariants.all { variant ->
    if (variant.buildType.name.equals('release')) {
        variant.mergedFlavor.versionCode = 2000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('debug')) {
        variant.mergedFlavor.versionCode = 2000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('debugArmV8a')) {
        variant.mergedFlavor.versionCode = 3000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('releaseArmV8a')) {
        variant.mergedFlavor.versionCode = 3000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('debugMips')) {
        variant.mergedFlavor.versionCode = 5000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('releaseMips')) {
        variant.mergedFlavor.versionCode = 5000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('debugMips64')) {
        variant.mergedFlavor.versionCode = 6000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('releaseMips64')) {
        variant.mergedFlavor.versionCode = 6000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('debugX86')) {
        variant.mergedFlavor.versionCode = 8000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('releaseX86')) {
        variant.mergedFlavor.versionCode = 8000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('debugX86_64')) {
        variant.mergedFlavor.versionCode = 9000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('releaseX86_64')) {
        variant.mergedFlavor.versionCode = 9000 + defaultConfig.versionCode
    }
}

Ahora todo lo que tiene que hacer es establecer el valor para versionCode en su objeto defaultConfig, como lo haría normalmente, y esto lo agrega al final de la cadena de versión específica de la arquitectura, en función del tipo de compilación. La cadena de versión sigue siendo la misma para todas las compilaciones, pero varía el código para proporcionar un orden de precedencia desde ARM hasta el final para X86_64. Es un poco hackish o hard-coded, pero hace el trabajo. Tenga en cuenta que esto le proporciona hasta 999 versiones, por lo que si necesita más, multiplique los números anteriores por 10, no estoy seguro de cuál es el valor máximo que puede poner para el código de versión.

En mi caso, tenemos un sistema de construcción bastante complejo. Construimos CPython para 9 arquitecturas, 3 de las cuales son Android, luego construimos un montón de nuestras propias bibliotecas y las vinculamos en una única biblioteca para cada arquitectura. Nos utilice las herramientas de compilación de línea de comandos ndk, automake y python para compilar todo, en lugar de Android.mk archivos. Las bibliotecas finales se vinculan a un único archivo cpp de interfaz JNI (llamado myNativeCPPModule arriba). Un clic del botón, y todo está construido a la vez, muy bonito Android Studio.

 76
Author: reactive-core,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-18 21:35:07

Encontró la respuesta. Incluir ndk.dir=path/to/ndk en el archivo local.properties hizo el truco.

Actualización: En las versiones más recientes de Android Studio, puedes establecer el valor directamente en la Estructura del proyecto > Ubicación del SDK.

 34
Author: user1906,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-08 10:25:24

También puede establecer la variable de entorno ANDROID_NDK_HOME

 15
Author: stefan.nsk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-24 11:43:42

Pasé mucho tiempo config ndk en build.gradle. Tengo un buen blog resolviendo mi problema.

 1
Author: peacepassion,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-19 06:08:41

Como se comentó antes, añadiendo ndk.dir = en local.propiedades ayuda. Curiosamente encontré que local.properties anula cualquier valor establecido para la variable de entorno ANDROID_NDK_HOME, incluso si no tiene ndk.dir configurado en local.propiedades. (al menos con gradle android plugin v 0.7.3).

Esto es confuso ya que Android Studio puede sobrescribir local.propiedades, y no parece proporcionar una forma de configurar ndk.dir: (

 0
Author: groundloop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-24 17:03:38

Android studio sugiere incluir la ruta a ndk en local.propiedades

 0
Author: Twinkle Mishra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-20 11:52:32