Интеграция Android Studio, Gradle и NDK

от автора

В свете недавних изменений (начиная с релиза 0.7.3 от 27 декабря 2013) новая система сборки под Android становится очень интересной в том числе и для тех, кто использует NDK. Теперь стало действительно просто интегрировать нативные библиотеки в вашу сборку и генерировать APK для различных архитектур, корректно обращаясь с кодами версий.

Интегрируем .so файлы в APK

Если вы используете Android Studio, то для интеграции нативных библиотек в приложение раньше было необходимо применение различных сложных способов, включая maven и пакеты .aar/.jar … Хорошая новость состоит в том, что теперь этого уже не требуется.

Вам только требуется положить .so библиотеки в каталог jniLibs в поддиректории, названные соответственно каждой поддерживаемой ABI (x86, mips, armeabi-v7a, armeabi) – и всё! Теперь все файлы .so будут интегрированы в APK во время сборки:

Если название папки jniLibs вас не устраивает, вы можете задать другое расположение в build.gradle:

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

Строим один APK на архитектуру и добиваемся успеха!

Построить один APK на архитектуру очень просто с использованием свойства abiFilter.
По умолчанию ndk.abiFilter(s) имеет значение all. Это свойство оказывает влияние на интеграцию .so файлов, а также на обращения к ndk-build (мы поговорим об этом в конце поста).
Давайте внесем некоторые архитектурные особенности (конфигурации) в build.gradle:

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

А затем синхронизируем проект с файлами gradle:

Теперь вы можете наслаждаться новыми возможностями, выбирая желаемые варианты сборки:

Каждый из этих вариантов даст вам APK для выбранной архитектуры:

Полный (Release|Debug) APK будет все еще содержать все библиотеки, как и стандартный пакет, упомянутый в начале этого поста.

Но не прекращайте читать на этом месте! Архитектурно-зависимые APK удобны при разработке, но если вы хотите залить несколько из них в Google Play Store, вам необходимо установить различный versionCode для каждого. Сделать это с помощью новейшей системы сборки очень просто.

Автоматически устанавливаем различные коды версий для ABI-зависимых APK

Свойство android.defaultConfig.versionCode отвечает за versionCode для вашего приложения. По умолчанию оно установлено в -1 и, если вы не измените это значение, будет использован versionCode, указанный в файле AndroidManifest.xml.
Поскольку мы хотим динамически изменять наш versionCode, сначала необходимо указать его внутри build.gradle:

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

Однако все еще возможно хранить эту переменную в AndroidManifest.xml, если вы получаете ее «вручную» перед изменением:

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

Теперь вы можете использовать versionCode с различными модификаторами:

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

Здесь мы поставили префикс 6 для x86, 4 для mips, 2 для ARMv7 и 1 для ARMv5.

Работа с ndk в Android Studio

Если в исходниках проекта есть папка jni, система сборки попытается вызвать ndk-build автоматически.
В текущей реализации ваши Android.mk мейкфайлы игнорируются, вместо них создается новый на лету. Это действительно удобно для небольших проектов (вам больше вообще не требуются .mk файлы!), но в больших может раздражать, если вам требуются все возможности, предоставляемые мейкфайлами. Существует возможность отключить это свойство в build.gradle:

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

Если вы хотите использовать генерируемый на лету мейкфайл, вы можете настроить его изначально, установив свойство ndk.moduleName, например, так:

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

Вы также можете установить другие свойства ndk:

  • cFlags,
  • ldLibs,
  • stl (т.е.: gnustl_shared, stlport_static …),
  • abiFilters (т.е.: «x86», «armeabi-v7a»).

Генерация отладочного APK достигается заданием значения true для свойства android.buildTypes.debug.jniDebugBuild; в этом случае ndk-build будет передано NDK_DEBUG=1.
Если вы используете RenderScript из NDK, вам потребуется установить значение true для свойства defaultConfig.renderscriptNdkMode.
Если вы доверяете авто-генерируемым мейкфайлам, то можете задавать различные cFlags в зависимости от конечной архитектуры, когда вы собираете многоархитектурные APK. Так что если вы хотите полностью довериться gradle, мы рекомендуем генерировать различные APK для архитектур, используя ранее описанные модификаторы конфигурации:

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

Мой пример файла .gradle

Соединяя все вместе, привожу файл build.gradle, который сам сейчас использую. В нем нет модификаторов для различных поддерживаемых ABI, он не использует интеграцию с ndk-build, поэтому работает в Windows окружении и не требует ни изменения обычных мест расположения исходников и библиотек, ни содержимого моих .mk файлов.

import java.util.regex.Pattern  buildscript {     repositories {         mavenCentral()     }     dependencies {         classpath 'com.android.tools.build:gradle:0.9.0'     } }  apply plugin: 'android'  android {     compileSdkVersion 19     buildToolsVersion "19.0.3"      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)) } 

Устранение неисправностей

NDK не сконфигурирован

Если вы получаете ошибку:

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

Это означает, что инструменты не найдены в каталоге NDK. Имеется два выхода: установить переменную ANDROID_NDK_HOME в соответствии с вашим каталогом NDK и удалить local.properties или же установить ее вручную внутри local.properties:

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

Не существует правила для создания цели

Если вы получаете ошибку:

make.exe: *** No rule to make target ...\src\main\jni 

Ее причиной может быть имеющаяся ошибка в NDK для Windows, когда имеется только один исходный файл для компилирования. Добавьте еще один пустой файл – и все заработает.

Прочие вопросы

Вы, возможно, сможете найти ответы на интересующие вас вопросы в google группе adt-dev.

Получение информации по интеграции NDK

Лучшее место для нахождения более подробной информации – официальная страница проекта.
Посмотрите на список изменений и, если пролистаете его целиком, найдете примеры проектов, связанных с интеграцией NDK, внутри самых свежих архивов gradle-samples-XXX.zip.

На видео ниже показано, как настроить проект с исходниками NDK из Android Studio.

ссылка на оригинал статьи http://habrahabr.ru/company/intel/blog/216353/