Android sdk или ndk

В чём отличия Android NDK от Android SDK ?

The Android NDK is a companion tool to the Android SDK that lets you build performance-critical portions of your apps in native code. It provides headers and libraries that allow you to build activities, handle user input, use hardware sensors, access application resources, and more, when programming in C or C++. If you write native code, your applications are still packaged into an .apk file and they still run inside of a virtual machine on the device. The fundamental Android application model does not change.

Android NDK является вспомогательным инструментом для Android SDK, который позволяет создавать критичные части вашего приложения в машинном коде. Он предоставляет заголовки и библиотеки, которые позволяют строить деятельность (хз) , ручной ввод данных пользователем, использование аппаратных датчиков, доступ к ресурсам приложения и многое другое, при программировании на С или C++. Если вы пишете native- код, ваши приложения по-прежнему упаковываются в .apk файл и они по-прежнему выполняются внутри виртуальной машины на устройстве. Фундаментальная модель Android-приложения не изменится.

SDK — Software Development Kit
NDK — Native Development Kit

На андроид приложения изначально можно было писать только на JAVA. Соответственно SDK у них был для их аналога JAVA машины.
Последние версии стали выходить с возможностью выполнения нативного кода (обычный скомпилированный под ARM код) , для него выпустили NDK.

Источник

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

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

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

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

Наложение слоя (режим Luminosity, цветной рисунок)
Nexus One Nexus 7
SDK NDK SDK NDK
2563 120 4850 90
2122 100 4520 190
2162 110 4330 100

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

Наложение слоя (режим Color Dodge, одноцветный рисунок)
Nexus One Nexus 7
SDK NDK SDK NDK
2673 30 5720 80
2572 20 6230 70
2573 20 6110 70

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

Наложение слоев по градиенту прозрачности
Nexus One Nexus 7
SDK NDK SDK NDK
1301 321 3010 470
1221 330 2670 620
1211 300 2770 610

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

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

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

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

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

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

Читайте также:  Chicken invaders для android

Источник

Android ndk vs sdk – если функции связаны

Я собираюсь начать разработку приложений для Android.

Что такое NDK? Документации я не смог извлечь следующую информацию:

Использует ли NDK новые возможности по сравнению с SDK?

Меня это интересует, потому что использование NDK значительно увеличивает сложность приложения, поэтому, если меня не интересует увеличение производительности, есть ли другие причины для NDK?

Я имею в виду, например:

  • (Я знаю, что ни SDK, ни NDK не допускают этого, я просто использую его как пример того, что я имею в виду) позволяет сказать, что java SDK не позволяет этого, но некоторые собственные библиотеки делают – тогда ответ на мой вопрос будет да, NDK действительно добавляет некоторые функции

Заблаговременно заблаговременно за любые полезные ответы.

Единственные причины использовать NDK, насколько я знаю, – это выжать дополнительную производительность из вашего приложения и приблизиться к голым металлам. Если вам не нужно делать что-либо из этого, вы, вероятно, должны держаться подальше от NDK.

Также обратите внимание, что у Dalvik VM уже есть довольно потрясающая производительность и сравнительно просто.

Вы действительно должны использовать SDK, если у вас нет веских оснований для использования NDK. Хорошие причины могут быть разными, но, например, вы можете использовать NDK:

Если вы хотите использовать OpenGL ES 2.0 для Android 2.1 (Eclair), он доступен только через NDK. Поддержка SDK для OpenGL ES 2.0 началась с версии Froyo.

Если вы хотите использовать Renderscript

Если у вас есть большая часть логики вашего приложения, написанная на C / C ++

NDK гораздо более ограничен с точки зрения функциональности.

То, что вы получаете от NDK, – это возможность написать свое приложение на C ++ и скомпилировать его на собственный ARM-код. Если вам нравится C ++ лучше, чем Java, если у вас есть существующее приложение на C ++, которое вы хотите перенести на Android, или если вам просто нужна дополнительная производительность, которую может предложить только собственный код, то, во всяком случае, вы должны использовать NDK.

Я не делал этого сам, но еще одна альтернатива – написать гибридное приложение, где приложение написано в основном на Java, с выбранными функциями, написанными на C ++, которые вызывается из кода Java.

Для меня, я думаю, важно знать NDK, который является мощным инструментом в разработке мобильных приложений. Особенно, если вы хотите разработать многоплатформенное приложение, NDK является непревзойденным в этом домене. Поскольку тот же код, написанный на C ++ для Android, можно легко портировать и запускать аналогично на iOS, Windows или любой другой платформе без изменения исходного кода. Что на самом деле экономит много времени на разработке приложений, разработанных для запуска на нескольких платформах; Как игры и другие классические приложения. То, что вы не можете сделать с SDK.

Родные методы – это код, специфичный для платформы. Обычно они записываются на языках C или C ++ и содержатся в библиотеках (dll). Можно создать гибридное Java-приложение, которое извлекает выгоду из таких библиотек.

Причины использования собственных методов

  1. Получение доступа к специальным возможностям вашего устройства или ОС Android
  2. Получение дополнительной скорости
  3. Получение доступа к большому объему существующего устаревшего кода

Как правило, хорошие примеры использования для NDK – это приложения с интенсивным использованием процессора, такие как игровые движки, обработка сигналов и физическое моделирование

Основным недостатком собственных методов является то, что у вас не будет возможности кросс-платформенности .

Теперь, если вы не знаете, что такое собственный код, возможно, вам не нужно использовать собственный код. Документация Android NDK объясняет это хорошо:

…, вы должны понимать, что NDK не будет полезен большинству приложений. Как разработчик, вам нужно сбалансировать свои преимущества с его недостатками. Примечательно, что использование собственного кода на Android обычно не приводит к заметному повышению производительности, но всегда увеличивает сложность вашего приложения. В общем, вы должны использовать NDK, если это важно для вашего приложения, – никогда, потому что вы просто предпочитаете программировать на C / C ++. Изучая вопрос о том, следует ли вам разрабатывать собственный код, подумайте о своих требованиях и посмотрите, предоставляют ли интерфейсные API-интерфейсы Android необходимые вам функции.

Источник

Введение в 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) – это набор инструментов, которые позволяют реализовать часть вашего приложения используя такие языки как С/С++.

Читайте также:  Log android studio java

Для чего используют 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 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

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

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

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

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

Потоки

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

Читайте также:  Google news для android

Первые шаги

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

Как мы видим из рисунка 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_PATH\toolchains\mipsel-linux-android-4.8\prebuilt\windows-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.

Источник

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