Узнайте, как установить Android NDK и начать его использовать. К концу этого урока вы создадите свой собственный проект, который делает простой вызов из Java-кода в собственный C-код.
Необходимый опыт
Прежде чем мы начнем, нам нужно немного поговорить здесь, чтобы обсудить уровень этого урока. Он помечен как расширенный. Причина этого в том, что мы, авторы, будем предполагать, что вы согласитесь со следующими утверждениями:
- Вы имеете опыт работы с Java и C.
- Вам удобно пользоваться командной строкой.
- Вы знаете, как выяснить, какая версия Cygwin, awk и других инструментов у вас есть.
- Вам комфортно с Android Development.
- У вас есть рабочая среда разработки Android (как будто это письмо, авторы используют Android 2.2)
- Вы используете Eclipse или можете с легкостью переводить инструкции Eclipse в свою собственную среду IDE.
Если вам это неудобно, вы, конечно, можете прочитать этот учебник, но у вас могут возникнуть трудности на определенных этапах, которые можно решить, если вы будете знакомы с вышеизложенным. Тем не менее, использование NDK по-прежнему является процессом, который подвержен проблемам и проблемам, даже если вы считаете себя ветераном мобильной разработки. Имейте в виду, что вам, возможно, придется самостоятельно устранить неполадки, прежде чем все будет работать гладко в вашей системе разработки.
Полный пример проекта для этого учебника можно скачать с открытым исходным кодом .
Примечание о том, когда использовать NDK
Итак, если вы читаете этот учебник, вы, возможно, уже рассматриваете NDK для своих проектов Android. Тем не менее, мы бы хотели поговорить о том, почему NDK важен, когда его следует использовать и, что не менее важно, когда его не следует использовать.
Вообще говоря, вам нужно использовать NDK, только если ваше приложение действительно связано с процессором. То есть, у вас есть алгоритмы, которые используют весь процессор в DalvikVM и выиграли бы от естественной работы. Также не забывайте, что в Android 2.2 JIT-компилятор также улучшит производительность такого кода.
Другая причина использования NDK — простота портирования. Если у вас есть множество кода C для вашего существующего приложения, использование NDK может ускорить процесс разработки вашего проекта, а также поможет синхронизировать изменения между вашими проектами Android и не Android. Это может быть особенно верно для приложений OpenGL ES, написанных для других платформ.
Не думайте, что вы увеличите производительность своего приложения только потому, что используете нативный код. Обмены JavaNative C добавляют некоторые накладные расходы, так что это действительно целесообразно, если у вас есть интенсивная обработка.
Шаг 0: загрузка инструментов
Хорошо, начнем. Вам необходимо скачать NDK. Сначала мы сделаем это, так как во время загрузки вы можете проверить, что у вас есть нужные версии остальных инструментов, которые вам нужны.
Загрузите NDK для вашей операционной системы с сайта Android .
Теперь сравните версии ваших инструментов с этими:
- Если на Windows, Cygwin 1.7 или позже
- Обновите awk до последней версии (мы используем 20070501)
- GNU Make 3.81 или более поздняя версия (мы используем 3.81)
Если какая-либо из этих версий слишком старая, пожалуйста, обновите их, прежде чем продолжить.
Шаг 1: Установка NDK
Теперь, когда NDK загружен (это правда?), Вам нужно распаковать его. Сделайте это и поместите в соответствующий каталог. Мы помещаем наш каталог в тот же каталог, в который мы поместили Android SDK. Помните, где вы положили это.
На этом этапе вы можете добавить инструменты NDK на свой путь. Если вы работаете на Mac или Linux, вы можете сделать это с помощью собственной настройки пути. Если вы используете Windows в Cygwin, вам нужно настроить параметр пути Cygwin.
Шаг 2: Создание проекта
Создайте обычный проект Android. Чтобы избежать проблем позже, ваш проект должен находиться в пути, не содержащем пробелов. Наш проект имеет имя пакета «com.mamlambo.sample.ndk1» с именем активности по умолчанию «AndroidNDK1SampleActivity» — вы увидите, что они скоро появятся снова.
На верхнем уровне этого проекта создайте каталог с именем «jni» — именно туда вы поместите свой нативный код. Если вы знакомы с JNI, Android NDK в значительной степени основан на концепциях JNI — по сути, это JNI с ограниченным набором заголовков для компиляции Си.
Шаг 3: Добавление кода на C
Теперь в папке jni создайте файл с именем native.c. Поместите следующий код C в этот файл, чтобы начать; мы добавим еще одну функцию позже:
#include <jni.h> #include <string.h> #include <android / log.h> #define DEBUG_TAG "NDK_AndroidNDK1SampleActivity" void Java_com_mamlambo_sample_ndk1_AndroidNDK1SampleActivity_helloLog (JNIEnv * env, jobject this, jstring logThis) { jboolean isCopy; const char * szLogThis = (* env) -> GetStringUTFChars (env, logThis, & isCopy); __android_log_print (ANDROID_LOG_DEBUG, DEBUG_TAG, "NDK: LC: [% s]", szLogThis); (* env) -> ReleaseStringUTFChars (env, logThis, szLogThis); }
Эта функция на самом деле довольно проста. Он принимает параметр String объекта Java, преобразует его в C-строку и затем записывает его в LogCat.
Название функции, однако, важно. Это следует за определенной моделью «Java», сопровождаемой именем пакета, сопровождаемым именем класса, сопровождаемым именем метода, как определено из Java. Каждый кусок отделяется подчеркиванием вместо точки.
Первые два параметра функции также являются критическими. Первый параметр — это среда JNI, часто используемая с вспомогательными функциями. Второй параметр — это объект Java, частью которого является эта функция.
Шаг 4: Вызов родного с Java
Теперь, когда вы написали собственный код, давайте вернемся к Java. В задании по умолчанию создайте кнопку и добавьте обработчик кнопки по своему усмотрению. Из вашего обработчика кнопки сделайте вызов helloLog:
helloLog («Это будет входить в LogCat через собственный вызов.»);
Затем вы должны добавить объявление функции на стороне Java. Добавьте следующую декларацию в ваш класс Activity:
приватный родной void helloLog (String logThis);
Это говорит системе компиляции и компоновки, что реализация этого метода будет происходить из собственного кода.
Наконец, вам нужно загрузить библиотеку, в которую в конечном итоге будет скомпилирован нативный код. Добавьте следующий статический инициализатор в класс Activity, чтобы загрузить библиотеку по имени (само имя библиотеки остается на ваше усмотрение, и на следующем шаге будет ссылка снова):
static { System.loadLibrary ( "ndk1"); }
Шаг 5: Добавление файла кода собственного кода
В папке jni вам нужно добавить make-файл, который будет использоваться во время компиляции. Этот файл должен называться «Android.mk», и если вы назвали свой файл native.c и свою библиотеку ndk1, содержимое Android.mk будет выглядеть так:
LOCAL_PATH: = $ (позвоните в мой каталог) включает $ (CLEAR_VARS) LOCAL_LDLIBS: = -llog LOCAL_MODULE: = ndk1 LOCAL_SRC_FILES: = native.c включает $ (BUILD_SHARED_LIBRARY)
Шаг 6: Компиляция родного кода
Теперь, когда ваш нативный код написан и ваш make-файл готов, пришло время скомпилировать нативный код. Из командной строки (пользователи Windows, вы захотите сделать это в Cygwin) вам нужно будет запустить команду ndk-build из корневого каталога вашего проекта. Инструмент ndk-build находится в каталоге инструментов NDK. Нам проще всего добавить этот инструмент на наш путь.
На последующих компиляциях вы можете убедиться, что все перекомпилировано, если вы используете команду «ndk-build clean».
Шаг 7: Выполнение кода
Теперь все готово для запуска кода. Загрузите проект в ваш любимый эмулятор или трубку, посмотрите LogCat и нажмите кнопку.
Может случиться одно из двух. Во-первых, это могло сработать. Если так, поздравляю! Но вы все равно можете продолжать читать. Вероятно, вы получили ошибку в LogCat, говорящую что-то вроде: «Не удалось выполнить метод действия». Это нормально. Это просто означает, что вы пропустили шаг. Это легко сделать в Eclipse. Обычно Eclipse настроен на автоматическую перекомпиляцию. То, что он не делает, это автоматически перекомпилируется и перекомпоновывается, если не знает, что что-то изменилось. И в этом случае Eclipse не знает, что вы скомпилировали нативный код. Поэтому принудительно перекомпилируйте Eclipse, «очистив» проект (Project-> Clean с панели инструментов Eclipse).
Шаг 8: Добавление еще одной нативной функции
Эта следующая функция продемонстрирует способность не только возвращать значения, но и возвращать объект, например String. Добавьте следующую функцию в native.c:
jstring Java_com_mamlambo_sample_ndk1_AndroidNDK1SampleActivity_getString (JNIEnv * env, jobject this, значение jint1, значение jint2) { char * szFormat = "Сумма двух чисел:% i"; char * szResult; // добавляем два значения сумма jlong = значение1 + значение2; // malloc комната для полученной строки szResult = malloc (sizeof (szFormat) + 20); // стандартный sprintf sprintf (szResult, szFormat, sum); // получить строку объекта jstring result = (* env) -> NewStringUTF (env, szResult); // очистка бесплатно (szResult); вернуть результат; }
Для того, чтобы это скомпилировать, вы захотите добавить также оператор включения для stdio.h. И, чтобы соответствовать этой новой нативной функции, добавьте следующее объявление в свой класс Activity Java:
приватная собственная строка String getString (int value1, int value2);
Теперь вы можете подключить функциональность, как вам нравится. Мы использовали следующие два вызова и выходы:
String result = getString (5,2); Log.v (DEBUG_TAG, "Результат:" + результат); результат = getString (105, 1232); Log.v (DEBUG_TAG, "Result2:" + результат);
Возвращаясь к функции C, вы заметите, что мы сделали пару вещей. Во-первых, нам нужно создать буфер для записи для вызова sprintf () с помощью функции malloc (). Это разумно, если вы не забыли освободить результаты, когда закончили использовать функцию free (). Затем, чтобы передать результат обратно, вы можете использовать вспомогательную функцию JNI с именем NewStringUTF (). Эта функция в основном берет строку C и делает из нее новый объект Java. Этот новый объект String затем может быть возвращен как результат, и вы сможете использовать его как обычный объект Java String из класса Java.
Наборы инструкций, совместимость и т.д.
Android NDK требует Android SDK 1.5 или более поздней версии. В более поздних версиях NDK новые заголовки стали доступны для расширенного доступа к определенным API-интерфейсам, в частности к библиотекам OpenGL ES.
Однако речь идет не о совместимости. Это нативный код, скомпилированный с используемой архитектурой процессора. Итак, один вопрос, который вы могли бы себе задать, — какие архитектуры процессоров поддерживаются? В текущем NDK (на момент написания этой статьи) поддерживаются только наборы команд ARMv5TE и ARMv7-A. По умолчанию целью является ARMv5TE, который будет работать на всех устройствах Android с чипами ARM.
Есть планы для дальнейших наборов инструкций (x86 был упомянут). Это имеет интересный смысл: решение NDK не будет работать на всех устройствах. Например, есть планшеты Android, которые используют процессор Intel Atom с набором команд x86.
Так как же работает NDK на эмуляторе? Эмулятор работает на настоящей виртуальной машине, включая полную эмуляцию процессора. И да, это означает, что при запуске Java в эмуляторе вы запускаете виртуальную машину внутри виртуальной машины.
Вывод
Как ты это сделал? Вы установили Android NDK и в конечном итоге создали функциональное работающее приложение, которое использует собственный код C как часть его? Мы надеемся на это. На этом пути есть много потенциальных «ошибок», но в некоторых случаях это может стоить усилий. Как всегда, мы хотели бы услышать ваши отзывы.
Об авторах
Разработчики мобильных приложений Лорен Дарси и Шейн Кондер являются соавторами нескольких книг по разработке Android: углубленная книга по программированию под названием « Разработка беспроводных приложений для Android» и « Разработка Android-приложений Sams TeachYourself за 24 часа» . Когда они не пишут, они тратят свое время на разработку мобильного программного обеспечения в своей компании и оказание консультационных услуг. С ними можно связаться по электронной почте [email protected] , через их блог на androidbook.blogspot.com и в Twitter @androidwireless .