- Как начать работу с Native Development Kit от Android
- Предпосылки
- 1. Зачем писать собственный код?
- 2. Создание нового проекта
- 3. Создание собственной библиотеки
- 4. Использование нативной библиотеки
- Заключение
- Фоновый звук в Android с MediaSessionCompat
- Сделайте снимки с помощью своего приложения для Android
- Использование NDK в Android Studio
Как начать работу с Native Development Kit от Android
С запуском Android Studio 2.2 разработка приложений для Android, содержащих код на C ++, стала проще, чем когда-либо. В этом уроке я покажу вам, как использовать набор Native Development Kit для Android, который обычно называют NDK, для создания собственной библиотеки C ++, функции которой доступны для классов Java.
Предпосылки
Чтобы иметь возможность следовать этому руководству, вам понадобится следующее:
1. Зачем писать собственный код?
Как правило, вы должны разработать приложение для Android, используя только Java. Добавление кода на C ++ значительно увеличивает его сложность и снижает его переносимость. Тем не менее, вот несколько причин, по которым вы все равно хотите это сделать:
- Чтобы максимизировать производительность: вы можете повысить производительность приложения для Android, хотя и незначительно, путем внедрения части бизнес-логики на процессоре на C ++.
- Для использования высокопроизводительных API-интерфейсов: реализация спецификаций API, таких как Vulkan Graphics и OpenSL ES, является частью NDK. Поэтому разработчики Android-игр, как правило, используют NDK.
- Использовать популярные библиотеки C / C ++: существует множество библиотек C и C ++, которые не имеют эквивалентов Java. Если вы хотите работать с ними в своем приложении для Android, использование NDK — это ваше решение.
- Повторное использование кода: до тех пор, пока код, написанный на C ++, не содержит зависимостей от конкретной платформы, он может использоваться как в приложениях Android, так и в iOS, как правило, с минимальными изменениями. Если вы разрабатываете большое приложение и собираетесь поддерживать как платформы iOS, так и Android, использование C ++ может повысить вашу производительность.
2. Создание нового проекта
В Android Studio 2.2 или выше мастер создания проектов позволяет быстро создавать новые проекты, поддерживающие код на C ++.
Начните с запуска Android Studio и нажмите кнопку Start a new Android Studio project на экране приветствия. На следующем экране дайте вашему приложению осмысленное имя и проверьте поле Include C ++ Support.
На экране создания активности мастера выберите параметр Add No Activity. На последнем экране мастера убедитесь, что для значения поля C ++ Standard установлено значение Toolchain Default и нажмите кнопку Finish.
Android NDK и инструменты, от которых он зависит, по умолчанию не установлены. Поэтому, как только проект будет сгенерирован, вы увидите ошибку, которая выглядит так:
Чтобы исправить ошибку, откройте Tools > Android > SDK Manager и перейдите на вкладку SDK Tools.
В списке доступных инструментов разработчика выберите CMake и NDK и нажмите кнопку Apply.
После завершения установки перезапустите Android Studio.
3. Создание собственной библиотеки
Проект Android Studio, который поддерживает C ++, имеет дополнительный каталог исходного кода cpp. Как вы могли догадаться, все файлы и библиотеки C ++ должны быть размещены внутри него. По умолчанию в каталоге есть файл native-lib.cpp. Пока что мы напишем весь наш код на C ++ внутри него.
В этом уроке мы создадим простую нативную библиотеку, содержащую функцию, которая вычисляет площадь круга с помощью формулы πr². Функция примет радиус круга как jdouble и вернет площадь как jstring .
Начните с добавления в файл директив include :
jni.h — это заголовочный файл, содержащий несколько макроопределений, типов, структур и функций, которые незаменимы при работе с NDK. (JNI означает Java Native Interface, и это среда, которая позволяет Java Runtime взаимодействовать с собственным кодом.) Заголовочный файл string необходим, потому что мы будем использовать тип jstring в нашей библиотеке. Заголовочный файл math.h содержит значение π.
По умолчанию для поддержки полиморфизма компилятор C ++ модифицирует имена всех функций, которые вы определяете в своем коде. Эта функция часто упоминается как изменение имени. Из-за манипуляции с именем вызов ваших функций C ++ из кода Java приведет к ошибкам. Чтобы избежать ошибок, вы можете отключить управление именами, указав свои функции внутри блока extern «C» .
Имена функций C ++, доступные через JNI, должны иметь следующий формат:
- Они должны иметь префикс Java_.
- Они должны содержать искаженную форму имени пакета, где точки заменяются символами подчеркивания.
- Они должны содержать имя класса Java, к которому они принадлежат.
Кроме того, вы должны указать видимость функции. Вы можете сделать это, используя макрос JNIEXPORT. По соглашению, большинство разработчиков также включают макрос JNICALL в определении функции, хотя в настоящее время он не служит никакой цели в Android.
Следующий код определяет функцию, называемую calculateArea , к которой можно получить доступ из класса Java, называемого MainActivity :
Обратите внимание, что помимо радиуса функция также принимает тип JNIEnv , который имеет функции утилиты, которые вы можете использовать для обработки типов Java, и экземпляр jobject , который является ссылкой на экземпляр MainActivity . Мы, конечно, будем создавать MainActivity позже в этом уроке.
Вычисление площади очень просто. Все, что вам нужно сделать, это умножить макрос M_PI на квадрат radius .
Просто чтобы вы знали, как обрабатывать строки при работе с JNI, давайте теперь создадим новую строку, содержащую сообщение о том, что такое площадь. Для этого вы можете использовать функцию sprintf() .
Поскольку Java не может напрямую обрабатывать массив символов C ++, возвращаемый тип нашей функции — jstring . Чтобы преобразовать массив output в объект jstring , вы должны использовать функцию NewStringUTF() .
На этом этапе наш код на C ++ готов.
4. Использование нативной библиотеки
На предыдущем шаге вы видели, что функция calculateArea() должна принадлежать классу Java MainActivity . Начните создавать класс, щелкнув правой кнопкой мыши имя вашего Java-пакета и выбрав File > New > Empty Activity.
В появившемся диалоговом окне назовите действие MainActivity. Убедившись, что опция Launcher Activity отмечена, нажмите кнопку Finish.
Перед тем, как его можно будет использовать, необходимо загрузить собственную библиотеку. Поэтому добавьте в класс static блок и загрузите библиотеку с помощью метода loadLibrary() класса System .
Чтобы иметь возможность использовать С++ функцию calculateArea() внутри действия, вы должны объявить ее как native метод.
Теперь вы можете использовать метод calculateArea() , как любой обычный метод Java. Например, вы можете добавить следующий код к методу onCreate() для вычисления и печати области круга с радиусом 5,5:
Если вы запустите приложение, вы сможете увидеть следующий вывод в окне logcat:
Заключение
В этом уроке вы узнали, как создать собственную C ++-библиотеку и использовать ее в приложении для Android. Стоит отметить, что собственный процесс сборки по умолчанию генерирует отдельный .so-файл для каждой архитектуры процессора, поддерживаемой NDK. Поэтому вы можете быть уверены, что ваше приложение будет работать на большинстве Android-устройств без каких-либо проблем.
Чтобы узнать больше об Android NDK, я предлагаю вам обратиться к руководству NDK.
И ознакомьтесь с некоторыми нашими другими учебниками и курсами по разработке Android!
Фоновый звук в Android с MediaSessionCompat
Сделайте снимки с помощью своего приложения для Android
Источник
Использование 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.
Источник