Для разработки приложений под ОС Android, Google предоставляет два пакета разработки: SDK и NDK. Про SDK существует много статей, книжек, а так же хорошие guidelines от Google. Но про NDK даже сам Google мало что пишет. А из стоящих книг я бы выделил только одну, Cinar O. - Pro Android C++ with the NDK – 2012 .
Эта статья ориентирована на тех, кто ещё не знаком (или мало знаком) с Android NDK и хотел бы укрепить свои знания. Внимание я уделю JNI, так как мне кажется начинать нужно именно с этого интерфейса. Так же, в конце рассмотрим небольшой пример с двумя функциями записи и чтения файла.
Двоичная совместимость или же бинарная совместимость – вид совместимости программ, позволяющий программе работать в различных средах без изменения её исполняемых файлов.
JNI таблица, организована как таблица виртуальных функций в С++. VM может работать с несколькими такими таблицами. Например, одна будет для отладки, вторая для использования. Указатель на JNI интерфейс действителен только в текущем потоке. Это значит, что указатель не может гулять с одного потока в другой. Но нативные методы могут быть вызваны из разных потоков. Пример:
Jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (JNIEnv *env, jobject obj, jint i, jstring s) { const char *str = (*env)->GetStringUTFChars(env, s, 0); (*env)->ReleaseStringUTFChars(env, s, str); return 10; }
Jclass clazz;
clazz = (*env)->FindClass(env, "java/lang/String");
//ваш код
(*env)->DeleteLocalRef(env, clazz);
Глобальные ссылки остаются пока они явно не будут освобождены. Что бы зарегистрировать глобальную ссылку следует вызвать метод NewGlobalRef. Если же глобальная ссылка уже не нужна, то её можно удалить методом DeleteGlobalRef:
Jclass localClazz; jclass globalClazz; localClazz = (*env)->FindClass(env, "java/lang/String"); globalClazz = (*env)->NewGlobalRef(env, localClazz); //ваш код (*env)->DeleteLocalRef(env, localClazz);
Jthrowable ExceptionOccurred(JNIEnv *env);
Например, некоторые функции JNI доступа к массивам не возвращают ошибки, но могут вызвать исключения ArrayIndexOutOfBoundsException или ArrayStoreException.
Java Type | Native Type | Description |
---|---|---|
boolean | jboolean | unsigned 8 bits |
byte | jbyte | signed 8 bits |
char | jchar | unsigned 16 bits |
short | jshort | signed 16 bits |
int | jint | signed 32 bits |
long | jlong | signed 64 bits |
float | jfloat | 32 bits |
double | jdouble | 64 bits |
void | void | N/A |
Для того, чтобы создать нативный проект, нужно создать обычный Android проект и проделать следующие шаги:
Пример минимальной конфигурации:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= NDKBegining
LOCAL_SRC_FILES:= ndkBegining.c
include $(BUILD_SHARED_LIBRARY)
Рассмотрим детально:
По умолчанию устанавливается поддержка 64-х разрядной версии утилит, но вы можете принудительно собрать только для 32-х установив флаг NDK_HOST_32BIT=1. Google, рекомендует всё же использовать 64-х разрядность утилит для повышения производительности больших программ.
Сейчас с этим всё намного проще. Идёте по этой ссылке . Качаете Eclipse ADT Bundle в котором уже есть всё то что необходимо для сборки.
Теперь можете запускать. В директории bin/classes будут лежать ваши header файлы.
Далее копируем эти файлы в jni директорию нашего нативного проекта. Вызываем контекстное меню проекта и выбираем пункт Android Tools – Add Native Library. Это позволит нам использовать jni.h функции. Дальше вы уже можете создавать cpp файл (иногда Eclipse его создаёт по умолчанию) и писать тела методов, которые уже описаны в header файле.
Пример кода я не стал добавлять в статью, чтобы не растягивать её. Пример вы можете посмотреть/скачать с github .
Теги:
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 или "мультиплатформенным кодом".
При использовании компилируемого языка программирования исходный код компилируется непосредственно в машинный код для центрального процессора, а не в промежуточное представление, такое, как в языке Java. Таким образом разработчики приложений могут создавать приложения с оптимальной производительностью для различных Android-устройств. Фрагменты компилируемого кода могут быть структурированы в рамках одной разделяемой библиотеки, функции из которой могут вызываться из кода на языке Java. Отдельная разделяемая библиотека должна создаваться для каждой из поддерживаемых архитектур центральных процессоров. Большая часть ее исходного кода при этом может оставаться неизменной. Скомпилированные разделяемые библиотеки должны быть добавлены в файл.apk вашего приложения. С учетом всего вышесказанного, фундаментальная модель приложений Android не изменится.
Android NDK является инструментарием, позволяющим реализовывать части приложения для Android на таких компилируемых языках программирования, как C и C++ и содержит библиотеки для управления активностями и доступа к физическим компонентам устройства, таким, как различные сенсоры и дисплей.
Android NDK интегрирован с инструментами из набора компонентов для разработки программного обеспечения (Android SDK), а также с интегрированной средой разработки Android Studio или устаревшей средой разработки Eclipse ADT. Однако, NDK не может использоваться отдельно.
Сердцем пакета Android NDK является сценарий ndk-build , который отвечает за автоматический обход файлов проекта Android (разработка каждого нового приложения для Android с помощью интегрированной среды разработки, такой, как Android Studio или Eclipse начинается с создания файлов нового проекта) и собирает информацию о том, какие компоненты нужно компилировать. Данный сценарий также ответственен за генерацию бинарных файлов и копирование этих бинарных файлов в директорию файлов проекта приложения.
Мы можем использовать ключевое слово native для того, чтобы компилятор знал, что данный фрагмент реализован в рамках компилируемого кода. Например:
Public native int numbers(int x, int y);
Также в процессе сборки проекта создаются разделяемые библиотеки (Native Shared Libraries, с расширением.so) и статические библиотеки (Native Shared Libraries, с расширением.a), которые могут связываться с другими библиотеками. Бинарный интерфейс приложения (Application Binary Interface, ABI) использует разделяемые библиотеки с расширением.so для исполнения машинного кода в системе в процессе работы приложения.
Весь компилируемый код исполняется посредством интерфейса под названием Java Native Interface (JNI), который позволяет связать друг с другом компоненты на языках Java и C/C++.
Для сборки проекта с помощью сценария ndk-build нам придется создать два файла: Android.mk и Application.mk . Оба этих файла должны размещаться в директории JNI . Файл Android.mk описывает модуль и его имя, флаги сборки, используемые библиотеки файлы исходного кода, которые должны компилироваться, а файл Application.mk - бинарные модули, необходимые для работы приложения.
Android NDK поставляется в формате самораспаковывающегося архива. По этой причине нам придется лишь установить бит исполнения и распаковать его:
$ chmod +x android-ndk-r10c-linux-x86_64.bin $ ./android-ndk-r10c-linux-x86_64.bin
В результате компоненты NDK будут сохранены в текущей рабочей директории.
Распаковка в ручном режиме
Ввиду того, что файл с расширением.bin является ничем иным, как самораспаковывающимся архивом формата 7-Zip, мы можем извлечь его содержимое вручную с помощью следующей команды:
$ 7za x -o/путь/к/целевой/директории/ android-ndk-r10c-linux-x86_64.bin
Пакет с компонентами архиватора 7-Zip доступен из официального репозитория Ubuntu и может быть установлен, к примеру, с помощью команды apt-get:
$ sudo apt-get install p7zip-full
Мы можем установить Android NDK с помощью компонента SDK Manager непосредственно из Android Studio.
Для этого после открытия проекта следует осуществить переход в рамках главного меню окна Tools > Android > SDK Manager . После этого нужно установить флажки напротив названий компонентов LLDB , CMake и NDK . Далее нужно просто применить изменения с помощью соответствующей кнопки.
После настройки Android Studio мы можем создать новый проект с поддержкой языков программирования C/C++. Однако, если нам понадобится добавить или импортировать существующий код на этих языках в проект Android Studio, мы будем вынуждены выполнить описанные ниже действия.
Для начала следует создать новые файлы исходного кода с использованием упомянутых языков программирования и добавить их в проект, открытый в Android Studio. Мы можем пропустить этот шаг, если в проекте уже имеются подобные файлы или нам нужно импортировать в него предварительно скомпилированную библиотеку.
Сценарий сборки CMake позволяет сообщить одноименной системе сборки о том, как нужно осуществлять компиляцию файлов исходного кода и сборку результирующей бинарной библиотеки. Этот файл также необходим для импорта и связывания с нашей библиотекой существующих или поставляемых в комплекте NDK библиотек. Мы также можем без каких-либо последствий пропустить данный шаг в том случае, если в комплекте поставки нашей существующей бинарной библиотеки уже содержится файл сценария сборки CMakeLists.txt или она использует компонент ndk-build и в ее комплекте поставки содержится файл сценария сборки Android.mk .
Далее следует сообщить Gradle о существовании нашей бинарной библиотеки путем указания пути к файлу сценария сборки CMake или ndk-build . Gradle использует указанный сценарий сборки для импорта исходного кода в проект Android Studio и упаковки результирующей бинарной библиотеки (файла с расширением.so) в файл пакета формата APK.
Важное замечание: если в рамках проекта используется устаревший инструмент ndkCompile , нам придется открыть файл build.poperties и удалить из него следующую строку кода перед настройкой Gradle с целью использования CMake или ndk-build:
Android.useDeprecatedNdk = true
Теперь мы можем собрать и выполнить наше приложение путем нажатия на кнопку Run . Gradle будет рассматривать процесс CMake или ndk-build в качестве зависимости, которая должна быть собрана, осуществлять сборку бинарной библиотеки и упаковывать ее в файл пакета формата APK.
После запуска приложения на устройстве или в эмуляторе мы сможем использовать функции различных интегрированных сред разработки, таких, как Android Studio, для его отладки.
Все это свидетельствует о важности Android NDK для разработчиков приложений для платформы Android. Например, данный набор программных компонентов позволяет создателям игровых движков лучше оптимизировать версии своих продуктов для Android, в результате чего они будут выдавать более впечатляющие графические эффекты, затрачивая на них меньше системных ресурсов.
Процесс создания простого приложения на основе Android NDK не связан с какими-либо сложностями. Однако, каждому разработчику следует уяснить один важный момент: набор программных компонентов Android NDK разрабатывался для использования в определенных случаях и не должен применяться при разработке любых приложений.
Android NDK может как помочь в процессе разработки приложения, так и максимально осложнить его. Не является тайной и то, что использование бинарного кода на платформе Android в некоторых случаях не приводит к заметному повышению производительности приложения (хотя в большинстве случаев его производительность все же повышается), но при этом оно в любом случае усложняет его код. Обычно повышение производительности приложений достигается благодаря задействованию кода со специфичными для используемого центрального процессора инструкциями. Но в общем случае рекомендуется использовать NDK лишь тогда, когда производительность приложения является критически важным параметром, а не тогда, когда разработчику удобнее писать код на языках C/C++.
В качестве заключения следует сказать о том, что не существует незыблемых правил, регламентирующих возможные случаи использования NDK, поэтому вам всегда стоит обращаться к своими знаниям, опыту и интуиции.
Android NDK is a reliable and effective toolset specially designed for Android and developers who need to implement parts of their app using programming languages such as C++ or C#.
However, before using Android NDK you need to be a great connoisseur of these native-code languages and make sure that your computer meets all the system requirements, otherwise you can not benefit off of all the features that the toolset comes with.
Generally speaking, you can get loads of C or Java script for the current application, but when using Android NDK you can speed up your project’s development process, as well as keep changes synchronized between Android and non-Android projects.
As an advanced developer, when using Android NDK you need to balance its benefits against its drawbacks. Hence, you should use it only if it is essential when developing a new app and you do need this component.
Still, you don’t have to assume that you can increase your application’s performance just because you are using native code. Just check the requirements and view if the Android framework APIs provide you with the main functionality you need.
That being said, when you are sure that Android NDK is a component that you truly need to run and develop your applications, you can unzip it and place it in an appropriate directory. After that, variables like ‘android_log_print’ and ‘sample_ndk’ will be available inside your project.
Plus, the NDK package provides you with the right tools so you can work efficiently with your scripts, without having to handle all the CPU and ABI details.
Taking into consideration that Android NDK is specially intended for Java developers, it provides them with helpful classes that notifies their native code of any activity lifecycle callbacks. Still, the most interesting part at this toolset is that it enables them to embed native libraries into an application package file, which can be deployed on Android devices.