что такое android ndk

Основы Android NDK на примере работы с OpenAL

День добрый, уважаемые Хабраюзеры!

С недавних пор занимаюсь разработкой приложений под Android, в частности разработкой игр. Так сложилось, что для одного проекта пришлось работать с Android ndk. Все трудности и нюансы работы с native рассмотреть в принципе невозможно в рамках одной статьи, решил в данной статье небольшое введение в ndk написать.
А чтобы статья была интересна не только новичкам, покажу как работать с OpenAL и форматами WAV, OGG.

Введение

Зачем нужен NDK?

Вызов C++ кода из Java

Про библиотеки из ndk можете почитать тут.

Создание C++ файлов

Необходимо определить методы для экспорта, который мы будем вызывать из Java. Как пример, при запуске приложения мы будем грузить музыку в OpenAL. Для этого определим метод:

Я всё это ручками пишу, но есть удобная утилита для автоматической генерации javah.

Затем нам необходимо будет его реализовать, но об этом немного позже.

Подключение библиотеки в Java

После генерации библиотека, необходимо её подключить в Java.

И определить метод с тем же названием, как и в C++ коде:

Вызов Java из C++

Конечно можно просто определить метод у класса, но лучше использовать интерфейсы. Тогда нам не придётся менять native код, если захотим работать с другим классом.

Как пример, создадим интерфейс всего с одним методом:

Ох уж эти потоки. Проблема в том, что нельзя воздействовать на вьюху из другого потока. Поэтому вся реализация интерфейса будет примерно такой:

Проблем с потоками может и не быть, но в нашем случае при запуске приложения будем создавать отдельный поток для загрузки ресурсов, поэтому для нас вопрос актуален.

Java интерфейсу в нативном коде на C++ будет соответствовать следующий класс:

Теперь можно показать реализацию loadAudio метода, хэдер которого в первой части статьи был.

В конструкторе класса мы сохраняем дескриптор класса и получаем ссылку на его метод:

Теперь мы может вызывать Java метод написав:

AAssetManager

Раньше использовалась open source библиотека libzip для работы с ресурсами приложения.
С 2.3 версии API в Android ndk появился замечательный класс для работы с директорией assets прямо из C++ кода.
Методы похожи на методы по работе с файлами из stdio.h. AAssetManager_open вместо fopen, AAsset_read вместо fread, AAsset_close вместо fclose.

Я для него небольшую обёртку написал. Код вставлять сюда не буду, так как в целом работа та же, что и с FILE обычным.

Работа с OpenAL

Статья уже довольная большая, а к самому интересному так и не приступил. Прошу меня простить за это…

Подготовка

В первую нужно собрать OpenAL. Для работы с WAV этого достаточно, но мы же ещё хотим и с OGG поработать. Для OGG нужен декодер Tremor.

Для звука я написал обёртки с необходимыми методами. Весь код тут приводить смысла нет, освещу самое интересное, а именно загрузку.

Прочитать WAV файл

Стоит сказать об WAV кое-что. Порой, файл на PC вроде прослушивается отлично, но в при работе в OpenAL с ним возникают ошибки. Это следствие того, что битые заголовки. Я встречал много конвертеров, которые в хэдеры писал какую-то чушь (свой логотип как пример), как правило в dataSize. Так почему не работает, а на PC играет?
Непосредственно сами данные аудио хранятся после хэдера и их размер в dataSize. Если с этим полем что-то не так, то будут ошибки. Можно правда посчитать размер в лоб. Размер данных = размер файла — размер хэдера. Так что, думаю, плееры берут размер данных вычитая, а не из хэдера.

По работе с WAV вроде всё просто, так как формат не сжатый. При работе с .Ogg всё посложнее.

Прочитать Ogg файл

В чём особенность Ogg по сравнению с WAV? Это сжатый формат. Так что, перед там как записать данные в буфер OpenAL, нам необходимо данные декодировать.
Загвоздка в том, что по умолчанию Vorbis стримит из FILE, так что нам необходимо переопределить все callback методы по работе с данными:

Теперь необходимо прочитать:

Мы при загрузке приложения вызываем C++ метод loadAudio, который вызывает load у NativeCallListener, который и грузит звкуи:

Заключение

Ещё раз извиняюсь, что статья вышла довольно объёмная, иначе не представляю как написать. С помощью представленной реализации можно работать со звуком независимо от платформы. Скажем, если вы пишите движок игры для iOS и Android.

Исходники

Проект написан на Eclipse. Исходники можно посмотреть на github.

Источник

Почему стоит использовать Android NDK в вашем следующем проекте

Оригинал: Why should you need to pick Android NDK for your next project
Автор: Harigovind Thoyakkat
Дата публикации: 28 марта 2017 г.
Перевод: А.Панин
Дата перевода: 28 апреля 2017 г.

что такое android ndk. Смотреть фото что такое android ndk. Смотреть картинку что такое android ndk. Картинка про что такое android ndk. Фото что такое android ndk

Android NDK (Native Development Kit) является очень популярным инструментарием, используемым для разработки приложений для мобильных устройств. Многие приложения из магазина приложений Android Market используют компоненты, разработанные с использованием языков программирования, отличных от Java, для достижения максимальной производительности. Исходя из этого NDK является инструментарием, помогающим разработчикам создавать компоненты для своих приложений с использованием компилируемых языков программирования для различных целей, начиная с достижения оптимальной производительности и заканчивая упрощением используемого кода.

Для чего и как используется бинарный код

Мы все знаем о том, что процесс разработки приложений для Android тесно связан с использованием языка программирования Java, а также о том, что использование данного языка программирования значительно упрощает жизнь разработчиков, ведь они могут использовать элегантную объектно-ориентированную модель Java. Приложения или алгоритмы, реализованные на языке Java, преобразуются в специальный байткод, который выполняется аналогичным образом на всех поддерживаемых платформах. При этом виртуальная машина Java или JVM (Java Virtual Machine) ответственная за JIT-компиляцию и исполнение байткода Java, доступна практически для всех существующих платформ, начиная с мэйнфреймами и заканчивая мобильными телефонами.

Однако, в случае системы Android, которая используется главным образом на смартфонах и планшетных компьютерах, ключевым фактором становится достижение максимальной производительности приложения на используемом аппаратном обеспечении. Исходный код Java, как говорилось выше, предварительно преобразуется в байткод. Это именно тот байткод, который исполняется с небольшими отличиями на платформах, для которых доступна виртуальная машина Java. В конце концов все приложение исполняется в рамках виртуальной машины на устройстве, работающем под управлением Android.

Что касается разработки приложений для Android, описанный выше фактор является незначительным недостатком. Но программирование на языке Java может оказаться достаточно сложным ввиду постоянного усложнения кода и затруднения его понимания. Более того, использование мультиплатформенного байткода и виртуальной машины обуславливает значительные затраты вычислительных ресурсов устройства.

Другим важным фактором, который требует внимания, является мультиплатформенный код. Если нам нужно создать программу для множества аппаратных платформ, мы можем переписать большую часть кода, относящегося к контроллеру и отображению, для каждой из платформ, что является не самым разумным решением. Но весь код, относящийся к контроллеру, должен быть обязательно портирован на языки C и C++, так, как практически все мобильные платформы поддерживают их; таким образом, если нам удастся реализовать логику в рамках библиотек на языках C и C++ и впоследствии использовать ее на нескольких платформах, нам удастся максимально сократить потери производительности приложения. В подобных случаях мы будем использовать код на языках C и C++ вместе с привычным кодом на языке Java или «мультиплатформенным кодом».

Использование Android NDK

Android NDK является инструментарием, позволяющим реализовывать части приложения для Android на таких компилируемых языках программирования, как C и C++ и содержит библиотеки для управления активностями и доступа к физическим компонентам устройства, таким, как различные сенсоры и дисплей.

Android NDK интегрирован с инструментами из набора компонентов для разработки программного обеспечения (Android SDK), а также с интегрированной средой разработки Android Studio или устаревшей средой разработки Eclipse ADT. Однако, NDK не может использоваться отдельно.

Принцип работы Android NDK

Мы можем использовать ключевое слово native для того, чтобы компилятор знал, что данный фрагмент реализован в рамках компилируемого кода. Например:

Весь компилируемый код исполняется посредством интерфейса под названием Java Native Interface (JNI), который позволяет связать друг с другом компоненты на языках Java и C/C++.

Установка и использование Android NDK в Ubuntu

Android NDK поставляется в формате самораспаковывающегося архива. По этой причине нам придется лишь установить бит исполнения и распаковать его:

В результате компоненты NDK будут сохранены в текущей рабочей директории.

Распаковка в ручном режиме

Пакет с компонентами архиватора 7-Zip доступен из официального репозитория Ubuntu и может быть установлен, к примеру, с помощью команды apt-get:

Установка с помощью Android Studio

Мы можем установить Android NDK с помощью компонента SDK Manager непосредственно из Android Studio.

Создание или импорт проекта с бинарными компонентами

После настройки Android Studio мы можем создать новый проект с поддержкой языков программирования C/C++. Однако, если нам понадобится добавить или импортировать существующий код на этих языках в проект Android Studio, мы будем вынуждены выполнить описанные ниже действия.

Для начала следует создать новые файлы исходного кода с использованием упомянутых языков программирования и добавить их в проект, открытый в Android Studio. Мы можем пропустить этот шаг, если в проекте уже имеются подобные файлы или нам нужно импортировать в него предварительно скомпилированную библиотеку.

После запуска приложения на устройстве или в эмуляторе мы сможем использовать функции различных интегрированных сред разработки, таких, как Android Studio, для его отладки.

Все это свидетельствует о важности Android NDK для разработчиков приложений для платформы Android. Например, данный набор программных компонентов позволяет создателям игровых движков лучше оптимизировать версии своих продуктов для Android, в результате чего они будут выдавать более впечатляющие графические эффекты, затрачивая на них меньше системных ресурсов.

Процесс создания простого приложения на основе Android NDK не связан с какими-либо сложностями. Однако, каждому разработчику следует уяснить один важный момент: набор программных компонентов Android NDK разрабатывался для использования в определенных случаях и не должен применяться при разработке любых приложений.

Android NDK может как помочь в процессе разработки приложения, так и максимально осложнить его. Не является тайной и то, что использование бинарного кода на платформе Android в некоторых случаях не приводит к заметному повышению производительности приложения (хотя в большинстве случаев его производительность все же повышается), но при этом оно в любом случае усложняет его код. Обычно повышение производительности приложений достигается благодаря задействованию кода со специфичными для используемого центрального процессора инструкциями. Но в общем случае рекомендуется использовать NDK лишь тогда, когда производительность приложения является критически важным параметром, а не тогда, когда разработчику удобнее писать код на языках C/C++.

В качестве заключения следует сказать о том, что не существует незыблемых правил, регламентирующих возможные случаи использования NDK, поэтому вам всегда стоит обращаться к своими знаниям, опыту и интуиции.

Источник

Android SDK vs NDK — сравнение производительности однотипных участков кода

В целях улучшения производительности приложения на Андроид начал постепенно переписывать критические участки кода с Java (SDK) на С++ (NDK). Результат оказался сравнимым с тем, что я получил пару десятков лет назад, делая ассемблерные вставки в код турбопаскаля.

Я не ставлю перед собой задачи описать работу с Android NDK — у самого недостаточно опыта. Тем, кто заинтересуется, лучше начать с этой ссылки.
Цель данной короткой статьи — привести несколько цифр, которые я получил опытным путем, сравнивая время выполнения определенных функций, написанных на Java и после этого переписанных на C++. И, возможно, эти цифры мотивируют кого-либо поглубже изучить этот вопрос.

Так как мое приложение связано с обработкой фотографий, то узкими местами являлись циклы обхода пикселей картинки и определенных действий над ними. Тестировал я на реальных устройствах — Nexus One и Nexus 7 (2012). Результаты экспериментов (в ms) свел в таблицы:

Наложение слоя (режим Luminosity, цветной рисунок)
Nexus OneNexus 7
SDKNDKSDKNDK
2563120485090
21221004520190
21621104330100

В среднем выигрыш в скорости для Nexus One — в 21 раз, для Nexus 7 — в 36 раз.

Наложение слоя (режим Color Dodge, одноцветный рисунок)
Nexus OneNexus 7
SDKNDKSDKNDK
267330572080
257220623070
257320611070

В среднем выигрыш в скорости для Nexus One — в 112 раз, для Nexus 7 — в 82 раза.

Наложение слоев по градиенту прозрачности
Nexus OneNexus 7
SDKNDKSDKNDK
13013213010470
12213302670620
12113002770610

В среднем выигрыш в скорости для Nexus One — в 4 раза, для Nexus 7 — в 5 раз.

Как видим, результаты различаются на один, а то и два порядка. Я специально привел цифры в абсолютных значениях, чтобы было видно реальное ускорение работы от применения NDK. Сравнительно скромные результаты последнего теста обусловлены тем, что для расчета наложения использовались в том числе и стандартные функции библиотеки OpenCV, которые достаточно хорошо оптимизированы. Соответственно данный тест наглядно показывает реальное ускорение работы приложения в целом.

Вскользь коснусь применения библиотеки OpenCV. Как я и ожидал, Java-часть библиотеки является обычной оберткой над NDK. Все же провел вышеописанные эксперименты над достаточно тяжелыми и долгоиграющими алгоритмами — такими как нахождение характерных точек на изображениях, grabcut — метод. Разница в скорости между Java и NDK составила максимум 10%, что можно списать на погрешность, так как совершенно одинаковых изображений в тот момент я получить не мог.

Update. Довольно неприятно признавать собственные ошибки, но что делать.
Итак, вот пример кода, с помощью которого я оценивал производительность Java-реализации библиотеки OpenCV:

Обходим попиксельно две матрицы одинакового размера и в зависимости от значения соответствующего пикселя той и другой матрицы рассчитываем результирующий пиксель.
Благодаря замечаниям в комментариях к статье код был оптимизирован следующим образом (рисунки одноцветные):

Для тестирования опять же использовал реальные устройства Nexus One и Nexus 7, на вход же подавал 3-х мегапиксельные картинки и в том и другом случае — хотел попутно сравнить производительность устройств между собой. Результаты (средние, в ms) свел в таблицу:

Источник

Введение в Android NDK

Для разработки приложений под ОС Android, Google предоставляет два пакета разработки: SDK и NDK. Про SDK существует много статей, книжек, а так же хорошие guidelines от Google. Но про NDK даже сам Google мало что пишет. А из стоящих книг я бы выделил только одну, Cinar O. — Pro Android C++ with the NDK – 2012.

Эта статья ориентирована на тех, кто ещё не знаком (или мало знаком) с Android NDK и хотел бы укрепить свои знания. Внимание я уделю JNI, так как мне кажется начинать нужно именно с этого интерфейса. Так же, в конце рассмотрим небольшой пример с двумя функциями записи и чтения файла. Кто не любит много текста, тот может посмотреть видео версию.

Что такое Android NDK?

Android NDK (native development kit) – это набор инструментов, которые позволяют реализовать часть вашего приложения используя такие языки как С/С++.

Для чего используют NDK?

Что такое JNI?

Java Native Interface – стандартный механизм для запуска кода, под управлением виртуальной машины Java, который написан на языках С/С++ или Assembler, и скомпонован в виде динамических библиотек, позволяет не использовать статическое связывание. Это даёт возможность вызова функции С/С++ из программы на Java, и наоборот.

Преимущества JNI

Основное преимущество перед аналогами (Netscape Java Runtime Interface или Microsoft’s Raw Native Interface and COM/Java Interface) является то что JNI изначально разрабатывался для обеспечения двоичной совместимости, для совместимости приложений, написанных на JNI, для любых виртуальных машин Java на конкретной платформе (когда я говорю о JNI, то я не привязываюсь к Dalvik машине, потому как JNI был написан Oracle для JVM который подходит для всех Java виртуальных машин). Поэтому скомпилированный код на С/С++ будет выполнятся в не зависимости от платформы. Более ранние версии не позволяли реализовывать двоичную совместимость.

Двоичная совместимость или же бинарная совместимость – вид совместимости программ, позволяющий программе работать в различных средах без изменения её исполняемых файлов.

Как устроен JNI

что такое android ndk. Смотреть фото что такое android ndk. Смотреть картинку что такое android ndk. Картинка про что такое android ndk. Фото что такое android ndk
JNI таблица, организована как таблица виртуальных функций в С++. VM может работать с несколькими такими таблицами. Например, одна будет для отладки, вторая для использования. Указатель на JNI интерфейс действителен только в текущем потоке. Это значит, что указатель не может гулять с одного потока в другой. Но нативные методы могут быть вызваны из разных потоков. Пример:

Локальные и глобальные ссылки

JNI делит ссылки на три типа: локальные, глобальные и слабые глобальные ссылки. Локальные действительны пока не завершиться метод. Все Java объекты которые возвращает функции JNI являются локальными. Программист должен надеется на то что VM сама подчистит все локальные ссылки. Локальные ссылки доступны лишь в том потоке в котором были созданы. Однако если есть необходимость то их можно освобождать сразу методом JNI интерфейса DeleteLocalRef:

Глобальные ссылки остаются пока они явно не будут освобождены. Что бы зарегистрировать глобальную ссылку следует вызвать метод NewGlobalRef. Если же глобальная ссылка уже не нужна, то её можно удалить методом DeleteGlobalRef:

Обработка ошибок

JNI не проверяет ошибки такие как NullPointerException, IllegalArgumentException. Причины:

Например, некоторые функции JNI доступа к массивам не возвращают ошибки, но могут вызвать исключения ArrayIndexOutOfBoundsException или ArrayStoreException.

Примитивные типы JNI

В JNI существуют свои примитивные и ссылочные типы данных.

Java TypeNative TypeDescription
booleanjbooleanunsigned 8 bits
bytejbytesigned 8 bits
charjcharunsigned 16 bits
shortjshortsigned 16 bits
intjintsigned 32 bits
longjlongsigned 64 bits
floatjfloat32 bits
doublejdouble64 bits
voidvoidN/A

Ссылочные типы JNI

что такое android ndk. Смотреть фото что такое android ndk. Смотреть картинку что такое android ndk. Картинка про что такое android ndk. Фото что такое android ndk

Модифицированный UTF-8

JNI использует модифицированную кодировку UTF-8 для представления строк. Java в свою очередь использует UTF-16. UTF-8 в основном используется в С, потому что он кодирует \u0000 в 0xc0, вместо привычной 0x00. Изменённые строки кодируются так, что последовательность символов, которые содержат только ненулевой ASCII символы могут быть представлены с использованием только одного байта.

Функции JNI

Интерфейс JNI содержит в себе не только собственный набор данных, но и свои собственные функции. На их рассмотрение уйдёт много времени, так как их не один десяток. Ознакомится с ними вы сможете в официальной документации.

Пример использования функций JNI

Небольшой пример, что бы вы усвоили пройденный материал:

Потоки

Всеми потоками в Linux управляет ядро, но они могут быть прикреплены к JavaVM функциями AttachCurrentThread и AttachCurrentThreadAsDaemon. Пока поток не присоединён он не имеет доступа к JNIEnv. Важно, Android не приостанавливает потоки которые были созданы JNI, даже если срабатывает GC. Но перед тем как поток завершиться он должен вызвать метод DetachCurrentThread что бы отсоединиться от JavaVM.

Первые шаги

Структура проекта у вас должна выглядеть следующим образом:
что такое android ndk. Смотреть фото что такое android ndk. Смотреть картинку что такое android ndk. Картинка про что такое android ndk. Фото что такое android ndk
Как мы видим из рисунка 3, весь нативный код находится в папке jni. После сборки проекта, в папке libs создастся четыре папки под каждую архитектуру процессора, в которой будет лежать ваша нативная библиотека (количество папок зависит от количество выбранных архитектур).

Android.mk

Как упоминалось уже выше, это make файл для сборки нативного проекта. Android.mk позволяет группировать ваш код в модули. Модули могут быть как статические библиотеки (static library, только они будут скопированные в ваш проект, в папку libs), разделяемые библиотеки (shared library), автономный исполняемый файл (standalone executable).

Пример минимальной конфигурации:

Application.mk

NDK-BUILDS

По умолчанию устанавливается поддержка 64-х разрядной версии утилит, но вы можете принудительно собрать только для 32-х установив флаг NDK_HOST_32BIT=1. Google, рекомендует всё же использовать 64-х разрядность утилит для повышения производительности больших программ.

Как собрать проект?

Раньше это было мучением. Нужно было установить CDT плагин, скачать компилятор cygwin или mingw. Скачать Android NDK. Подключить это всё в настройках Eclipse. И как на зло это всё оказывалось не рабочим. Я первый раз когда столкнулся с Android NDK, то настраивал это всё 3 дня (а проблема оказалось в том что в cygwin нужно было дать разрешение 777 на папку проекта).

Сейчас с этим всё намного проще. Идёте по этой ссылке. Качаете Eclipse ADT Bundle в котором уже есть всё то что необходимо для сборки.

Вызов нативных методов из Java кода

Для того что бы использовать нативный код из Java вам сперва следует определить нативные методы в Java классе. Например:

Перед методом следует поставить зарезервированное слово «native». Таким образом компилятор знает, что это точка входа в JNI. Эти методы нам нужно реализовать в С/С++ файле. Так же Google рекомендует начинать именовать методы со слова nativeХ, где Х – реальное название метода. Но перед тем как реализовывать эти методы вручную, следует сгенерировать header файл. Это можно сделать вручную, но можно использовать утилиту javah, которая находится в jdk. Но пойдём дальше и не будет использовать её через консоль, а будем это делать при помощи стандартных средств Eclipse.

Теперь можете запускать. В директории bin/classes будут лежать ваши header файлы.

Далее копируем эти файлы в jni директорию нашего нативного проекта. Вызываем контекстное меню проекта и выбираем пункт Android Tools – Add Native Library. Это позволит нам использовать jni.h функции. Дальше вы уже можете создавать cpp файл (иногда Eclipse его создаёт по умолчанию) и писать тела методов, которые уже описаны в header файле.

Пример кода я не стал добавлять в статью, чтобы не растягивать её. Пример вы можете посмотреть/скачать с github.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *