0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Разработка и портирование приложений Android* на основе NDK на архитектуру IA

Приложения Android* могут включать собственный код, созданный с помощью пакета Native Development Kit (NDK). Этот пакет дает разработчикам возможность повторного использования устаревшего кода, создавать код для низкоуровневого взаимодействия с оборудованием, а также реализовывать функции, которые невозможно или затруднительно создавать другими способами.

Эта статья содержит базовые сведения о создании приложений на базе NDK для архитектуры Intel, а также простые примеры портирования приложений на базе NDK на устройства с архитектурой Intel. Для демонстрации мы будем использовать пошаговый сценарий разработки простого приложения.

Мы исходим из того, что вы уже установили среду разработки Android, включая Android SDK и NDK, а также настроили emulator-x86 для тестирования приложений. Дополнительные сведения см. в разделе сообщества Android на веб-сайте Intel. Для простоты среды разработки мы по большей части используем средства Linux* для командной строки.

Введение в 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?

Google рекомендует прибегать к использованию NDK только в редчайших случаях. Зачастую это такие случаи:

  • Нужно увеличить производительность (например, сортировка большого объема данных);
  • Использовать стороннюю библиотеку. Например, много уже чего написано на С/С++ языках и нужно просто заиспользовать существующий материал. Пример таких библиотек, как, Ffmpeg, OpenCV;
  • Программирование на низком уровне (например, всё что выходит за рамки Dalvik);

Что такое 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


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

  • *env – указатель на интерфейс;
  • оbj – ссылка на объект в котором описан нативный метод;
  • i and s – передаваемые аргументы;

Примитивные типы копируются между VM и нативным кодом, а объекты передаются по ссылке. VM обязана отслеживать все ссылки которые передаются в нативный код. Все переданные ссылки в нативный код не могут быть освобождены GC. Но нативный код в свою очередь должен информировать VM о том что ему больше не нужны ссылки на переданные объекты.

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

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

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

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

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

  • снижение производительности;
  • в большинстве функций C библиотек очень и очень трудно защитится от ошибок.

JNI позволяет использовать Java Exception. Большинство JNI функций возвращают код ошибок а не сам Exception, и поэтому приходится обрабатывать сам код, а в Java уже выбрасывать Exception. В JNI следует проверять код ошибки вызываемых функций и после них следует вызвать ExceptionOccurred(), которая в свою очередь возвращает объект ошибки:

Например, некоторые функции 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

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

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

Функции JNI

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

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

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

  • JavaVM – предоставляет интерфейс для вызова функций, которые позволяют создавать и уничтожать JavaVM;
  • JNIEnv – обеспечивает большинство функций JNI;
  • JavaVMInitArgs – аргументы для JavaVM;
  • JavaVMOption – опции для JavaVM;

Метод JNI_CreateJavaVM() инициализирует JavaVM и возвращает на неё указатель. Метод JNI_DestroyJavaVM() выгружает созданную JavaVM.

Потоки

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

Первые шаги

Структура проекта у вас должна выглядеть следующим образом:

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

Для того, чтобы создать нативный проект, нужно создать обычный Android проект и проделать следующие шаги:

  • В корне проекта нужно создать папку jni, в которую поместить исходники нативного кода;
  • Создать файл Android.mk, который будет собирать проект;
  • Создать файл Application.mk, в котором описываются детали сборки. Он не является обязательным условием, но позволяет гибко настроить сборку;
  • Создать файл ndk-build, который будет запускать процесс сборки (тоже не является обязательным).

Android.mk

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

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

  • LOCAL_PATH := $(call my-dir) – функция call my-dir возвращает путь папки в которой вызывается файл;
  • include $(CLEAR_VARS) – очищает переменные которые использовались до этого кроме LOCAL_PATH. Это необходимо так как все переменные являются глобальными, потому что сборка происходит в контексте одного GNU Make;
  • LOCAL_MODULE – имя выходного модуля. В нашем примере имя выходной библиотеки установлено как NDKBegining, но после сборки в папке libs создадутся библиотеки с именами libNDKBegining. Android добавляет к названию префикс lib, но в java коде при подключении вы должны указывать название библиотеки без префикса (то есть названия должны совпадать с установленными в make файлах);
  • LOCAL_SRC_FILES – перечисление исходных файлов из которых следует создать сборку;
  • include $(BUILD_SHARED_LIBRARY) – указывает тип выходного модуля.
Читать еще:  Что делать, если iPhone залочен под оператора

В Android.mk можно определить свои переменные, но они не должны иметь такой синтаксис: LOCAL_, PRIVATE_, NDK_, APP_, my-dir. Google, рекомендует называть свои переменные, как MY_. Например:

Application.mk

В этом make файле описывается несколько переменных, которые помогут сделать сборку более гибкой:

  • APP_OPTIM – дополнительная переменная которая устанавливается в значения release или debug. Используется для оптимизации при сборке модулей. Отлаживать можно как release так и debug, но debug предоставляет больше информации для отладки;
  • APP_BUILD_SCRIPT – указывает на альтернативный путь к Android.mk;
  • APP_ABI – наверное одна из самых важных переменных. Она указывает для какой архитектуры процессоров собирать модули. По умолчанию стоит armeabi которая соответствует ARMv5TE архитектуры. Например для поддержки ARMv7 следует использовать armeabi-v7a, для IA-32 – x86, для MIPS – mips, или если вам нужно поддерживать все архитектуры то значение должно быть таким: APP_ABI := armeabi armeabi-v7a x86 mips. Если вы использует ndk версии 7 и выше, то можно не перечислять все архитектуры, а установить так APP_ABI := all.
  • APP_PLATFORM – таргет платформы;
  • APP_STL – Android использует runtime библиотеку libstdc++.so которая является урезанной и разработчику доступен не весь функционал С++. Однако, переменная APP_STL позволяет включить в сборку поддержку расширений;
  • NDK_TOOLCHAIN_VERSION – позволяет выбрать версию компилятора gcc (по умолчанию 4.6);

NDK-BUILDS

Ndk-build из себя представляет обёртку GNU Make. После 4-й версии ввели флаги для ndk-build:

  • clean – очищает все сгенеренные бинарные файлы;
  • NDK_DEBUG=1 – генерирует отладочный код;
  • NDK_LOG=1 – показывает лог сообщений (используется для отладки);
  • NDK_HOST_32BIT=1 – Android имеет средства для поддержки 64-х битных версий утилит (например NDK_PATHtoolchainsmipsel-linux-android-4.8prebuiltwindows-x86_64 и т.д.);
  • NDK_APPLICATION_MK — указывается путь к Application.mk.

В 5-й версии NDK был введён такой флаг как NDK_DEBUG. Если он установлен в 1 то создаётся отладочная версия. Если флаг не установлен то ndk-build по умолчанию проверяет стоит ли атрибут android_debuggable=«true» в AndroidManifest.xml. Если вы используете ndk выше 8-й версии, то Google не рекомендует использовать атрибут android:debuggable в AndroidManifest.xml (потому что если вы используете «ant debug» или строите отладочную версию с помощью ADT плагина то они автоматически добавляют флаг NDK_DEBUG=1).

По умолчанию устанавливается поддержка 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.

Подключитесь к своей учетной записи в социальной сети

Введение в разработку под Android: инструменты разработчика.

Итак, прежде чем начинать разработку под любую операционную систему, будь то Windows, Mac OS, iOS или Android, прежде всего необходимо разобраться с чем нам предстоит иметь дело вообще. Каждая операционная система — это свой уникальный стиль, своя идеология, свои принципы разработки и построения приложений и т.д. Это свой, отдельный от других, «монастырь» входить в который со своим уставом не следует. И было бы наивно полагать, что вот в сентябре выйдет Delphi for Android и мы сразу, с пол-пинка возьмем да и разработаем приложение под Android да ещё и денег заработаем. Язык программирования останется, что, конечно же, для нас упростит процесс разработки приложения. Вполне вероятно, что какая-то часть исходников тоже будет работать корректно в новой для нас ОС. Но вот вникать в тонкости разработки под Android, а равно и в саму ОС большинству, видимо, придётся с нуля или, как мне — имея очень и очень поверхностные знания о том, что такое Android. И этот пост и, наверное, ещё несколько (до официального выхода Delphi for Android) будут касаться исключительно операционной системы Android, необходимых инструментов для разработчика, полезных ресурсов и т.д. Думаю, что в будущем, желающим разработать свое приложение под Android в Delphi эта информация окажется полезной.

Итак, мы с Вами решили основательно подготовится к разработки под Android в Delphi. С чего следует начинать изучение новой (в плане разработки) операционной системы?

Есть много достаточно толковых и грамотно написанных книг по Android, есть и огромное сайтов и блогов, посвященным Android’у, но начинать стоит с основного сайта — developer.android.com. Только здесь Вы сможете найти самую свежую информацию по операционной системе, а также скачать актуальные версии инструментов разработчика, получить необходимую информацию по API и т.д. и т.п.

Для того, чтобы начать разработку, пусть даже и элементарного приложения в стиле «Hello, world!» нам потребуются Android SDK. Самую свежую версию SDK можно скачать с сайта разработчиков, перейдя по этой ссылке:

Для удобства разработчиков Android SDK можно скачать в двух вариантах:

Скачать ADT Bundle
Скачать только Android SDK
В первом случае, в довесок к SDK мы сразу получаем Eclipse с уже настроенным плагином для разработки под Android. Нам же Eclipse пока не нужен (мы ожидаем Delphi for Android), поэтому на скрине я выделил кнопку для загрузки только SDK.

Следующий инструмент без которого нам никак не обойтись — JRE (Java Runtime Environment) — без JRE у нас не даже не установятся инструменты разработчика Android, не говоря уже про разработку. Поэтому переходим по ссылке ниже и качаем установщик для своей ОС:

http://www.oracle.com/technetwork/java/ . 80261.html
Соответственно, устанавливаем инструментарий в обратном порядке, т.е. вначале устанавливаем JRE, а потом запускаем установщик Android SDK. Если Вы всё сделали правильно, то в окне установщика SDK вы увидите следующую информацию:

После окончания установки установщик предложит нам запустить SDK Manager:
Жмем Finish и смотрим, что из себя представляет первый инструмент разработчика Android — SDK Manager.

Используя SDK Manager мы можем загружать и устанавливать необходимые нам API, документацию по SDK, получать доступ к инструментам SDK, например к эмулятору Android-устройств. Выглядит SDK Manager следующим образом:

В окне менеджера мы можем увидеть путь по которому расположен SDK, а также установить или удалить необходимые нам API, узнать какие ресурсы уже установлены. Прежде, чем мы приступим к установке API необходимо сделать небольшой отступление и прояснить некоторые моменты по поводу уровней API (API Level).

API Level представляет собой целочисленное значение, которое однозначно определяет текущую версию API. Каждая новая версия API разрабатывается таким образом, чтобы быть совместимой с предыдущей версией. Из более новой версии API крайне редко удаляются какие-либо элементы и, в основном, удаление производится с целью сохранения надежности и безопасности все платформы Android. Каждая версия Android поддерживает только один уровень API, хотя подразумевается поддержка всех прежних уровней API (API до 1-го уровня).

То есть, грубо говоря, если у Вас есть приложение для Android, собранное с использованием 15 уровня API, что соответствует версии Android 4.0.3, то ваше приложение заработает и в Android 4.3, но врядли запуститься в Android 3.0, которому соответствует API Level 11.

Теперь вернемся в SDK Manager и загрузим необходимые для работы Android API. Я решил установить API Level 17 и 18, что соответствует версиям Android 4.2 и 4.3. Выбираем в менеджере необходимые API и жмем кнопку «Install … packages».

После установки необходимых пакетов в директории, в которую вы устанавливали SDK, появятся новые файлы и поддиректории, например:

Более подробно разбираться с тем для чего предназначены те или иные инструменты разработчика мы будем уже с Delphi XE5, а пока перейдем к следующему шагу.

Android NDK — это набор инструментов, позволяющих разрабатывать приложения под Android на «родном» для вас языке, например, на C/C++ и, уже совсем скоро, на Delphi. Сами же разработчики Android предупреждают о том, что использование NDK в большинстве случаев не идёт на пользу приложениям и, зачастую усложняет исходник приложения, но нам деваться-то некуда — поэтому, единственным более менее простым способом разработки для Android в Delphi является использование этого самого Android NDK.

Читать еще:  Tenorshare ReiBoot Pro 7.3.4.7 + serial key

Скачать NDK можно, перейдя по этой ссылке. На момент написания этой статьи архив Android NDK имел размер порядка 490 Мб, так что придется подождать.

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

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

Заключение
На данный момент мы скачали, установили и настроили все инструменты разработчика Android-приложений, которые будут нам необходимы для работы в Delphi XE5 уже совсем скоро.

Конечно, установщик для RAD Studio XE5 должен будет проделывать все вышеописанные манипуляции с инструментами для Android-разработчиков автоматически, но кто знает под какую версию Android нам захочется разрабатывать приложения и какие уровни API нам будут необходимы? Поэтому ИМХО лучше уж потратить минут 30 времени и настроить все инструменты самостоятельно попутно разобравшись что и где лежит.
Следующий шаг в подготовке к разработке под Android в Delphi — это установка Android NDK.

Ретабоуил Сильвен «Android NDK: руководство для начинающих» ДМК Пресс, 2016 год, 518 стр., 2-е издание (Перевод. с EN) Киселева А. Н.(7,01 мб. pdf )

Вы Java-разработчик для Android, но есть желание изучить создание производительных, низкоуровневых приложений на C и C++, тогда данное руководство для вас. Не секрет, что использование языков C и C++ дают возможность писать быстрые игровые и мультимедийные программы. В представленном руководстве приводится информация по использованию сборки библиотек — NDK (Android Native Development Kit) для разработки приложений под Android и интеграции кода C и C++ с кодом Java посредством Java Native Interfaces. Вы узнаете как правильно программно построить вывод графики и звука, обработку событий, устройств ввода и датчиков, как выводить графику с использованием библиотеки OpenGL ES, а также другие актуальные вопросы. Издание адресовано для разработчикам мобильных приложений — начинающим и более опытным, уже знакомым с использованием Android SDK. Второе издание переработанное и дополненное. ISBN 978-5-97060-394-9

Содержание

Глава 1.
Подготовка окружения 19
Приступая к разработке программ для Android 19
Настройка Windows 20
Установка инструментов разработки для Android в Windows 26
Настройка Mac OS X 31
Установка инструментов разработки для Android в Mac OS X 34
Настройка Linux 40
Установка инструментов разработки для Android в Linux 42
Установка среды разработки Eclipse 47
Эмулятор платформы Android 52
Разработка с действующим устройством на платформе Android 56
Дополнительно о службе ADB 60
В заключение 62

Глава 2.
Создание низкоуровневого проекта для Android 64
Компиляция и развертывание примеров приложений из Android NDK 65
Создание файлов проекта с помощью менеджера Android 68
Компиляция низкоуровневого кода с помощью NDK-Build 71
Сборка и упаковка приложений с помощью Ant 71
Развертывание пакета приложения с помощью Ant 72
Запуск приложения с помощью командной оболочки ADB 73
Дополнительно об инструментах для Android 75
Создание первого низкоуровневого проекта для Android 75
Введение в Dalvik и ART 80
Взаимодействие Java и C/C++ 81
Отладка низкоуровневых приложений для Android 85
Определение настроек NDK для приложения 88
Повседневное использование NDK-GDB 90
Анализ аварийных дампов 91
Настройка проекта Gradle для компиляции низкоуровневого кода 96
В заключение 103

Глава 3.
Взаимодействие Java и C/C++ посредством JNI 104
Инициализация библиотеки JNI 105
Преобразование Java-строк в низкоуровневые строки 114
Кодирование строк в низкоуровневом коде 121
Поддержка строк в JNI API 122
Передача элементарных типов Java в низкоуровневый код 124
Ссылки на Java-объекты из низкоуровневого кода 128
Локальные ссылки 133
Глобальные ссылки 135
Слабые ссылки 135
Обработка Java-массивов 137
Элементарные массивы 148
Массивы объектов 151
Возбуждение и проверка Java-исключений 152
Выполнение кода при наличии исключения 156
API обработки исключений 157
В заключение 158

Глава 4.
Вызов функций на языке Java из низкоуровневого кода 160
Обратный вызов Java-методов из низкоуровневого кода 161
Дополнительно о JNI Reflection API 168
Отладка JNI 170
Синхронизация Java с низкоуровневыми потоками выполнения 171
Синхронизация программного кода на Java и C/C++ с помощью мониторов JNI 183
Присоединение и отсоединение потоков выполнения 184
Низкоуровневая обработка растровых изображений 185
Регистрация низкоуровневых методов вручную 200
JNI в C и C++ 201
В заключение 202

Глава 5.
Создание исключительно низкоуровневых приложений 203
Создание низкоуровневого визуального компонента 204
Подробнее о низкоуровневом связующем модуле 211
Обработка событий визуального компонента 214
Доступ к окну из низкоуровневого кода 225
Измерение времени в низкоуровневом коде 236
В заключение 247

Глава 6.
Отображение графики средствами OpenGL ES 248
Инициализация OpenGL ES 249
Конвейер OpenGL 256
Чтение текстур с помощью диспетчера ресурсов 258
Дополнительно об Asset Manager API 262
Подробнее о текстурах 278
Рисование двухмерных спрайтов 280
Массивы вершин и буферные объекты с вершинами 301
Эффект частиц 303
Программирование шейдеров на языке GLSL 314
Адаптация графики для разных разрешений 316
В заключение 324

Глава 7.
Проигрывание звука средствами OpenSL ES 325
Инициализация OpenSL ES 327
Еще о философии OpenSL ES 333
Воспроизведение музыкальных файлов 334
Воспроизведение звуков 342
Обработка событий в очереди звуков 355
Важность низкой задержки в Android 356
Запись звука 358
В заключение 362

Глава 8.
Устройства ввода и датчики 363
Обработка событий касания 364
Обработка событий от клавиатуры, клавиш направления (D-Pad) и трекбола 378
Проверка датчиков 385
Дополнительно о датчиках 400
В заключение 401

Глава 9.
Перенос существующих библиотек на платформу Android 402
Разработка с применением стандартной библиотеки шаблонов 403
Перенос Box2D на платформу Android 420
Мир Box2D 441
Подробнее об определении столкновений 442
Режимы столкновений и фильтрация 444
Дополнительные ресурсы, посвященные Box2D 446
Компиляция Boost на платформе Android 447
Мастерство владения файлами Makefile 459
Переменные в файлах Makefile 459
Инструкции в файлах сборки 463
Архитектуры процессоров (ABI) 467
Дополнитель 471

Глава 10.
Интенсивные вычисления на RenderScript 472
Что такое RenderScript? 473
Выполнение встроенной функции 474
Создание собственного ядра 486
Объединение сценариев 495
В заключение 504
Послесловие 505
Что мы узнали 505
Куда двигаться дальше 506
Где искать помощь 507
Это лишь начало 508
Предметный указатель 509

Введение в 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?

Google рекомендует прибегать к использованию NDK только в редчайших случаях. Зачастую это такие случаи:

  • Нужно увеличить производительность (например, сортировка большого объема данных);
  • Использовать стороннюю библиотеку. Например, много уже чего написано на С/С++ языках и нужно просто заиспользовать существующий материал. Пример таких библиотек, как, Ffmpeg, OpenCV;
  • Программирование на низком уровне (например, всё что выходит за рамки Dalvik);

Что такое 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


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

  • *env – указатель на интерфейс;
  • оbj – ссылка на объект в котором описан нативный метод;
  • i and s – передаваемые аргументы;
Читать еще:  Тормозит Андроид: причины и способы устранения проблемы

Примитивные типы копируются между VM и нативным кодом, а объекты передаются по ссылке. VM обязана отслеживать все ссылки которые передаются в нативный код. Все переданные ссылки в нативный код не могут быть освобождены GC. Но нативный код в свою очередь должен информировать VM о том что ему больше не нужны ссылки на переданные объекты.

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

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

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

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

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

  • снижение производительности;
  • в большинстве функций C библиотек очень и очень трудно защитится от ошибок.

JNI позволяет использовать Java Exception. Большинство JNI функций возвращают код ошибок а не сам Exception, и поэтому приходится обрабатывать сам код, а в Java уже выбрасывать Exception. В JNI следует проверять код ошибки вызываемых функций и после них следует вызвать ExceptionOccurred(), которая в свою очередь возвращает объект ошибки:

Например, некоторые функции 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

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

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

Функции JNI

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

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

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

Разберём построчно:

  • JavaVM – предоставляет интерфейс для вызова функций, которые позволяют создавать и уничтожать JavaVM;
  • JNIEnv – обеспечивает большинство функций JNI;
  • JavaVMInitArgs – аргументы для JavaVM;
  • JavaVMOption – опции для JavaVM;

Метод JNI_CreateJavaVM() инициализирует JavaVM и возвращает на неё указатель. Метод JNI_DestroyJavaVM() выгружает созданную JavaVM.

Потоки

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

Первые шаги

Структура проекта у вас должна выглядеть следующим образом:

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

Для того, чтобы создать нативный проект, нужно создать обычный Android проект и проделать следующие шаги:

  • В корне проекта нужно создать папку jni, в которую поместить исходники нативного кода;
  • Создать файл Android.mk, который будет собирать проект;
  • Создать файл Application.mk, в котором описываются детали сборки. Он не является обязательным условием, но позволяет гибко настроить сборку;
  • Создать файл ndk-build, который будет запускать процесс сборки (тоже не является обязательным).

Android.mk

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

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

Рассмотрим детально:

  • LOCAL_PATH := $(call my-dir) – функция call my-dir возвращает путь папки в которой вызывается файл;
  • include $(CLEAR_VARS) – очищает переменные которые использовались до этого кроме LOCAL_PATH. Это необходимо так как все переменные являются глобальными, потому что сборка происходит в контексте одного GNU Make;
  • LOCAL_MODULE – имя выходного модуля. В нашем примере имя выходной библиотеки установлено как NDKBegining, но после сборки в папке libs создадутся библиотеки с именами libNDKBegining. Android добавляет к названию префикс lib, но в java коде при подключении вы должны указывать название библиотеки без префикса (то есть названия должны совпадать с установленными в make файлах);
  • LOCAL_SRC_FILES – перечисление исходных файлов из которых следует создать сборку;
  • include $(BUILD_SHARED_LIBRARY) – указывает тип выходного модуля.

В Android.mk можно определить свои переменные, но они не должны иметь такой синтаксис: LOCAL_, PRIVATE_, NDK_, APP_, my-dir. Google, рекомендует называть свои переменные, как MY_. Например:

Application.mk

NDK-BUILDS

Ndk-build из себя представляет обёртку GNU Make. После 4-й версии ввели флаги для ndk-build:

  • clean – очищает все сгенеренные бинарные файлы;
  • NDK_DEBUG=1 – генерирует отладочный код;
  • NDK_LOG=1 – показывает лог сообщений (используется для отладки);
  • NDK_HOST_32BIT=1 – Android имеет средства для поддержки 64-х битных версий утилит (например NDK_PATHtoolchainsmipsel-linux-android-4.8prebuiltwindows-x86_64 и т.д.);
  • NDK_APPLICATION_MK — указывается путь к Application.mk.

В 5-й версии NDK был введён такой флаг как NDK_DEBUG. Если он установлен в 1 то создаётся отладочная версия. Если флаг не установлен то ndk-build по умолчанию проверяет стоит ли атрибут android_debuggable=«true» в AndroidManifest.xml. Если вы используете ndk выше 8-й версии, то Google не рекомендует использовать атрибут android:debuggable в AndroidManifest.xml (потому что если вы используете «ant debug» или строите отладочную версию с помощью ADT плагина то они автоматически добавляют флаг NDK_DEBUG=1).

По умолчанию устанавливается поддержка 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.

Обёртка для работы с файлами

Я для таких вещей всегда обёртки делаю. В принципе можете прям тут вызывать AAssetManager_open , а потом с помощью AAsset_read читать. Но тогда на той же винде не будет это работать, так как под WIN надо работать с помощью Win API и FILE. Поэтому я создал свой класс c нужным директивами препроцессора.

В зависимости от того, под какую платформу билдите, те методы и будут включены в сборку. Если под Андройд, то будет работать с AAsset, если под Win, то с FILE. Ну и реализация методов именно под Андройд:

В логе увидите что-то такое:

Теперь вы можете работать с ресурсами напрямую из C++ кода. Можете скачать исходники AndroidNDK3.rar.

Ссылка на основную публикацию
Статьи c упоминанием слов:
Adblock
detector