Нативные библиотеки android что это

Android NDK

Android NDK(расшифровывается как Android Native Development Kit) — набор инструментов для создания приложений для ОС Android с использованием C/C++.

это и позволяет писать нативные приложения, работающие быстрее чем Java. Android использует альтернативный libc — Bionic, а также имеет встроенные библиотеки zlib, OpenGL ES, Vulkan и различные API.

Что такое Android NDK?

22 февраля 2021 (Обновление: 5 мар. 2021)

«Хотя это и позволяет писать нативные приложения, работающие быстрее чем Java, писать только на C/C++ нельзя, точка входа обязательно должна быть написана на Java», — это не правда. Полностью нативные андроид-приложения можно делать чуть ли не со времён 9-й версии API (android 2.3). Proof: https://developer.android.com/ndk/samples/sample_na

General GDA
Пофиксил

Я бы ещё текст прогнал через спелл чекер (пунктуация, опечатки) и согласовал предложение. А то «это . » и начинается с маленькой буквы, и читается как-то не очень. Но это мелочи.

Главное, что «работающие быстрее, чем Java», — это субъективное оценочное суждение. Мало того, что на android нету Java (да, программу на этом языке можно скомпилировать для работы на Android; но на устройствах нет Java VM, модель памяти отличается от Java и т.п.). Так ещё на C/C++ надо постараться написать быстрее. К примеру, в современных андроидах очень сложная среда исполнения, где есть пред-компиляция в нативный код ещё на момент инсталляции приложения. При обновлении ОС может (и делает) перекомпиляция такого кода. Ещё пример: лично я при должном старании определённый класс задач под андроид вполне могу написать на Java/Kotlin так, что будет быстрее, чем на C++. Как в плане скорости выполнения кода, так и в плане скорости его написания.

Раз форум у нас тут гемдеву посвящён. То я могу набросить пример: на Unity так вообще всё на C# пишется. И ничего. Тонны успешных проектов (гемдев проектов!) под мобильные телефоны.

Если и писать про «быстрее», то со ссылками на конкретные исследования или case studies.

General GDA, хотелось бы поподробнее, как вы напишите приложение, которое написано на Java так, чтобы оно работало быстрее внутреннего цикла нативной программы? В плане скорости работы кода.

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

Mirrel
ну, как пример из головы — если работа приложения состоит в обработке строк или структур, которые оно получает с помощью одних вызовов API и отображает с помощью других, то большую часть времени нативное приложение может заниматься перепаковыванием их в свое представление, а java — будет работать с данными «как есть».
Другой пример — оптимизатор java вполне может быть круче оптимизатора fpc и соответственно одни и те же вычисления будут преобразованы в более эффективный машинный код.

kipar, я не об этом спрашивал!

Есть (у абсолютно нативного приложения) свой внутренний цикл (как и в других системах). Из этого цикла и происходят всё вызовы этого самого приложения (Start, Stop, Pause, Resume . ).

Как, человек сможет, используя java-код, обойти скорость работы внутреннего цикла?

И, всеми данными, можно пользоваться из нативного кода. В дополнению к этому, многие данные на Android находятся именно в нативном виде, а внутренние библиотеки соединяют этот код для удобства работы с Java-кодом.

но это больше для информации.

Mirrel
> Start, Stop, Pause, Resume
эти операции делаются достаточно редко, чтобы игнорировать JNI-оверхед при их вызове. Да даже если Update — один JNI-вызов каждый кадр — это копейки.

Что если, я скажу, что можно полностью игнорировать JNI-вызовы?

Mirrel
> Что если, я скажу, что можно полностью игнорировать JNI-вызовы?
и писать при этом на яве?

Зачем? Лично я буду писать на Паскале. Другой народ на C/C++. Кто умеет, тот на каком ещё языке, кроме всех вышеперечисленных.

Читайте также:  Что будет если отключить резервное копирование андроид

General GDA
То есть dalvik и art не выполняют (свой) java байт-код? Про скорость C/C++ согласен.

egoros7
Dalvik, вроде как — вполне себе jvm и исполняет байткод. Хотя и не совсем compliant. А art — он AOT.

kkolyan
>а art — он AOT
Ну при включении машинный код всё же компилируется из байт-кода. Или я не прав?

egoros7
Скорее всего да, т.к. это проще чем компилить из сорцов. Но я наверняка не знаю.

Источник

Как использовать нативные библиотеки в Android

Предположим, что вам нужно сделать обработку/воспроизведение медиа данных в вашем приложении. Такие задачи обычно решаются при помощи библиотек, которые написаны на C/C++.
Чтобы работать с C/C++ кодом в Android вам нужно установить NDK (native development kit).

На примере аудиокодека Opus, который является по сути библиотекой на C, мы покажем: как добавить к себе в приложение нативную библиотеку, использовать из неё код, и как собрать это вместе так, чтобы оно работало 🙂

1. Скачиваем исходники библиотеки

Скачиваем с официального сайта Opus исходники. Теперь у нас есть куча C файлов, но что с ними делать? На этот вопрос можно ответить зная, какую задачу вы хотите решить с помощью данной библиотеки.

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

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

Для этой задачи в Opus есть структуры OpusEncoder и OpusDecoder, а также функции opus_encode и opus_decode , которые собственно и выполняют полезную работу. Добавим исходники к себе в проект: для этого создадим в app/src/main папку cpp и копируем в неё папку с исходниками, которую скачали до этого.

Отлично, мы нашли нужные функции, почитали документацию к ним и поняли, что мы должны им передать, чтобы получить нужный результат, но возникает новый вопрос: а как их вызвать из Java?

2. Пишем JNI обертку для вызова C функций

Вся работа с кодом написанным на C/C++ в Java реализована с помощью Java Native Interface(JNI).

Рассмотрим его работу на примере вызова метода opus_encoder_init , который инициализирует объект энкодера. Чтобы вызвать метод opus_encoder_init из Java нужно создать обычный Java метод с ключевым словом native.

Для начала создадим класс, где будут располагаться все такие методы(JNI declarations). Назовем его Opus и в нём уже создадим метод initEncoder :

Как видно, мы передаём туда некоторое количество параметров, а в ответ нам вернётся boolean .

Теперь нам нужен С файл, в котором мы реализуем данный метод. Создадим его в той же папке, где лежат исходники Opus (app/src/main/cpp) и назовём jniopus.c, cодержимое этого файла:

Чтобы JNI смог с ним работать обязательно нужно добавить . Обратите внимание на именование метода: сначала идёт указание, что вызов идёт из Java, а затем через нижнее подчёркивание полное имя класса(т.е. вместе с package name) и имя самого метода.

Входных параметров больше, чем мы передавали: добавился JNIEnv *env и jobject instance . Эти праметры передаются в каждом JNI вызове.

  • JNIEnv *env это указатель на структуру, которая содержит все функции необходимые для взаимодействия с виртуальной машиной и для работы с Java объектами.
  • jobject instance это ссылка на Java объект где объявлен native метод, который мы вызвали.

Далее, мы уже вызываем метод из самой библиотеки opus opus_encoder_init и делаем инициализацию. В конце возвращаем JNI_TRUE : это константа равная 1, которая заранее определена в JNI для удобства.

Теперь объявим другие, нужные нам, native методы в классе Opus. Этот класс можно сравнить с интерфейсом, т.к. у методов с ключевым словом native нет реализации. Мы просто объявляем какие методы мы будем использовать для работы с нативным кодом, какие параметры мы будем туда передавать и что ожидаем получить назад. Вот, что получилось:

Важно отметить, что в самом начале мы добавили static блок с System.loadLibrary(«jniopus»); , в котором загружаем нативную библиотеку, которую будем использовать.

Нативный код можно собрать в:

  • статическую( / .lib) библиотеку
  • динамическую(.so) библиотеку
Читайте также:  Самый точный компас для андроида

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

Но статическая библиотека нам не подходит, т.к. с JNI в Android можно использовать только динамические библиотеки(.so).

Динамические библиотеки ещё называют shared objects отсюда и расширение .so. Они собираются в отдельные модули и при сборке используется динамическая линковка с кодом основного проекта, так что код из такой библиотеки вызывается динамически в рантайме.

Теперь в папке cpp у нас есть jniopus.c, где находится имплементации всех методов из Java с ключевым словом native и папка opus с исходниками этой библиотеки. Настало время собрать все это и упаковать в .apk.

3. Собираем нативный код

Для сборки нативного кода в Android есть два инструмента: CMake и ndk-build.

CMake является инструментом сборки по умолчанию и при написании нового кода, в документации рекомендуют использовать его.
ndk-build считается устаревшим, но до сих пор поддерживается, т.к. есть много legacy проектов где он используется.

Мы будем собирать с помощью CMake. Для этого нужно создать билд скрипт, в котором мы опишем как нужно собрать наш нативный код. В CMake такой билд скрипт называется CMakeLists.txt .Поэтому создаём CMakeLists.txt в корне проекта (app/).

В начале любого CMakeLists.txt должна идти строчка:

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

Сначала нам нужно собрать библиотку с Opus:

Команда add_library используется для компиляции библиотеки. Сначала указывается имя, создаваемой библиотеки( opus ), затем тип ( STATIC ) , далее идут все исходники из которых нужно собрать библиотеку(почему они так странно написаны расскажу чуть позже).

В данном случае мы собираем библиотеку как статическую и это никак не сходится с тем, что я писал выше о невозможности их использования с JNI. Дело в том, что с JNI мы будём использовать другую библиотеку, которорую создадим как динамическую из нашего файла jniopus.c, а библиотеку с opus мы статически прилинкуем к ней, чтобы у нас в итоге был только один .so файл, а не два.

Пропишем правило для компиляции библиотеки, которую будет использовать JNI:

Здесь название jniopus , тип SHARED и исходный файл из которого мы собираем.

Также нужно добавить команду include_directories($) с указанием путей к файлам хэдеров(.h), чтобы компилятор знал где их искать.

Теперь стоит сказать о конструкциях вида $ : таким образом в CMake скрипте мы можем обращаться к значению переменной, которую до этого объявили с помощью команды set .

Пишем имя переменной, дальше через пробел/табуляцию/новую строку пишем значение переменной(причём можно писать как с кавычками, так и без них).

Значением переменной в этой команде могут являться сразу несколько элементов, как например $ :

Это очень удобно, когда нужно указать много файлов исходников. Так как Opus достаточно большая и сложная библиотека, то и исходников там много, поэтому в моём CMakeLists.txt билд скрипте много команд set . Все эти пути к исходникам и хэдерам прописаны в make файлах самой библиотеки, так что не волнуйтесь, если думаете, что придётся искать все исходники и вручную всё прописывать. Создатели библиотек обычно делают это сами.

Но нам нужна еще одна библиотека: для логов, чтобы можно было выводить в Logcat сообщения из нативного кода. Эта библиотека, как и множество других, часто используемых, есть в NDK и она уже собрана(pre-built), поэтому нам нужно просто найти её и слинковать с нашей .so. Чтобы найти уже собранную библиотеку нужно воспользоваться командой find_library :

Читайте также:  Android обзор текстовых редакторов

Здесь мы указываем имя( log-lib ) по которому потом будем обращаться к этой библиотеке и имя( log ), по которому CMake будет искать в NDK библиотеку для логирования.

И финальный шаг это линкование нашей .so с библиотеками, код которых мы используем в jniopus.c:

Сначала указываем имя нашей target библиотеки, к которой мы будем линковать остальные, затем через пробел/табуляцию/новую строку указываем имена библиотек, которые будут слинкованы с target библиотекой.

Важно отметить, что после сборки к названию библиотеки автоматически будет добавлен префикс lib и получится libjniopus.so(при этом в Java, когда мы пишем System.loadLibrary(«jniopus»); , мы должны указать имя библиотеки без префикса). Добавление префикса lib в начале это правило именования библиотек в мире нативного кода.

4. Собираем проект

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

Здесь блок externalNativeBuild c блоком cmake внутри встречается два раза.

В первом случае мы указываем внутри агрумент командной строки, который нужно применить arguments «-DANDROID_ARM_NEON=FALSE» , который указывает, что поддержка набора инструкций NEON для архитектуры Arm будет выключена(сделано это для упрощения, т.к. для Opus при включении NEON нужно было бы прописывать еще дополнительные исходники из которых нужно собирать библиотеку). Здесь также есть параметр cppFlags , где можно указать C++ флаги.

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

Теперь синхронизируем проект с помощью Gradle, нажимаем Run и готово. Приложение открывается, жмём на кнопку Start call и говорим в микрофон, тут же слышим, что сказали: значит всё работает как надо.

Если посмотреть на содержимое собраного .apk , то там будет папка lib, в ней несколько папок с названиями разных архитектур процессоров или по-другому Application Binary Interface(ABI) и внутри каждой папки наша библиотека libjniopus.so.

СMake автоматически собирает под все non-deprecated ABIs на данный момент. Можно указать явно конкретные архитектуры, которые вам нужны. Это делается с помощью добавления пары строчек в build.gradle :

Здесь мы указываем, что хотим собрать только под архитектуры arm64-v8a и armeabi-v7a .

Кстати именно по причине наличия различных ABI нам нужно было собирать Opus из исходников, хотя казалось бы мы могли собрать саму библиотеку Opus(с помощью, подготовленного разработчиками Opus, билд скрипта) у себя на машине в виде libopus.so или libopus.a и просто слинковать её с нашей библиотекой libjniopus.so(таким образом мы бы собрали libopus.so только под одну архитектуру).

В CMakeLists.txt есть такой участок:

Это небольшое усложнение связанное с обязательными для сборки opus аргументами командной строки и выбора режима FIXED_POINT или FLOAT_POINT.

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

Покажите мне код

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

Итоги

В данной статье мы рассмотрели:

  • процесс добавления в Android проект нативной библиотеки на примере аудиокодека Opus
  • вызов нативного кода из Java через JNI
  • сборку проекта вместе со сборкой нативного кода

Обратите особое внимание на процесс сборки нативных библиотек (написание билд скрипта CMakeLists.txt ), т.к там есть много мелких деталей и неочевидных вещей, которые не описаны ни в документации Android, ни в документации CMake.

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

С Вами была Ваша
Fora Soft Android Team 😎

Источник

Оцените статью