- Учимся работать с Android NDK или как использовать C код в java проектах
- Когда нужно использовать Android NDK?
- Шаг 1: Установка Android NDK и настройка среды разработки
- Шаг 2: Создание нового проекта
- Шаг 3: Добавляем C код в Android проект
- Шаг 4: Вызов нативного кода из Java
- Шаг 5: Создаем Make file для нативного кода
- Шаг 6: компиляция нативного кода
- Шаг 7: Запуск приложения
- Шаг 8: Как вернуть объект из нативной функции
- Замечания
- Заключение
- Использование NDK в Android Studio
Учимся работать с Android NDK или как использовать C код в java проектах
Android NDK представляет собой набор утилит, позволяющих Вам включать код, написанный на C и C++, в ваше приложение. Такой код называется нативным(native), поскольку он не может быть выполнен на виртуальной машине и компилируется непосредственно в машинный код требуемой процессорной архитектуры.
Данный урок относится к разряду продвинутых. Подразумевается, что читатель уже имеет некоторые навыки, в частности
- Вы умеете программировать на Java и C
- Вы умеете работать с командной строкой
- Вы знаете, как узнать версии Cygwin, awk и других инструментов, которыми нам придется пользоваться
- Вы умеете разрабатывать приложения для Android
- У Вас настроена среда разработки для Android (в момент написания автор использовал Android 2.2)
- Вы используете Eclipse или можете транслировать инструкции по работе с eclipse на свою IDE.
Если какой-либо из указанных пунктов вызывает у вас затруднения, не беда, вы все равно вполне можете усвоить предлагаемый материал, правда некоторые шаги будут казаться Вам довольно сложными. Вообще вопрос использование Android NDK часто вызывает затруднения даже у матерых разработчиков. Скорей всего вам придется приложить значительные усилия, прежде чем вы сможете настроить свою среду разработки и написать работающий проект.
Когда нужно использовать Android NDK?
Обычно разработчики решают использовать нативный код в двух случаях: они хотят увеличить производительность своего приложения, или у них есть готовый C/C++ проект, который требуется с минимальными затратами портировать на Android. Давайте не будем спешить и разберемся, когда же целесообразно использовать NDK, а когда этого не нужно делать.
Наиболее часто программистами высказывается мнение, что NDK стоит использовать, когда приложение сильно нагружает процессор. Существуют алгоритмы, позволяющие полностью загрузить процессор через DalvikVM, в этом случае использование нативного кода действительно с большой вероятностью позволит получить выигрыш в производительности. Однако, не нужно забывать, что использование JIT компилятора также позволяет повысить производительность java кода. Многие думают, что использование в приложении машинного кода автоматически означает увеличение скорости работы приложения. На самом деле это не так. Переключение с выполнения java кода на машинный код и обратно несет с собой накладные расходы, поэтому использовать NDK стоит, только если у вас выполняется какой-нибудь долгий сложный расчет, полностью написанный на C, и в java коде не предполагается частое дерганье нативных функций.
Другой причиной, которая может побудить Вас использовать NDK является необходимость портирования готового приложения. Вполне логично не переписывать уже проверенные и отлаженные куски кода на java, а использовать NDK. Этот подход также позволит Вам в дальнейшем без особых затрат вносить параллельно правки в исходное и портированное на android приложение. В частности, такой подход оправдан в отношении приложений, использующих OpenGL ES.
Шаг 1: Установка Android NDK и настройка среды разработки
Прежде всего, Вам необходимо скачатьAndroid NDK. Для установки и нормальной работы нам также понадобятся утилиты Cygwin 1.7 или старше, awk последней версии, а также GNU Make 3.81 или старше.
После того, как Вы скачали архив с NDK, распакуйте его в какую-нибудь папку. Можно распаковать этот архив туда же, где лежит Android SDK. Путь к этой папке необходимо прописать в системной переменной PATH. В Windows для этих целей лучше настроить конфигурацию Cygwin.
Шаг 2: Создание нового проекта
Создайте новый Android проект. Чтобы избежать проблем в будущем сохраните проект так, чтобы путь к нему не содержал в себе символов пробела. Для примера создайте проект, в качестве названия пакета укажите «com.mamlambo.sample.ndk1», а в качестве Activity — «AndroidNDK1SampleActivity».
В корне проекта создайте папку с названием «jni». Именно здесь будет содержаться файлы с нативным кодом. Если Вы знакомы с JNI, то вам будет приятно узнать, что Android NDK по сути представляет собой JNI с ограниченным набором заголовочных файлов для компиляции C кода.
Шаг 3: Добавляем C код в Android проект
Создайте в папке jni файл с именем native.c и добавьте в него следующий код
Созданная таким образом функция берет параметр String у java объекта, конвертирует его в C-string и записывает в LogCat. Зубодробительное имя функции выбрано не случайно, оно несет в себе важную информацию: сначала указывается название паттерна («Java»), затем идут название пакета, имя класса и название метода. Каждая часть имени отделяется знаком подчеркивания.
Первые два параметра у функции имеют особое значение. Первый параметр определяет JNI среду и часто используется со вспомогательными функциями. Второй параметр является объектом Java, частью которого является функция.
Шаг 4: Вызов нативного кода из Java
Давайте создадим в нашем проекте кнопку, при нажатии на которую будем вызывать следующий код:
Необходимо также объявить функцию helloLog в классе, где она вызывается. Сделать можно с помощью строки
Таким образом, мы сообщаем компилятору и линковщику, что реализацию этой функции стоит искать в папке с нативным кодом.
Наконец, нужно загрузить библиотеку, куда в конечном счете будет скомпилирован код. Добавьте следующую инициализацию в класс Activity.
System.loadLibrary() обеспечивает загрузку библиотеки по имени. Вы можете использовать любое название.
Шаг 5: Создаем Make file для нативного кода
Для компиляции нативного кода в папке jni должен находиться Make file с именем «Android.mk». Ниже приведен код этого фала для нашего примера, то есть когда функция находится в файле native.c и в качестве имени библиотеки указано ndk1
Шаг 6: компиляция нативного кода
После того, как Вы написали код и добавили make файл в папку jni можно приступать к компиляции. Для этого нужно в командной строке (Если вы работаете в windows, запустите Cygwin) запустить ndk-build из папки проекта. Утилита ndk-build входит в состав Android NDK. Если Вы все сделали правильно, то вы должны увидеть что-то вроде этого
Шаг 7: Запуск приложения
Теперь можно запустить проект, нажать на кнопку и посмотреть, как изменится LogCat.
Может произойти одна из двух вещей: 1) ваш проект может запуститься и работать, как Вы того ожидаете. В этом случае примите мои поздравления. 2) Возникнет ошибка, которая в LogCat отобразиться как «Could not execute method of activity.» Ничего страшного. Обычно Eclipse сконфигурирован так, что при запуске проекта автоматически происходит его перекомпиляция. В случае, если эта опция отключена, то нужно вручную заставить Eclips перекомпилировать проект. Для этого перед запуском нужно вызвать менюProject->Clean from the Eclipse toolbar.
Шаг 8: Как вернуть объект из нативной функции
Следующий пример демонстрирует возможность нативных функций возвращать объекты, например String. Добавьте код в файл native.c
С помощью команды malloc мы создали буфер, куда затем с помощью sprintf поместили строку. Чтобы функция возвращала корректный результат, мы использовали JNI helper функцию NewStringUTF(), которая фактически создает Java объект на основании C строки. После этого мы очистили память с помощью команды free().
Для успешной компиляции проекта необходимо в native.c подключить заголовочный файл stdio.h. В классе Activity нужно также объявить новую функцию:
после этого с функцией getString можно работать, например следующим образом:
Замечания
Android NDK для своей работы требует Android SDK 1.5. С помощью NDK можно получить доступ ко многим API, например к OpenGL ES.
Нативный код компилируется в машинный код, соответствующий архитектуре процессора, и поскольку на разных телефонах используются процессоры разной архитектуры, у Вас может возникнуть проблемы с переносимостью программы. По умолчанию NDK производит компиляцию для ARMv5TE. Когда Вы запускаете свое приложение на эмуляторе, а не на реальном устройстве, этот машинный код выполняется не напрямую, а через еще один эмулятор процессора.
Заключение
Ну вот собственно и все. Думаю теперь вам понятно, как работать с нативным кодом. Во многих случаях его применение оправдано, однако бездумное применеие нативного кода в своих проектах может оказаться губительным.
Источник
Использование NDK в Android Studio
В настоящее время среди Android-девелоперов большую популярность имеет среда разработки Android Studio, основанная на IntelliJ IDEA от JetBrains. Однако, при использовании данной IDE, могут возникнуть проблемы при разработке приложений, использующих нативный код, так как Android NDK рассчитан преимущественно на использование IDE Eclipse и ADT.
Цель данной статьи — подробное описание процесса создания Android-приложения, использующего NDK в Android Studio, в частности — предложение достаточно простой и эффективной конфигурации gradle (системы сборки пакетов, используемая в Android Studio), гарантирующей включение нативных библиотек в APK-файл. Также статья включает краткую инструкцию работы с NDK в IDE Eclipse и введение в нативную разработку, достаточное для написания первого приложения.
Данная статья предназначена преимущественно для начинающих разработчиков. Описанное решение не является единственным, но оно достаточно удобно, особенно для тех, кто работал с NDK в Eclipse. Если кому-то из читателей объяснение покажется слишком подробным, то в конце статьи есть краткое резюме, описывающее лишь алгоритм требуемых действий без комментариев к каждому шагу.
Так как существует множество статей, описывающих работу с NDK, я не буду использовать в качестве примера сложные библиотеки, а ограничусь лишь самым простым примером hello-jni. Исходный код данного примера можно найти в каталоге /samples/hello-jni
В среде Eclipse особых проблем с использованием NDK не возникало. Каталог проекта выглядит примерно так:
Рис.1 Главный каталог проекта для Eclipse
Нас интересуют каталоги jni и libs. Каталог jni содержит исходные коды на нативных языках (*.c; *.cpp), заголовочные файлы (*.h), makefiles (*.mk). Долго останавливаться на предназначении данных файлов не буду, так как этому посвящено немало материалов и статей. Упомяну лишь, что jni означает java native interface. Именно через этот интерфейс производится вызов нативных процедур из кода на java. Поэтому не забывайте подключать библиотеку в ваши c/c++ файлы и помните о правильном синтаксисе функций, которые будут вызываться через jni. Например, в моём случае приложение имеет package name:
evi.ntest
поэтому описание функции выглядит так:
где jstring — название типа данных c, соответствующего типу string в java, Java — в данном случае служебный префикс, показывающий язык, из которого будет вызвана функция, evi_ntest — имя пакета, который будет вызывать функцию, MainActivity — имя активити, из которой будет вызываться функция, stringFromJNI — название функции.
В java-коде описание данной функции выглядит гораздо проще:
Не забывайте также указывать используемые файлы кода в .mk файлах. Для начала можете воспользоваться .mk файлами из примеров NDK, модифицируя названия файлов, но в дальнейшем рекомендую изучить их структуру.
Каталог libs содержит готовые бинарные библиотеки для различных архитектур процессоров (по умолчанию — armeabi). Динамическая библиотека представляет собой файл с расширением .so, статическая — файл с расширением .a. Для получения данных библиотек требуется компиляция исходных кодов с помощью Android NDK. В Unix-системах (в моём случае — Mac OSX) для этого требуется в терминале ввести следующие строчки:
При этом NDK автоматически компилирует исходные коды из папки jni и помещает полученные библиотеки в libs/armeabi (также можно с помощью параметров командной строки задать компиляцию под x86, mips, arm v7-neon процессоры).
При использовании Windows потребуется воспользоваться дополнительными утилитами, возможно — плагинами для MS Visual Studio.
В любом случае, не имеет значения, каким именно способом получены готовые библиотеки, важен факт, что если в папке с проектом находится подкаталог libs, Eclipse при сборке автоматически включает его содержимое в APK-файл.
Так как данная статья посвящена лишь основам работы с NDK, то на этом введение в программирование на нативных языках под android я закончу. Для более подробного ознакомления с принципами нативной разработки рекомендую изучить примеры из каталога /samples/, а также читать статьи, в том числе и на русском языке. Пример хорошей статьи о Android NDK на русском языке, советую обратить внимание (написана не мной):
Перехожу к основному разделу статьи — настройке IDE Android Studio для работы с нативным кодом.
Среда Android Studio по умолчанию собирает APK с помощью gradle. Данный сборщик имеет широкие возможности кастомизации, но при стандартных настройках gradle не включает нативные библиотеки в APK-файл.
Рассмотрим частичную структуру проекта в Android Studio:
Рис.2 Путь к исходному коду проекта Android Studio.
При работе у меня возникло логичное желание разместить папку jni в каталоге src/main, так как именно там хранятся все остальные файлы с исходным кодом. Разумеется, читатель может размещать каталог jni там, где ему удобно. Главное — не забыть собрать бинарные библиотеки с помощью NDK (повторюсь, в UNIX системах для этого нужно в терминале перейти к каталогу, содержащему jni, затем вызвав исполняемый файл ndk-build, лежащий в папке с NDK, прописав полный путь к нему в этом же терминале, в MS Windows нужно использовать дополнительные утилиты). Проблема же заключается в том, что по умолчанию gradle не будет упаковывать в APK библиотеки.
Однако gradle несложно настроить на включение в сборку java-библиотек (файлов *.jar). Стоит заметить, что jar-файлы представляют собой zip-архивы, содержащие какие-либо ресурсы, а также объектный код. Таким образом, для включения бинарных библиотек *.so и *.a достаточно упаковать их в jar-файл.
Делается это так:
- Переименовываем папку libs, содержащую наши бинарные библиотеки в lib
- Сжимаем данную папку любым zip-архиватором
- Меняем расширение полученного файла на .jar
Полученную библиотеку можно подключить на этапе сборки проекта, при этом полученный APK-файл будет включать двоичные библиотеки, а приложение — вызывать процедуры, написанные на нативном коде.
Данный вопрос не раз обсуждался на различных англоязычных форумах, например Stack Overflow:
stackoverflow.com/questions/16667903/android-studio-gradle-and-ndk
Однако, данная информация достаточно краткая, разрозненная и требует от читателя определённых знаний синтаксиса gradle. Цель моей статьи – предоставить читателям подробное русскоязычное объяснение, доступное даже тем, кто только начал работать с Android Studio и gradle.
Давайте рассмотрим 2 способа упаковки библиотек: ручной и автоматический.
Ручной способ упаковки:
Данный способ весьма неудобен, однако имеет место на существование. Допустимый случай применения на практике: наличие готовой библиотеки и отсутствие необходимости её изменять. В таком случае, описанные ниже операции потребуется выполнить лишь один раз.
Откройте build.gradle, находящийся по адресу » / Project/ /», в моём случае:
Рис.3 Местонахождение конфигурируемого файла build.gradle.
Данный файл изначально выглядит приблизительно так:
В самом низу есть раздел dependencies. Туда следует добавить такую строчку:
compile fileTree(dir: ‘src/main/’, include: ‘*.jar’)
Рассмотрим эту команду: gradle в ходе компиляции будет вынужден включить дерево файлов (структуру файлов и папок, соответствующих заданной маске ), расположенное по адресу ‘src/main/’ (т.е. в том каталоге, где расположены исходные коды, а также созданный нами jar файл), при этом в качестве маски использован параметр ‘*.jar’, т.е. включатся будут все файлы с таким расширением. Обратите внимание, что в данном случае путь считается относительно месторасположения файла build.gradle.
В результате выполнения данной команды gradle распакует jar-файл и включит двоичные библиотеки в APK-файл.
Ознакомтесь с модифицированным файлом build.gradle, что бы не перепутать dependencies и buildscript.dependencies.
Читатель скорее всего подметит, что данный способ весьма неудобен при наличии необходимости частых изменений в нативном коде, так как после каждой перекомпиляции необходимо удалять старый jar-файл, переименовывать папку libs в lib, архивировать её, менять расширение архива. Поэтому воспользуемся мощью gradle и автоматизируем процесс.
Сборщик пакетов gradle позволяет создавать задания (функции), также в его возможности входит создание различных типов архивов, в том числе zip. Воспользуемся этим и добавим в build.gradle (расположение данного файла рассмотрено выше) такие строчки:
Данный отрывок кода можно добавить в любую часть файла, кроме существующих разделов, например в конец файла. Я поместил их перед разделом dependencies.
Этот код включает в себя task, который создает в папке build, находящийся по адресу » / Project/ /» подкаталог native-libs, в этой подпапке создается файл native-libs.jar, структура файла соответствует требуемой структуре java-библиотеки, содержащей бинарные библиотеки .so. Если Вы планируете использовать также статические библиотеки .a, то вместо строки:
from fileTree(dir: ‘src/main/libs’, include: ‘**/*.so’)
Вам следует использовать:
from fileTree(dir: ‘src/main/libs’, include: ‘**/*.*’)
Далее остается добавить в раздел dependencies строку:
compile fileTree(dir: «$buildDir/native-libs», include: ‘native-libs.jar’)
В ходе сборки эта команда включит содержимое созданной программно библиотеки native-libs.jar в APK-файл.
Пример build.gradle с данным кодом:
Обратите внимание, что у меня дирректории jni, libs расположены по адресу » / Project/ /src/main». Если в Вашем проекте эти папки лежат в другом месте, то Вам следует учесть это в формировании путей для всех команд.
Если всё сделано правильно, то Android Studio в ходе сборки проекта автоматически создаст в каталоге build правильную библиотеку и включит её в готовую программу. Таким образом, после каждой перекомпиляции нативного кода отпадает необходимость совершения каких-либо дополнительных действий и настроек, gradle сделает всё сам.
Теперь, как и было обещано в начале статьи, краткое резюме, описывающее лишь полный алгоритм без лишних комментариев:
Краткое резюме
- Открываем папку » / Project/ /src/main» и создаём там подпапку jni.
- Открываем файл » / Project/ /build.gradle», модифицируем раздел dependencies, после чего добавляем туда код:
Для включения также статических библиотек *.a (при их наличии) меняем строку
from fileTree(dir: ‘src/main/libs’, include: ‘**/*.so’)
from fileTree(dir: ‘src/main/libs’, include: ‘**/*.*’)
Важно!
Данная инструкция предназначена для операционных систем Unix (в моём случае — MacOSX). Для операционной системы MS Windows пункты 4 и 5 не актуальны, так как для компиляции нативных библиотек требуются дополнительные утилиты. Также, скорее всего, будет целесообразным изменить пути хранения библиотек на более удобные и учесть это в скрипте сборки.
На этом я завершаю статью и откланиваюсь. Надеюсь, кому-то данная статья сэкономит время.
Удачного Вам нативного программирования, главное — каждый раз не забывайте себя спрашивать, стоит ли использовать нативный код. Вполне могут быть java-аналоги, использование которых проще, а в большинстве случаев – лучше, так как сокращается время разработки, улучшается понимаемость кода другими, снижается сложность архитектуры приложения, а мощности современных устройств хватает на выполнение большинства задач даже в Dalvik VM.
Источник