Android Studio*, Gradle* und NDK-Integration

Durch die kürzlich implementierten Änderungen (Release 0.7.3 wird das neue Android-Build-System nun wirklich interessant, insbesondere, wenn man das NDK verwendet! 
Sie können jetzt native Bibliotheken einfach in Ihr Paket integrieren sowie APKs für unterschiedliche Architekturen generieren und dabei die Versionscodes korrekt handhaben (weitere Informationen, warum dies wichtig sein könnte, finden Sie in meinem ersten Artikel).

.so-Dateien in Ihr APK integrieren

Wenn Sie Android Studio verwenden und native Bibliotheken in Ihre App integrieren wollen, mussten Sie bislang eventuell komplexe Methoden anwenden, darunter Maven- und .aar/.jar-Pakete. Aber es gibt gute Neuigkeiten: Diese Zeiten sind vorbei!

Sie müssen nur Ihre .so-Bibliotheken im jniLibs-Verzeichnis in den entsprechenden Unterverzeichnissen für jede unterstützte ABI (x86, mips, armeabi-v7a, armeabi) ablegen... und das war's! 
Sobald dies erledigt ist, werden alle .so-Dateien beim Build Ihres APK darin integriert:

Wenn Sie den Verzeichnisnamen jniLibs ändern wollen (Sie können Ihre .so-Dateien an einer anderen Stelle generieren), geben Sie den gewünschten Speicherort in build.gradle an:

android {
    ...
    sourceSets.main {
        jniLibs.srcDir 'src/main/libs'
    }
}

Wie man ein APK pro Architektur erstellt... und zwar richtig!

Mithilfe von Flavors – und der abiFilter-Eigenschaft – können Sie äußerst einfach ein APK pro Architektur erstellen.

Der Standardwert für ndk.abiFilter(s) lautet all. Diese Eigenschaft wirkt sich auf die Integration von .so-Dateien und die ndk-build-Aufrufe (auf die ich am Ende dieses Artikels zu sprechen komme) aus.

Fügen Sie nun einige Architektur-Flavors zu build.gradle hinzu:

android{
  ...
  productFlavors {
        x86 {
            ndk {
                abiFilter "x86"
            }
        }
        mips {
            ndk {
                abiFilter "mips"
            }
        }
        armv7 {
            ndk {
                abiFilter "armeabi-v7a"
            }
        }
        arm {
            ndk {
                abiFilter "armeabi"
            }
        }
        fat
    }
}

Synchronisieren Sie anschließend Ihr Projekt mit den Gradle-Dateien:

Sie können diese neuen Flavors ab nun nutzen, indem Sie die gewünschten Build-Varianten wählen:

Jede dieser Varianten liefert Ihnen ein APK für die angegebene Architektur:

app-x86-release-unsigned.apk

In fat(Release|Debug) werden auch weiterhin alle Bibliotheken enthalten sein, etwa das Standardpaket, das zu Beginn dieses Blog-Beitrags angesprochen wurde.

Aber lesen Sie weiter! Diese Architektur-abhängigen APKs sind bei der Entwicklung nützlich, aber wenn Sie mehrere APKs in den Google Play Store* hochladen möchten, müssen Sie für jedes einzelne einen eigenen versionCode angeben. Allerdings geht dies mit dem neuesten Android-Build-System ganz einfach!

Für ABI-abhängige APKs automatisch unterschiedliche Versionscodes festlegen

Die Eigenschaft android.defaultConfig.versionCode enthält den versionCode Ihrer App. Die Standardeinstellung ist -1. Wenn Sie diese Einstellung nicht ändern, wird der in Ihrer AndroidManifest.xml-Datei festgelegte versionCode verwendet.

Wenn Sie also die Möglichkeit nutzen wollen, Ihren versionCode dynamisch zu verändern, müssen Sie ihn zuerst in Ihrem build.gradle angeben:

android {
    ...
    defaultConfig{
        versionName "1.1.0"
        versionCode 110
    }
}

Allerdings es ist immer noch möglich, diese Variable nur innerhalb von AndroidManifest.xml festzulegen, wenn Sie sie vor der Änderung „manuell“ abrufen:

import java.util.regex.Pattern

android {
    ...
    defaultConfig{
        versionCode getVersionCodeFromManifest()
    }
    ...
}

def getVersionCodeFromManifest() {
    def manifestFile = file(android.sourceSets.main.manifest.srcFile)
    def pattern = Pattern.compile("versionCode=\"(\\d+)\"")
    def matcher = pattern.matcher(manifestFile.getText())
    matcher.find()
    return Integer.parseInt(matcher.group(1))
}

Nachdem dies erledigt ist, können Sie innerhalb Ihrer Flavors den versionCode mit Präfixen versehen:

android {
    ...
    productFlavors {
        x86 {
            versionCode Integer.parseInt("6" + defaultConfig.versionCode)
            ndk {
                abiFilter "x86"
            }
        }
        mips {
            versionCode Integer.parseInt("4" + defaultConfig.versionCode)
            ndk {
                abiFilter "mips"
            }
        }
        armv7 {
            versionCode Integer.parseInt("2" + defaultConfig.versionCode)
            ndk {
                abiFilter "armeabi-v7a"
            }
        }
        arm {
            versionCode Integer.parseInt("1" + defaultConfig.versionCode)
            ndk {
                abiFilter "armeabi"
            }
        }
        fat
    }
}

Hier habe ich das Präfix 6 für x86, 4 für mips, 2 für ARMv7 und 1 für ARMv5 verwendet. Wenn Sie sich fragen Warum?, lesen Sie den Abschnitt, den ich schon zuvor über Architektur-abhängige APKs im Play Store [in Englisch] geschrieben habe.

ndk-build von Android Studio (nicht) aufrufen

Wenn sich ein jni/-Verzeichnis in Ihrer Projektquelle befindet, wird das Build-System automatisch versuchen, ndk-build aufzurufen. Ab 0.7.3 funktioniert diese Integration nur auf Systemen, die mit Unix kompatibel sind (cf bug 63896). Unter Windows* sollten Sie sie deaktivieren, damit Sie ndk-build.cmd selbst aufrufen können. Die Einstellung hierfür können Sie in build.gradle vornehmen. Dies wurde berichtigt. Die aktuelle Implementierung ignoriert Ihre Android.mk-Makefiles und erstellt unmittelbar eine neue Datei. Während dies für einfache Projekte äußerst praktisch ist (man braucht keine *.mk-Dateien mehr!), kann es lästig sein, wenn Sie alle von Makefiles bereitgestellten Funktionen benötigen. In build.gradle lässt sich dieses Verhalten deaktivieren:

android{
    ...
    sourceSets.main.jni.srcDirs = [] //disable automatic ndk-build call
}

Wenn Sie das direkt generierte Makefile verwenden möchten, können Sie es zuerst konfigurieren, indem Sie die ndk.moduleName-Eigenschaft folgendermaßen festlegen:

android {
 ...
 defaultConfig {
        ndk {
            moduleName "hello-jni"
        }
    }
}

Zudem können Sie diese weiteren ndk-Eigenschaften festlegen:

  • cFlags
  • ldLibs
  • stl (d. h.: gnustl_shared, stlport_static…)
  • abiFilters (d. h.: „x86“, „armeabi-v7a“)

Sie können auch android.buildTypes.debug.jniDebugBuild auf true setzen, damit beim Erstellen eines Debug-APK NDK_DEBUG=1 an ndk-build übergeben wird. 
Wenn Sie RenderScript aus dem NDK verwenden, müssen Sie auch die Eigenschaft defaultConfig.renderscriptNdkMode auf true setzen. Sollten Sie sich auf automatisch generierte Makefiles stützen, können Sie beim Erstellen von APKs für mehrere Architekturen nicht einfach verschiedene cFlags für die jeweiligen Ziel-Architekturen festlegen. Wenn Sie daher ausschließlich Gradle verwenden wollen, empfehle ich, mithilfe von Flavors verschiedene APKs pro Architektur zu genieren (wie weiter oben in diesem Beitrag beschrieben):

  ...
  productFlavors {
    x86 {
        versionCode Integer.parseInt("6" + defaultConfig.versionCode)
        ndk {
            cFlags cFlags + " -mtune=atom -mssse3 -mfpmath=sse"
            abiFilter "x86"
        }
    }
    ...

Meine .gradle-Beispieldatei

Nach all diesen Erläuterungen werde ich Ihnen nun die build.gradle-Datei zeigen, dich ich zurzeit verwende. Sie hat einen Flavor pro unterstütze ABI und da sie keine ndk-build-Integration verwendet, funktioniert sie auch in der Windows*-Umgebung. Außerdem erfordert sie keine Anpassung der Standardspeicherorte für die Quellen und Bibliotheken (Quellen in jni/, Bibliotheken in libs/) oder der Inhalte meiner *.mk-Dateien:

import java.util.regex.Pattern

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.7.3'
    }
}

apply plugin: 'android'

android {
    compileSdkVersion 19
    buildToolsVersion "19.0.1"

    defaultConfig{
        versionCode getVersionCodeFromManifest()
    }

    sourceSets.main {
        jniLibs.srcDir 'src/main/libs'
        jni.srcDirs = [] //disable automatic ndk-build call
    }

    productFlavors {
        x86 {
            versionCode Integer.parseInt("6" + defaultConfig.versionCode)
            ndk {
                abiFilter "x86"
            }
        }
        mips {
            versionCode Integer.parseInt("4" + defaultConfig.versionCode)
            ndk {
                abiFilter "mips"
            }
        }
        armv7 {
            versionCode Integer.parseInt("2" + defaultConfig.versionCode)
            ndk {
                abiFilter "armeabi-v7a"
            }
        }
        arm {
            versionCode Integer.parseInt("1" + defaultConfig.versionCode)
            ndk {
                abiFilter "armeabi"
            }
        }
        fat
    }
}

def getVersionCodeFromManifest() {
    def manifestFile = file(android.sourceSets.main.manifest.srcFile)
    def pattern = Pattern.compile("versionCode=\"(\\d+)\"")
    def matcher = pattern.matcher(manifestFile.getText())
    matcher.find()
    return Integer.parseInt(matcher.group(1))
}

Problembehandlung 
„Cannot run program ndk-build“ 
Sie erhalten den folgenden Fehler:

java.io.IOException: Cannot run program "C:\Android\ndk\ndk-build": CreateProcess error=193, %1 is not a valid Win32 application

Dies bedeutet, dass die Tools-NDK-Integration ndk-build aufruft, allerdings funktioniert dies noch nicht unter Windows* (ndk-build ist ein Unix-Shell-Skript; stattdessen sollte ndk-build.cmd aufgerufen werden). Dies lässt sich deaktivieren, indem Sie für jni srcDirs einen leeren Wert angeben:

sourceSets.main.jni.srcDirs = []

„NDK not configured“ 
Sie erhalten den folgenden Fehler:

Execution failed for task ':app:compileX86ReleaseNdk'.
> NDK not configured

Dies bedeutet, dass die Tools das NDK-Verzeichnis nicht gefunden haben. Sie können diesen Fehler auf zwei Arten beheben: Legen Sie die ANDROID_NDK_HOME-Variablenumgebung auf Ihr NDK-Verzeichnis fest und löschen Sie local.properties, oder legen Sie das Verzeichnis manuell in local.properties fest:

ndk.dir=C\:\\Android\\ndk

Andere Probleme 
In der offiziellen adt-dev-Google-Gruppe finden Sie ebenfalls nützliche Informationen: https://groups.google.com/forum/#!forum/adt-dev

Weitere Informationen zur NDK-Integration 
Die beste Quelle für weiterführende Informationen ist die offizielle Projektseite: http://tools.android.com/tech-docs/new-build-system 
Die Release-Hinweise enthalten nützliche Informationen zu den Neuerungen und Änderungen. Am Ende der Seite finden Sie zudem das neueste „gradle-samples-XXX.zip“-Archiv mit Beispielprojekten zur NDK-Integration.

Dieser Beitrag ist eine Übersetzung des Artikels Android Studio*, Gradle* und NDK-Integration, der zuerst auf der privaten Webseite des Intel Mitarbeiters Xavier Hallade veröffentlicht wurde: http://ph0b.com/android-studio-gradle-and-ndk-integration/#more-71

Einzelheiten zur Compiler-Optimierung finden Sie in unserem Optimierungshinweis.
Kategorien: