Для разработки приложений под ОС 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 интерфейс действителен только в текущем потоке. Это значит, что указатель не может гулять с одного потока в другой. Но нативные методы могут быть вызваны из разных потоков. Пример:
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; }
- *env – указатель на интерфейс;
- оbj – ссылка на объект в котором описан нативный метод;
- i and s – передаваемые аргументы;
Примитивные типы копируются между VM и нативным кодом, а объекты передаются по ссылке. VM обязана отслеживать все ссылки которые передаются в нативный код. Все переданные ссылки в нативный код не могут быть освобождены GC. Но нативный код в свою очередь должен информировать VM о том что ему больше не нужны ссылки на переданные объекты.
Локальные и глобальные ссылки
JNI делит ссылки на три типа: локальные, глобальные и слабые глобальные ссылки. Локальные действительны пока не завершиться метод. Все Java объекты которые возвращает функции JNI являются локальными. Программист должен надеется на то что VM сама подчистит все локальные ссылки. Локальные ссылки доступны лишь в том потоке в котором были созданы. Однако если есть необходимость то их можно освобождать сразу методом JNI интерфейса DeleteLocalRef:
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);
Обработка ошибок
JNI не проверяет ошибки такие как NullPointerException, IllegalArgumentException. Причины:
- снижение производительности;
- в большинстве функций C библиотек очень и очень трудно защитится от ошибок.
JNI позволяет использовать Java Exception. Большинство JNI функций возвращают код ошибок а не сам Exception, и поэтому приходится обрабатывать сам код, а в Java уже выбрасывать Exception. В JNI следует проверять код ошибки вызываемых функций и после них следует вызвать ExceptionOccurred(), которая в свою очередь возвращает объект ошибки:
jthrowable ExceptionOccurred(JNIEnv *env);
Например, некоторые функции JNI доступа к массивам не возвращают ошибки, но могут вызвать исключения ArrayIndexOutOfBoundsException или ArrayStoreException.
Примитивные типы JNI
В JNI существуют свои примитивные и ссылочные типы данных.
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 |
Ссылочные типы JNI
Модифицированный UTF-8
JNI использует модифицированную кодировку UTF-8 для представления строк. Java в свою очередь использует UTF-16. UTF-8 в основном используется в С, потому что он кодирует u0000 в 0xc0, вместо привычной 0x00. Изменённые строки кодируются так, что последовательность символов, которые содержат только ненулевой ASCII символы могут быть представлены с использованием только одного байта.
Функции JNI
Интерфейс JNI содержит в себе не только собственный набор данных, но и свои собственные функции. На их рассмотрение уйдёт много времени, так как их не один десяток. Ознакомится с ними вы сможете в официальной документации.
Пример использования функций JNI
Небольшой пример, что бы вы усвоили пройденный материал:
#include //... JavaVM *jvm; JNIEnv *env; JavaVMInitArgs vm_args; JavaVMOption* options = new JavaVMOption[1]; options[0].optionString = "-Djava.class.path=/usr/lib/java"; vm_args.version = JNI_VERSION_1_6; vm_args.nOptions = 1; vm_args.options = options; vm_args.ignoreUnrecognized = false; JNI_CreateJavaVM(&jvm, &env, &vm_args); delete options; jclass cls = env->FindClass("Main"); jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V"); env->CallStaticVoidMethod(cls, mid, 100); jvm->DestroyJavaVM();
Разберём построчно:
- 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) include $(CLEAR_VARS) LOCAL_MODULE := NDKBegining LOCAL_SRC_FILES := ndkBegining.c include $(BUILD_SHARED_LIBRARY)
Рассмотрим детально:
- 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_. Например:
MY_SOURCE := NDKBegining.c Что бы обратится к переменной: $(MY_SOURCE) Переменные, так же можно конкатенировать, например: LOCAL_SRC_FILES += $(MY_SOURCE)
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 String nativeGetStringFromFile(String path) throws IOException; native void nativeWriteByteArrayToFile(String path, byte[] b) throws IOException;
Перед методом следует поставить зарезервированное слово «native». Таким образом компилятор знает, что это точка входа в JNI. Эти методы нам нужно реализовать в С/С++ файле. Так же Google рекомендует начинать именовать методы со слова nativeХ, где Х – реальное название метода. Но перед тем как реализовывать эти методы вручную, следует сгенерировать header файл. Это можно сделать вручную, но можно использовать утилиту javah, которая находится в jdk. Но пойдём дальше и не будет использовать её через консоль, а будем это делать при помощи стандартных средств Eclipse. Теперь можете запускать. В директории bin/classes будут лежать ваши header файлы. Далее копируем эти файлы в jni директорию нашего нативного проекта. Вызываем контекстное меню проекта и выбираем пункт Android Tools – Add Native Library. Это позволит нам использовать jni.h функции. Дальше вы уже можете создавать cpp файл (иногда Eclipse его создаёт по умолчанию) и писать тела методов, которые уже описаны в header файле. Пример кода я не стал добавлять в статью, чтобы не растягивать её. Пример вы можете посмотреть/скачать с github. 595 190.3k 595
Most of the modern and current smartphones are built on Android phone technology. That means that if you want to have a tool uploaded on this technology then you must use compatible tools to develop and create the apps. This is one such application that comes in handy to make sure you are able to use various programming languages to develop the app. This is a tool that codes the text details using the command line prompt to handle this tool. In fact, without this, you will not even start the process of handling the tool.
This SDK is helpful for Android developers. Although you can also get scripts for both Java and C programming language. The synchronization settings of this tool are such that you can deal with both the Android and other phone technology. The fact that you use native codes in designing the projects then you are sure that you can handle both the single and multiple scripts. In short, you can say that this is a native development toolkit or an Android development kit and also a toolchain.
Android NDK is licensed as freeware for PC or laptop with Windows 32 bit and 64 bit operating system. It is in sdk category and is available to all software users as a free download.
Share |
Give a rating |
Author |
Last Updated On |
January 10, 2019 |
Runs on |
Total downloads |
742 |
License |
Free |
File size |
498,28 MB |
Filename |
android-ndk-r18b-windows-x86.zip android-ndk-r18b-windows-x86_64.zip |
Октябрь 24th, 2010 Данил
Android NDK.
Android NDK является инструментом для Android SDK, который позволяет разработчикам приложений Android построить производительность критически важных частей своих приложений в машинном коде. Он предназначен для использования только в сочетании с Android SDK, так что если вы еще не установили последние Android SDK, пожалуйста, сделайте это до загрузки НДК. Кроме того, следует почитать , что такое Что такое Android NDK? , чтобы получить понимание того, что НДК предложение и будет ли это полезно для вас.
Выберите скачать пакет, который подходит для вашего компьютера.
Platform | Package | Size | MD5 Checksum |
Windows | android-ndk-r4b-windows.zip | 45792835 bytes | e397145e155a639be53ee4b6db8ad511 |
Mac OS X (intel) | android-ndk-r4b-darwin-x86.zip | 50586041 bytes | 41dbd54335fb828ee408eab17103a1b0 |
Linux 32/64-bit (x86) | android-ndk-r4b-linux-x86.zip | 49464776 bytes | 2deabcb125c219b34140975b710f00ec |
Изменения В нижеследующих разделах предоставлена информация и заметки о последовательных релизах НДК, по обозначению номера ревизии. Android NDK, Revision 4b Android NDK, Версия 4, б (июнь 2010): Включает в себя исправления для нескольких вопросов в НДК создания и отладки скриптов — если вы используете NDK R4, мы рекомендуем скачать R4b NDK . Для получения подробной информации изменений в этом выпуске, читать CHANGES.txt документа, включенного в скачанный NDK пакет.Общие указания: •Предоставляет упрощенную систему сборки с помощью новой НДК-строителя команд. •Добавлена поддержка для легкой отладки сгенерированного кода машины по производству устройств через новую команду НДК-GDB. •Добавление новых Android-конкретных ABI для ARM основе процессорных архитектур, armeabi-v7a. Новые ABI расширяют существующие armeabi ABI включенные в этот набор инструкции процессора расширений: Thumb-2 инструкции аппаратных VFP FPU инструкции (VFPv3-D16) Дополнительная поддержка ARM расширений SIMD (неон) GCC и встроенные VFPv3-D32. Поддержка устройств, таких как Verizon Droid от Motorola, Google Nexus-первых, и другие. •Добавление новых cpufeatures статической библиотеки (с источниками), что позволяет вашему приложению обнаружить процессор хост-устройства во время выполнения. В частности, приложения могут проверить на ARMv7-поддержку, а также VFPv3-D32 и NEON поддержки, а затем предоставить отдельные пути коду по мере необходимости. •Добавляет пример приложения, привет-неон, который иллюстрирует, как использовать cpufeatures библиотеку, чтобы проверить процессор, а затем обеспечить оптимизированный код, используя NEON instrinsics, если поддерживается процессором. • Позволяет генерировать машинный код для одной или обеих наборов инструкций поддерживаемых НДК. Например, вы можете построить для ARMv5 и ARMv7-архитектуры в то же время всё сохранится .
АПК приложения. •Для того чтобы приложения были доступны для пользователей, только если их устройства способны запускать их, Android Market имеет фильтры приложений, основанных на информации, набор инструкций, включенных в приложения — с вашей стороны необходимы действия для того, чтобы сделать фильтрацию. Кроме того, во время установки система Android проверяет также и себя ,приложения и позволяет продолжить установку только тогда, когда приложение предоставляет библиотеку, которая составляется для архитектуры процессора устройства. •Добавлена поддержка для Android 2.2, в том числе новых стабильных API для доступа к пикселям буферов растровых объектов из машинного кода. Android NDK, Revision 3 Android NDK, Версия 3 (март 2010)Общие указания: • Добавляет OpenGL ES 2.0 встроенную поддержку библиотек. • Добавляет пример приложения, привет-gl2, который иллюстрирует использование OpenGL ES 2.0 и верхние шейдеры фрагментов. • набор инструментов исполняемых файлов были обновлены для этой версии с GCC 4.4.0, которая должна генерировать чуть более компактный и эффективный машинный код, чем предыдущий (4.2.1). НДК также по-прежнему обеспечивает 4.2.1 двоичные файлы, которые можно дополнительно использовать для создания машинного кода. Android NDK, Revision 2 Android NDK, Пересмотр 2 (сентябрь 2009) Первоначально выпущенный как «Android 1,6 NDK, выпуск 1».Общие указания: • Добавляет OpenGL ES 1.1 встроенная поддержка библиотек. •Добавляет пример приложения, Сан —Анджелесе, что делает 3D-графику через родной OpenGL ES API, в то время как управление жизненным циклом деятельности с объектом GLSurfaceView. Android NDK, Revision 1 Android NDK, Пересмотр 1 (июнь 2009) Первоначально выпущенный как «Android 1,5 NDK, выпуск 1».Общие указания: •Включает в себя компилятор поддержки (ССЗ) для ARMv5TE инструкции, в том числе Thumb- инструкции. •Включает в себя системы заголовков для стабильной родной API, документацию и примеры приложений.
- Автор: Данил
- Распечатать
Оцените статью:
- 5
- 4
- 3
- 2
- 1
(0 голосов, среднее: 0 из 5) Поделитесь с друзьями! Опубликовано в Системные программы : Windows 7 – секреты, объем 4 Гб в Windows, уроки по windows « Установка и удаление плагина ADT. Что такое Android NDK. »
Добавить комментарий
Не отвечать
- There’s a new open source Cardboard SDK for iOS and Android NDK that offers a streamlined API, improved device compatibility, and built-in viewer profile QR code scanning. A corresponding Unity package (SDK) is planned for a future release. We recommend that all developers actively building for Google Cardboard migrate (iOS, Android NDK) to the new Cardboard SDK.
October 15, 2019 update:
- The Daydream View VR headset is no longer available for purchase. However, you can continue to use the existing Google VR SDK to update and distribute your apps to the Google Play Store, and make them available to users in the Daydream app.
Terms & Conditions
By downloading the Google VR SDK, you agree that the Google APIs Terms of Service govern your use thereof.
Google VR NDK
The Google VR NDK for Android lets you create VR apps for Cardboard or Daydream.
To get the Google VR NDK, do the following:
- Make sure you have the latest version of the Android NDK installed.
- Download or clone the
gvr-android-sdk
GitHub repository. - Extract the NDK .so files by running:
./gradlew :extractNdk
After completing these steps, you’ll find the header files in gvr-android-sdk/libraries/headers
and the extracted .so files in gvr-android-sdk/libraries/jni
. See the Getting Started guide for examples of linking against these files.
Google VR code samples for Android
The Android NDK code samples for Cardboard and Daydream are in the samples
directory of the repository. See the Getting Started guide for more information on building the demos.
Приложения на Android, как правило, написаны на языке Java, с его элегантными объектно-ориентированным дизайном. Однако в случае, если необходимо преодолеть ограничения языка Java, такие как управление памятью и производительность, посредством программирования непосредственно в нативный интерфейс Android. Для этих целей, помимо Android SDK, Android предоставляет Native Developmemt Kit (NDK), реализующий поддержку разработки на C/C++.
NDK предоставляет все инструменты (компиляторы, библиотеки и заголовочные файлы) для создания приложений, которые получают доступ к устройству нативно. Нативный код обеспечит высокую производительность там, где Java имеет ограничения. С помощью NDK также можно управлять нативными процессами и физическими компонентами устройства, такими как датчики и сенсорный ввод. Кроме того, NDK может быть полезен в следующих случаях:
- Извлечение дополнительной производительности из устройства, чтобы добиться низкой задержки или запуска приложений с интенсивными вычислениями, таких как игры или физическое моделирование.
- Использование своих библиотек или библиотек других разработчиков, написанных на C/C++.
При сборке APK-файла, Gradle компилирует код на C/C++ в нативную библиотеку, после чего добавляет в APK-файл. Затем ваш код на Java сможет обращаться к библиотеке и её методам через инфраструктуру JNI (Java Native Interface).
Поддержка NDK осуществляется, начиная с версии Android Studio 2.2 и выше. Чтобы использовать NDK в своём приложении, нужно установить его. Для этого в Android Studio нужно открыть Configure и выбрать SDK Manager.
В открывшемся окне на вкладке SDK Tools нужно поставить галочки напротив выделенных элементов. После этого достаточно будет нажать Apply и Android Studio начнёт загрузку и установку.
- CMake — утилита внешней сборки, которая работает вместе с Gradle для создания нативной библиотеки. Этот компонент не нужен в случае, если используется ndk-build.
- LLDB — отладчик, который Android Studio использует для отладки нативного кода.
- NDK — собственно набор инструментов для написания нативного кода на C/C++.
Теперь можно перейти к созданию нового проекта. В Configure your new project поставьте галочку Include C++ Support.
Затем идёт стандартная процедура создания проекта, заполняем все поля так, как хотим, после чего попадаем на экран Customize C++ Support.
В этом окне можно настроить следующие параметры:
- C++ Standard — в раскрывающемся списке выбирается стандарт C++, который будет использоваться в приложении. Вариант Toolchain Default использует настройки CMake по умолчанию.
- Exceptions Support — флажок, определяющий, нужно ли включить поддержку обработки исключений C++. Если включить флажок, то Android Studio добавит флаг -fexceptions в cppFlags в файле build.gradle уровня модуля, который Gradle передает в CMake.
- Runtime Type Information Support — флажок, определяющий, нужно ли включить поддержку RTTI (Runtime Type Information — механизм, который определяет тип переменной или объекта во время выполнения программы). Если включить флажок, Android Studio добавит флаг -frtti в cppFlags в файле build.gradle уровня модуля, который Gradle передает в CMake.
После настройки Android Studio соберёт проект. Перейдя в список файлов, можно увидеть новые файлы, созданные для нативной библиотеки.
В папке cpp можно разместить все исходные файлы, заголовочные файлы и готовые библиотеки, которые нужно добавить в приложение.
Нельзя просто так взять и вызвать какой-либо из нативных методов с помощью Java-кода, для этого нужно реализовать метод, который будет вызывать особым образом. В качестве примера автоматически создаётся файл native-lib.cpp. Он предоставляет метод stringFromJNI(), который возвращает в приложение строку «Hello from C++«.
#include #include NewStringUTF(hello.c_str()); }
Известная проблема: Android Studio в настоящее время показывает только заголовочные файлы, которые соответствуют исходным файлам — даже если вы укажете другие заголовочные файлы в скрипте CMake.
В External Build Files можно увидеть скрипт сборки CMake или ndk-build. Аналогично тому, как build.gradle сообщает Gradle о том, как собрать приложение, CMake и ndk-build требуют, чтобы скрипт сборки знал, как создать свою нативную библиотеку. Для новых проектов Android Studio создаёт файл CMakeList.txt и помещает его в корневой каталог модуля. В данном случае, по умолчанию он выглядит следующим образом:
# For more information about using CMake with Android Studio, read the # documentation: https://d.android.com/studio/projects/add-native-code.html # Sets the minimum version of CMake required to build the native library. cmake_minimum_required(VERSION 3.4.1) # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. add_library( # Sets the name of the library. native-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). src/main/cpp/native-lib.cpp ) # Searches for a specified prebuilt library and stores the path as a # variable. Because CMake includes system libraries in the search path by # default, you only need to specify the name of the public NDK library # you want to add. CMake verifies that the library exists before # completing its build. find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log ) # Specifies libraries CMake should link to your target library. You # can link multiple libraries, such as libraries you define in this # build script, prebuilt third-party libraries, or system libraries. target_link_libraries( # Specifies the target library. native-lib # Links the target library to the log library # included in the NDK. ${log-lib} )
Чтобы к нативному коду можно было обращаться из приложения, в MainActivity.java нужно выполнить несколько операций, перечисленных ниже.
public class MainActivity extends AppCompatActivity { // 1 static { System.loadLibrary("native-lib"); } // 2 public native String stringFromJNI(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 3 TextView tv = findViewById(R.id.sample_text); tv.setText(stringFromJNI()); } }
- С помощью метода System.loadLibrary() мы загружаем библиотеку из папки cpp.
- Объявляем нативный метод загруженной библиотеки.
- Вызываем нативный метод библиотеки.
Запустим приложение и увидим строчку из нативного кода в TextView.
Вот так выглядит процесс запуска приложения с нативным кодом:
- Gradle вызывает внешний скрипт сборки CMakeLists.txt.
- CMake следует командам из скрипта сборки, чтобы скомпилировать исходный файл native-lib.cpp в общую библиотеку, и называет полученную библиотеку libnative-lib.so, который Gradle затем упаковывает в APK.
- Во время выполнения MainActivity приложения загружает нативную библиотеку, используя System.loadLibrary(). После этого метод библиотеки stringFromJNI() становится доступным для использования.
- MainActivity.onCreate() вызывает stringFromJNI(), который возвращает строку «Hello from C++» в TextView.
Примечание: Instant Run несовместим с компонентами проекта, написанными на нативном коде.
Чтобы убедиться, что нативная библиотека была добавлена в APK, можно этот APK проанализировать с помощью утилиты APK Analyzer. Для этого в Android Studio выберем в меню Build — Build APK(s).
После того, как APK будет собран, появится уведомление с предложением открыть папку, в которой он находится, либо проанализировать его. Выбираем analyze. Либо можно в меню выбрать Build — Analyze APK и указать путь до созданного APK (app/build/outputs/apk).
В Android Studio откроется результат анализа APK. Здесь можно увидеть, какой размер имеет собранный APK, сколько классов и методов включает в себя, список ресурсов и прочее.
Здесь можно увидеть, что собранный APK имеет следующие характеристики:
Характеристика | Значение |
Размер APK в несжатом виде | 1.7 MB |
Размер APK в сжатом виде | 1.4 MB |
Количество классов | 1269 |
Количество методов | 10359 |
Однако нас интересует наличие нативной библиотеки. Если раскрыть папку lib, занимающую 18,7% от размера APK, то можно увидеть там файл libnative-lib.so под разные архитектуры. Это и есть скомплиированная нативная библиотека.
Сравним, насколько меняется размер APK-файла по сравнению с приложением без использования NDK. Создадим пустой проект с надписью «Hello, world!» на экране. Соберём APK и посмотрим на его характеристики.
Характеристика | Значение |
Размер APK в несжатом виде | 1.4 MB |
Размер APK в сжатом виде | 1.2 MB |
Количество классов | 1269 |
Количество методов | 10357 |
В результате сравнения получаем, что размер APK за счет добавления нативной библиотеки изменяется незначительно.
Рассмотрим, как можно добавить свой нативный код в приложение.
Чтобы добавить в cpp свои файлы, нужно нажать правой кнопкой мыши на папку cpp и выбрать C/C++ Source File.
В появившемся окне нужно написать имя исходного файла, а также выбрать расширение файла.
В раскрывающемся списке Type можно выбрать стандартные расширения файла, либо создать свои. Для этого нужно нажать на кнопку справа от раскрывающегося списка, после чего откроется окно настроек, где будет предложено создать тип и выбрать для него расширение исходного файла и заголовочного.
Если есть необходимость в создании заголовочного файла, можно поставить флажок на Create an associated header.
Применение нативного кода не ограничивается возвратом захардкоженных строк. Например, можно написать нативный код, который принимает от Java объект, работает с ним и возвращает результат работы.
Создадим Java-класс MeshData, который будет служить для хранения данных объекта.
public class MeshData { private int _facetCount; public float[] VertexCoords; public MeshData(int facetCount) { _facetCount = facetCount; VertexCoords = new float[facetCount]; // заполняем массив значениями for (int i = 0; i < facetCount; ++i) { VertexCoords[i] = 10.0f * i; } } public int getFacetCount() { return _facetCount; } }
Добавим созданный исходный файл test-lib.cpp в метод System.loadLibrary() активности и объявим метод getMemberFieldFromNative(), с помощью которого мы будем передавать объект из Java в нативный код.
public class MainActivity extends AppCompatActivity { static { System.loadLibrary("native-lib"); } public native String stringFromJNI(); public native float getMemberFieldFromNative(MeshData obj); ...
Откроем test-lib.cpp и добавим в него JNI-метод, который будет принимать объект MeshData и получать доступ к полю VertexCoords.
JNIEXPORT jfloat JNICALL Java_ru_androidtools_ndktest_MainActivity_getMemberFieldFromNative( JNIEnv *env, jobject callingObject, jobject obj) //obj это экземпляр MeshData, который мы передали из Java { float result = 0.0f; // получаем класс объекта jclass cls = env->GetObjectClass(obj); // получаем поле, [F это массив float jfieldID fieldId = env->GetFieldID(cls, "VertexCoords", "[F"); // получаем поле объекта, возвращает JObject (потому что это массив) jobject objArray = env->GetObjectField(obj, fieldId); // Преобразовываем объект в jfloatarray jfloatArray *fArray = reinterpret_cast jsize len = env->GetArrayLength(*fArray); // получаем элементы массива float *data = env->GetFloatArrayElements(*fArray, 0); for (int i = 0; i ReleaseFloatArrayElements(*fArray, data, 0); return result; }
Не забудьте в начале файла подключить библиотеку jni.h.
#include
Следующий JNI-метод возвращает значение.
int getFacetCount(JNIEnv *env, jobject obj) { jclass cls = env->GetObjectClass(obj); jmethodID methodId = env->GetMethodID(cls, "getFacetCount", "()I"); int result = env->CallIntMethod(obj, methodId); return result; } JNIEXPORT jint JNICALL Java_ru_androidtools_ndktest_MainActivity_invokeMemberFuncFromNative( JNIEnv *env, jobject callingObject, jobject obj) { int facetCount = getFacetCount(env, obj); // возвращаем результат (в int) return facetCount; }
После этого создаем объект Java в следующем JNI-методе чтобы вернуть его.
JNIEXPORT jobject JNICALL Java_ru_androidtools_ndktest_MainActivity_createObjectFromNative( JNIEnv *env, jobject callingObject, jint param) { jclass cls = env->FindClass("ru/androidtools/ndktest/MeshData"); jmethodID methodId = env->GetMethodID(cls, "", "(I)V"); jobject env->NewObject(cls, methodId, param); return obj; }
Поскольку мы не можем передавать список или объекты в нативный код, мы должны передавать в него массив. Для этого объявим в коде активности метод processObjectArrayFromNative().
public native int processObjectArrayFromNative(MeshData[] objArray);
Добавим JNI-метод, который будет считывать этот массив.
JNIEXPORT jint JNICALL Java_ru_androidtools_ndktest_MainActivity_processObjectArrayFromNative( JNIEnv *env, jobject callingObject, jobjectArray objArray) { int resultSum = 0; int len = env->GetArrayLength(objArray); // получаем все объекты в массиве for (int i = 0; i GetObjectArrayElement(objArray, i); resultSum += getFacetCount(env, obj); } return resultSum; }
Примечание: Поскольку мы используем C++, в код нужно добавить extern «C». Это ключевое слово необходимо для того, чтобы сообщать компилятору о том, чтобы он не преобразовывал имена функций, а оставлял их такими, как в реализации. В противном случае во время работы приложения можно столкнуться с исключением «No implementation found».
В результате код test-lib.cpp выглядит следующим образом:
#include extern "C" { int getFacetCount(JNIEnv *env, jobject obj) { jclass cls = env->GetObjectClass(obj); jmethodID methodId = env->GetMethodID(cls, "getFacetCount", "()I"); int result = env->CallIntMethod(obj, methodId); return result; } JNIEXPORT jfloat JNICALL Java_ru_androidtools_ndktest_MainActivity_getMemberFieldFromNative( JNIEnv *env, jobject callingObject, jobject obj) { float result = 0.0f; jclass cls = env->GetObjectClass(obj); jfieldID fieldId = env->GetFieldID(cls, "VertexCoords", "[F"); jobject objArray = env->GetObjectField(obj, fieldId); jfloatArray *fArray = reinterpret_cast(&objArrayze len = env->GetArrayLength(*fArray); float *data = env->GetFloatArrayElements(*fArray, 0); for (int i = 0; i ReleaseFloatArrayElements(*fArray, data, 0); return result; } JNIEXPORT jint JNICALL Java_ru_androidtools_ndktest_MainActivity_invokeMemberFuncFromNative( JNIEnv *env, jobject callingObject, jobject obj) { int facetCount = getFacetCount(env, obj); return facetCount; } JNIEXPORT jobject JNICALL Java_ru_androidtools_ndktest_MainActivity_createObjectFromNative( JNIEnv *env, jobject callingObject, jint param) { jclass cls = env->FindClass("ru/androidtools/ndktest/MeshData"); jmethodID methodId = env->GetMethodID(cls, "", "(I)V"); jobject obj = env->NewObje, methodId, param); return obj; } JNIEXPORT jint JNICALL Java_ru_androidtools_ndktest_MainActivity_processObjectArrayFromNative( JNIEnv *env, jobject callingObject, jobjectArray objArray) { int resultSum = 0; int len = env->GetArrayLength(objArray); for (int i = 0; i GetObjectArrayElement(objArray, i); resultSum += getFacetCount(env, obj); } return resultSum; } }
Теперь в коде активности объявим оставшиеся методы и вызовем их. Результат работы выведем в TextView.
public class MainActivity extends AppCompatActivity { static { System.loadLibrary("native-lib"); } public native String stringFromJNI(); public native float getMemberFieldFromNative(MeshData obj); public native int invokeMemberFuncFromNative(MeshData obj); public native MeshData createObjectFromNative(int param); public native int processObjectArrayFromNative(MeshData[] objArray); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // выводим сообщение из примера исходного файла String msg = stringFromJNI(); // инициализируем объект MeshData в Java-коде и передаем его в методы MeshData obj = new MeshData(3); msg += "nnResult getMemberFieldFromNative: " + getMemberFieldFromNative(obj); msg += "nResult invokeMemberFuncFromNative: " + invokeMemberFuncFromNative(obj); // инициализируем объект MeshData в нативном коде и возвращаем его в Java MeshData obj2 = createObjectFromNative(18); msg += "nnResult createObjectFromNative: " + obj2.getFacetCount(); // обрабатываем массив объектов в нативном коде и возвращаем его в Java MeshData[] objArray = new MeshData[] { new MeshData(10), new MeshData(20) }; int arrayRes = processObjectArrayFromNative(objArray); msg += "nnResult processObjectArrayFromNative: " + arrayRes; TextView tv = findViewById(R.id.sample_text); tv.setText(msg); } }
Однако написать код исходного файла недостаточно. Если посмотреть на вкладку Project, то можно обнаружить, что там нет файла test-lib.cpp. Исправить это можно, сообщив CMake о его наличии.
Теперь, когда мы написали нативный код, нужно добавить test-lib.cpp в CMakeLists.txt, чтобы CMake скомпилировал его в библиотеку. По факту, в файле из примера уже есть весь нужный код, однако там нужно добавить созданный выше исходный файл.
Откроем CMakeLists.txt и найдем там команду add_library().
add_library( # Sets the name of the library. native-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). src/main/cpp/native-lib.cpp )
Здесь в параметры передаётся название, которое будет дано скомпилированной библиотеке. После этого нужно указать тип создаваемой библиотеки (STATIC, SHARED или MODULE). Затем идет перечисление файлов, которые нужно скомпилировать.
В списке файлов нужно добавим путь до test-lib.cpp и синхронизируем проект, после чего CMake скомпилирует указанные файлы в библиотеку.
add_library( # Sets the name of the library. native-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). src/main/cpp/native-lib.cpp src/main/cpp/test-lib.cpp )
В библиотеку можно скомпилировать столько файлов, сколько может потребоваться для работы.
В результате запускаем приложение и видим, что наша нативная библиотека работает.
В случае, если вы добавляете NDK в проект вручную, в build.gradle модуля приложения нужно прописать некоторые команды.
Откроем build.gradle. В defaultConfig нужно добавить externalNativeBuild и указать внутри него флаги следующим образом.
android { compileSdkVersion 27 defaultConfig { applicationId "ru.androidtools.ndktest" minSdkVersion 16 targetSdkVersion 27 versionCode 1 versionName "1.0" externalNativeBuild { cmake { cppFlags "" } } } ... }
Здесь перечисляются флаги, о которых мы говорили в начале статьи (поддержка исключений, RTTI). Если никакие флаги не нужны, просто оставляем поле пустым.
Затем нужно передать в Gradle скрипт, по которому CMake будет собирать нативную библиотеку. Для этого внутри android нужно добавить externalNativeBuild и передать в него имя файла скрипта сборки.
android { ... externalNativeBuild { cmake { path "CMakeLists.txt" } } }
На этом конфигурация Gradle завершается и можно собирать приложение.
- https://m.habr.com/post/203014/
- https://softfamous.com/android-ndk/
- https://kompkimi.ru/programms-2/sistemnye-programmy/android-ndk
- https://developers.google.com/vr/develop/android-ndk/download
- https://android-tools.ru/coding/ispolzovanie-ndk-v-svoix-prilozheniyax/