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 файл, данный подход описан в этой статье.

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


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

Выяснилось что Gradle игнорирует Android.mk файлы и создает свои. Для этого он предоставляет большой функционал по передаче различных флагов и свойств ndk. Об это хорошо написано в этой статье. Но нам больше нравилось использовать возможности по компиляции, с использованием *.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 файл. Ну и как плюс теперь не надо хранить скомпилированные библиотеки в репозитории.

Надеюсь данный подход будет полезен.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 6

    0
    Спасибо. Интересная статья!
      0
      А на чем пишите, правите нативный код если не секрет? Я имею ввиду IDE(какая?), блокнот.
        0
        Вопрос к автору, но например я разрабатываю с использованием Android NDK на C++, код пишу в Sublime Text, собираю Android Studio примерно как и автор.
          0
          На данный момент редактирование C++\ С кода является небольшой проблемой. Потому что Idea не поддерживает редактирование нативного кода для в Android проекте «из коробки», или мы не знаем как это сделать.

          Сейчас мы ищем, подходящее решение. Пока что редактируем прямо в Intellij Idea. Без подсветки и прочих радостей, то есть по факту, вы правы «блокнот».

          Есть плагин для удобного редактирования C++\ C кода (ссылка), но ввиду некоторых ограничений (только 32 битные платформы, нет поддержки 13 Idea), мы его не используем.

          На хабре был пост про то что AnvancedTools открыли исходники (github.com/nicity/CppTools) данного плагина, в которой автор предлагает совместно решить вопрос улучшения работы данного плагина.
          В данном репозитории есть коммит «Idea 13 compatibility fixes», что не может не радовать. Сам я никогда не писал плагины для Idea, и к сожалению пока нет времени с этим разобраться, думаю там не так все просто касательно этого плагина, но я бы с радостью поддержал инициативу.
            0
            Тот плагин имеет out-of-process парсинг, и сорс не открыт. Можно изменять — то что есть, но это на базовом уровне.

            Я могу его апнуть к последней Идеи, но я не думаю — что это чтото даст.
          0
          Второй шаг (который nativeLibsToJar) уже не нужен ведь, можно просто задать в sourceSet'е jniLibs.srcDirs = ['libs'].
          Да и проще, кажется, один раз ndk-build в пути прописать и использовать просто
          task ndkBuild(type: Exec) {
              if (Os.isFamily(Os.FAMILY_WINDOWS)) {
                  commandLine 'ndk-build.cmd'
              } else {
                  commandLine 'ndk-build'
              }
          }
          

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое