- PVSM.RU - https://www.pvsm.ru -

Android JNI + Intelij Idea + Gradle. Полная автоматизация процесса

Доброго времени суток!
Данный пост является небольшим руководством, по автоматизации компиляции нативного кода в среде Intellij Idea с использованием Gradle. Gradle предоставляет достаточно большой функционал для автоамтизации сборки проектов. Но даже подключение нативных библиотек к Android проекту требует дополнительных усилий со стороны разработчика.

Предыстория

Недавно я сменил место работы и устроился работать в компанию, занимающуюся разработкой собственного мобильного программного обеспечения. Мы с моими новыми коллегами по работе решили перейти с Eclipse (на котором до этого велась вся разработка) на Intellij Idea, и в добавок с Ant на Gradle. У нас достаточно большой проект, с приличным количеством кода, в том числе с использованием нативного C и C++ кода, как самописного так и уже готовых библиотек.

Тех, кто занимается разработкой Android проектов с использованием Android NDK в среде Intellij Idea + Gradle прошу под кат.

На скорую руку

Что касается java кода мы достаточно легко перенесли весь процесс разработки на новую IDE и систему сборки, не буду углубляться в данный процесс. С переносом нативного кода было все гораздо сложнее.

Так как нам не дали достаточно времени на поиск подходящего решения сразу, поэтому мы просто собрали все модули нативного проекта для необходимых нам платформ, положили в папку с исходниками нашего проекта и воспользовались быстро найденным решением для автоматического включения их в наш apk файл, данный подход описан в этой статье [1].

Поиск подходящего решения

На самом деле первый подход какое-то время нас абсолютно устраивал. Библиотеки были давно протестированы, и нам не приходилось часто вносить изменения в нативный код, до недавнего времени, когда нам понадобилось дописать еще один модуль. Тогда мы стали искать решение как компилировать весь наш исходный код, включая нативный.

Выяснилось что Gradle игнорирует Android.mk файлы и создает свои. Для этого он предоставляет большой функционал по передаче различных флагов и свойств ndk. Об это хорошо написано в этой статье [2]. Но нам больше нравилось использовать возможности по компиляции, с использованием *.mk файлов.

По этому мы вспомнили что Gradle предоставляет большой функционал для собрки и попробовали, напрямую вызвать ndk-build скрипт, который предоставляет Android NDK.

Для того чтобы это происходило автоматически, была написана отдельная Gradle-задача и добавлена к зависимостям задачи по автоматической упаковке нативных библитек. Вот вырезка из build.gralde файла нашего модуля:

task('compileNative') {
    exec {
        executable = 'path/to/ndk/ndk-build'
        args = ["NDK_PROJECT_PATH=src/main"]
    }
}

task nativeLibsToJar(type: Zip, description: 'create a jar archive of the native libs', dependsOn: 'compileNative') {
    destinationDir file("$buildDir/native-libs")
    baseName 'native-libs'
    extension 'jar'
    from fileTree(dir: 'jniLibs', include: '**/*.so')
    into 'lib/'
}

tasks.withType(JavaCompile) {
    compileTask -> compileTask.dependsOn(nativeLibsToJar)
}

В принципе данного кода уже достаточно чтобы компилировать нативные исходники в автоматическом режиме и подключать их к проекту. Но мы работаем в команде и не хорошо если каждый будет исзменять под себя основной файл сборки, потому что 'path/to/ndk/' у всех будет скорее всего свой. Поэтому, было решено вынести путь до NDK в файл локальных настроек сборки проекта.

# Настройка локального расположения NDK 
ndk.dir=path/to/ndk

# Настройка локального расположения SDK
sdk.dir=path/to/sdk

Файл local.properties должен находится в корне проекта. Если вы добавляете данный файл, то необходимо будет указать не только директорию NDK, но и директорию SDK, иначе Gradle выдаст соответствующее предупреждение, и откажется собирать ваш проект.

Теперь меняем нашу Gradle-задачу, добавляя использование локального пути до NDK.

task('compileNative') {
    def $ndkProjectRootFolder = 'src/main'
    def $ndkDirPropertyName = 'ndk.dir' 

    // переменная для хранения настроек
    Properties properties = new Properties()
    // загружаем файл с настройками проекта
    properties.load(project.rootProject.file('local.properties').newDataInputStream())
    // получаем путь до корневой директории ndk
    def $ndkDir = properties.getProperty($ndkDirPropertyName)

    // если не установлен то посоветуем это сделать в локальных настройках
    if( $ndkDir == null)
        throw new RuntimeException("Property 'ndk.dir' not found. Please specify it. n" +
                " It must be something like this in your local.properties file n" +
                " ndk.dir=path/to/your/ndk")

    // вызываем на выполнение скрипт для компиляции native исходников
    exec {
        executable = $ndkDir + '/ndk-build' 
        args = ["NDK_PROJECT_PATH=" + $ndkProjectRootFolder]
    }

}

Gradle позволяет определять переменные и выкидывать исключения в процессе сборки, по этому воспользуемся данным функционалом. Если в локальных настройках, разработчик не указал путь до Android NDK то напомним ему это сделать.

И на последок надо вспомнить что некоторые разработчики сидят на Windows.

task('compileNative') {
    def $ndkBuildScript = //путь то файла ndk-build (linux) / ndk-build.cmd (windows) относительно корня ndk
            System.properties['os.name'].toLowerCase().contains('windows') ?
                    'ndk-build.cmd' :
                    'ndk-build'
    def $ndkProjectRootFolder = 'src/main'
    def $ndkDirPropertyName = 'ndk.dir' 

    // переменная для хранения настроек
    Properties properties = new Properties()
    // загружаем файл с настройками проекта
    properties.load(project.rootProject.file('local.properties').newDataInputStream())
    // получаем путь до корневой директории ndk
    def $ndkDir = properties.getProperty($ndkDirPropertyName)

    // если не установлен то посоветуем это сделать в локальных настройках
    if( $ndkDir == null)
        throw new RuntimeException("Property 'ndk.dir' not found. Please specify it. n" +
                " It must be something like this in your local.properties file n" +
                " ndk.dir=path/to/your/ndk")

    // вызываем на выполнение скрипт для компиляции native исходников
    exec {
        executable = $ndkDir + '/' + $ndkBuildScript
        args = ["NDK_PROJECT_PATH=" + $ndkProjectRootFolder]
    }

}

Результат нашей работы — это автоматическое выполнения компиляции нативного кода с использованием всех преимуществ Android.mk файлов и компановки его в apk файл. Ну и как плюс теперь не надо хранить скомпилированные библиотеки в репозитории.

Надеюсь данный подход будет полезен.

Автор: busylee

Источник [3]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/android-development/63311

Ссылки в тексте:

[1] этой статье: http://habrahabr.ru/post/193122/

[2] этой статье: http://habrahabr.ru/company/intel/blog/216353/

[3] Источник: http://habrahabr.ru/post/227541/