Pull to refresh
0

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

Reading time 5 min
Views 46K
Original author: Xavier Hallade
В свете недавних изменений (начиная с релиза 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.
Tags:
Hubs:
+27
Comments 5
Comments Comments 5

Articles

Information

Website
www.intel.ru
Registered
Founded
Employees
5,001–10,000 employees
Location
США
Representative
Анастасия Казантаева