Jak skonfigurować NDK z Androidem Gradle plugin 0.7

Nowa wtyczka Android gradle (0.7) wydaje się Zawierać Nowe wsparcie dla NDK, ale w dokumentacji nie ma o niej zbyt wiele wzmianki (jedyne odniesienie znalazłem to test o nazwie ndkSanAngeles).

Wygląda na to, że gradle szuka NDK, które włączyłem do swojej ścieżki. Jednak budowa projektu nie powiodła się z

  • co poszło nie tak: Wykonanie nie powiodło się dla zadania ':ogltests:compileDefaultFlavorDebugNdk'. NDK nie skonfigurowany

Jak skonfigurować NDK w gradle?

Moja obecna Budowa.gradle wygląda tak:
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'
        }
    }
}
Dzięki.
Author: user1906, 2013-12-19

6 answers

Przeglądając kod wtyczki gradle, znalazłem następujące, które pomogły mi używać zarówno NDK, jak i prebuilt natywnych bibliotek:

Po prostu Link w Prebuilt natywnych bibliotekach , wystarczy dodać sekcję ndk do zadania. Na przykład dodałem go poniżej w productFlavors. AbiFilter to nazwa folderu, w którym przechowywane są biblioteki. abiFilters oznacza, że obie biblioteki z rozdzielonej przecinkami listy zostaną dodane do końcowego pliku APK (więc teoretycznie możesz mieć "armeabi", " armeabi-v7a", "x86 " i" mips " w jednym APK, A O / S wybierze obsługiwaną architekturę lib podczas instalacji):

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

W tym przykładzie kompilator arm utworzy APK z bibliotekami arm V5 i V7A, a kompilator x86 utworzy APK z bibliotekami x86. Spowoduje to wyszukanie natywnych bibliotek w katalogu projektu jniLibs. Katalog jniLibs powinien być strukturą jak stary katalog jni, tzn.:

[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

Następnie można go załadować w Javie jako następuje:

static
{
    loadLibrary("myNative");
}
Powiedzmy, że jedna rodzima biblioteka zależy od drugiej. Musisz (jeśli ustawiasz swoje min API NA API 17 lub niższe) najpierw załadować zależne biblioteki:
static
{
    loadLibrary("myDependency");
    loadLibrary("myNative");
}

Możesz również umieścić sekcję NDK {} w defaultConfig lub buildType(np. debug, release lub cokolwiek innego, czego możesz użyć). Na przykład:

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

Przez prebuilt mam na myśli biblioteki stron trzecich, które pobrałeś lub bibliotekę zbudowaną przy użyciu NDK toolchain lub własnego ARM toolchain (nie NDK-sam skrypt budowy).

W API 18 Naprawiono długotrwały problem architektoniczny, który uniemożliwiał natywnemu loaderowi lib "automatyczne" ładowanie zależności, ponieważ nie wiedział on o katalogu lib aplikacji (względy bezpieczeństwa, itp.). W API 18 i nowszych, jeśli myNative zależy od myDependency powyżej, możesz po prostu wywołać loadLibrary ("myNative"), a system operacyjny obsłuży Ładowanie myDependency. Nie polegaj jednak na tym, dopóki nie wejdą na rynek urządzenia z interfejsem API 17 a poniżej jest w niskiej liczbie akceptowalnej dla Ciebie.


Aby jawnie zbudować Biblioteki NDK ze źródła w bieżącej wersji Android Studio, możesz wykonać następujące czynności:

Ustaw NDK.wartość dir w Twoim lokalnym.właściwości do wskazywania domu NDK, jak wspomniano wcześniej. Czy ktoś wie czy można używać ENV vars bezpośrednio w lokalnym.nieruchomości? :)

W Twojej budowie.gradle plik, dodać coś takiego do zadania (ponownie, może być defaultConfig, debug, release, a productFlavor, etc):
ndk {
    moduleName "myNDKModule"
    stl "stlport_shared"
    ldLibs "log", "z", "m"
    cFlags "-I/some/include/path"
}

Jest to podstawowa struktura z aktualnie obsługiwanymi typami (moduleName, stl, ldLibs i cFlags). Szukałem i nie znalazłem więcej niż to. Wydaje mi się, że jest problem z ldLibs, ponieważ automatycznie dodaje "-l" do przodu każdego pola powyżej. Można to jednak oszukać (musiałem) mówiąc: ldLibs "log - Lz-lm-Wl,-whole-archive-l/path/to/someOtherLib-WL,-no-whole-archive"

W tej linii, jesteś tylko tagowanie do końca pierwszego parametru dodającego parametry, które nie zaczynają się od-l, więc na razie możesz sobie poradzić. W powyższym przypadku linkuję całą bibliotekę statyczną do mojego modułu NDK do użytku z poziomu Javy. Poprosiłem programistę google o dodanie dodatkowych funkcji, aby umożliwić To lub nawet możliwość scalania własnych Android.mk plik do procesu budowania NDK, ale ponieważ jest to wszystko nowe, może to trochę potrwać.

Obecnie, cokolwiek umieścisz w build.gradle usuwa katalog temp build i odtwarza go za każdym razem, więc jeśli nie chcesz pobrać i zmodyfikować kodu źródłowego wtyczki Gradle android (co byłoby zabawne), są pewne "make due"'s jak to wymagane, aby twoje rzeczy skopiowane do kompilacji. Skrypt android gradle, który zapewnia obsługę ndk w istocie generuje Android.mk plik i buduje za pomocą systemu NDK w katalogu tymczasowym.

Przesunięty na sekundę. nazwa modułu powinna pasować do pliku C lub cpp w Twoim projekcie w katalogu jni like:

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

Wartość stl powinna być ustawiona na wartość "stlport_shared" lub "stlport_static", jeśli chcesz używać bibliotek stlport dla C++. Możesz pominąć stl, jeśli nie potrzebujesz rozszerzonej obsługi C++. Pamiętaj, że Android domyślnie zapewnia bardzo podstawową obsługę C++. W przypadku innych obsługiwanych bibliotek C++ zapoznaj się z wytycznymi dokumentacji NDK w pobranym pliku NDK. Zauważ, że ustawiając tutaj na stlport_shared, gradle kopiuje libstlport_shared.so lib z twojego NDK sources/CXX-katalog stl/stlport / libs do katalogów lib twojego APK. Obsługuje również ścieżkę include w kompilatorze (technicznie gradle nie robi tego wszystkiego, ale Android NDK build system). Więc nie umieszczaj własnej kopii stlport w katalogu jniLibs.

Wreszcie, myślę, że cFlags jest dość oczywiste.

Nie można ustawić ANDROID_NDK_HOME na Mac OSX( patrz poniżej), ale z niektórych badań zrobiłem wydaje się, że może to nadal działa na innych OSs. Zostanie usunięty chociaż.

Chciałem skomentować, ale nie mam jeszcze reputacji. Dennis, zmienne środowiskowe są całkowicie ignorowane, a nie tylko nadpisywane. W rzeczywistości nie otrzymujesz żadnej ze swoich zmiennych środowiskowych. Z tego co wiem, Android Studio IDE tworzy własne środowisko z kilkoma specyficznymi zmiennymi środowiskowymi (check System.getenv () i wydrukować go ze skryptu gradle).

Napisałem to jako błąd, ponieważ używanie ENV vars buduje fine z linii cmd:
https://code.google.com/p/android/issues/detail?id=65213

Ale jak widzisz, Google zdecydowało, że nie chcą, aby zmienne środowiskowe były używane przez IDE w ogóle; nadal jestem na ogrodzeniu tej decyzji. To sprawia, że moje życie jest bolesne, gdy muszę aktualizować lokalne.właściwości wskazujące na ścieżki bezwzględne, które można załadować w moich skryptach gradle, które nadal Nie wiem, jak to zrobić (ale nie wyglądałem tak ciężko). Oznacza to, że albo zmuszam członków mojego zespołu do korzystania z ta sama ścieżka co ja, pobaw się linkami, spraw, aby wszystkie wpisywały je za każdym razem, gdy wyciągają repo, lub dodaj skrypt automatyzacji. Uważam, że jest to zła decyzja, która będzie kosztować czas dla programistów, którzy polegają na Var-ach env, które mogą być małe na poziomie mikro, ale ogromne na poziomie makro.

Groundloop, wierzę, że IDE zostanie wkrótce zaktualizowane o możliwość dodania ścieżki do folderu NDK do projektu, a to automatycznie wygeneruje lokalny.Plik Właściwości z tego (przynajmniej nie miałoby to sensu gdyby o tym nie pomyśleli).

Aby uzyskać bardziej szczegółowe przykłady z Google, Oto najnowsze przykłady (Szukaj jni lub ndk): https://docs.google.com/viewer?a=v&pid=sites&srcid=YW5kcm9pZC5jb218dG9vbHN8Z3g6NDYzNTVjMjNmM2YwMjhhNA


międzyplatformowe pliki APK fat przy użyciu NDK:

Wreszcie, jest wada korzystania z gradle i nie jest w stanie zapewnić własne Android.mk plik tak, aby można było linkować tylko w bibliotekach natywnych innych firm z pojedyncza Architektura do twojego NDK. Zauważ, że powiedziałem "link in". Możesz zbudować Moduły NDK (Nazwa modułu powyżej) w kilku architekturach za pomocą polecenia "abiFilters" i zostaną one umieszczone w Twojej aplikacji tak, że ten sam APK może być używany na wielu architekturach. Jeśli potrzebujesz połączyć się w swoich bibliotekach innych firm lub nawet mieć różne wartości dla cFlags w zależności od architektury, nie ma prostego sposobu.

próbowałem następujących i wydawało się działać na początku, ale potem znalazłem po prostu budowało NDK poprzez dołączanie wszystkiego z dwóch sekcji ndk (lub coś w tym stylu, w jakiś sposób zbudowało wiele bibliotek architektury): {]}

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
            }
        }
    }
}

Po wielu żalach próbujących stworzyć fatalny plik binarny w czystej posiadłości z gradle i natywnymi bibliotekami stron trzecich, w końcu doszedłem do wniosku, że wbudowana obsługa Wielu architektur Google Play dla Apk Jest naprawdę najlepszą drogą, więc twórz Indywidualne apk dla każdej architektury.

Więc stworzyłem wiele buildtype, żadnych smaków produktu, i dodałem następujący kod, aby wygenerować kod wersji dla każdego typu.

// 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
    }
}

Teraz wszystko co musisz zrobić to ustawić wartość versionCode w obiekcie defaultConfig, tak jak byś to zrobił normalnie, a to doda go do końca ciągu wersji specyficznej dla architektury, na podstawie typu build. Ciąg wersji pozostaje taki sam dla wszystkich kompilacji, ale zmienia kod, aby zapewnić kolejność pierwszeństwa od ARM aż do góry do X86_64. To trochę hakerskie lub mocno zakodowane, ale robi robotę. Zauważ, że zapewnia to do 999 wersji, więc jeśli potrzebujesz więcej, pomnóż powyższe liczby przez 10, Nie wiem, jaka jest maksymalna wartość, którą możesz umieścić w kodzie wersji.

W moim przypadku, mamy dość skomplikowany system budowania. Budujemy CPython dla 9 architektur, z których 3 to Android, a następnie budujemy kilka własnych bibliotek i łączymy je w jedną bibliotekę dla każdej architektury. My użyj narzędzi do budowania wiersza poleceń NDK, automake i python, aby zbudować wszystko, zamiast Android.mk pliki. Biblioteki końcowe są następnie połączone w jeden plik cpp interfejsu JNI (zwany powyżej myNativeCPPModule). Jedno kliknięcie przycisku i wszystko jest zbudowane na raz, bardzo ładne studio Android.

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-18 21:35:07

Znalazłem odpowiedź. Włączenie ndk.dir=path/to/ndk do pliku local.properties załatwiło sprawę.

Update: W najnowszych wersjach Android Studio możesz ustawić wartość bezpośrednio w strukturze projektu > lokalizacji 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/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-08 10:25:24

Możesz również ustawić zmienną środowiskową 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/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-24 11:43:42

Spędziłem dużo czasu na konfigurację NDK w build.gradle. Mam dobry blog

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-19 06:08:41

Jak już wcześniej skomentowałem, dodałem ndk.dir = in local.właściwości pomagają. Co ciekawe, znalazłem to miejscowe.właściwości nadpisują dowolną wartość ustawioną dla zmiennej środowiskowej ANDROID_NDK_HOME, , nawet jeśli nie masz ndk.katalog skonfigurowany w trybie lokalnym.właściwości . (przynajmniej z gradle android plugin v 0.7.3).

Jest to mylące, ponieważ Android Studio może nadpisać lokalne.właściwości, i nie wydaje się, aby zapewnić sposób konfiguracji 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/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-24 17:03:38

Android studio sugeruje włączenie ścieżki do NDK w lokalnym.właściwości

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-20 11:52:32