В свете недавних изменений (начиная с релиза 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 ...srcmainjni
Ее причиной может быть имеющаяся ошибка в NDK для Windows, когда имеется только один исходный файл для компилирования. Добавьте еще один пустой файл – и все заработает.
Прочие вопросы
Вы, возможно, сможете найти ответы на интересующие вас вопросы в google группе adt-dev.
Получение информации по интеграции NDK
Лучшее место для нахождения более подробной информации – официальная страница проекта.
Посмотрите на список изменений и, если пролистаете его целиком, найдете примеры проектов, связанных с интеграцией NDK, внутри самых свежих архивов gradle-samples-XXX.zip.
На видео ниже показано, как настроить проект с исходниками NDK из Android Studio.
Автор: saul