
Использование нативного кода, написанного на C или С++ — это тема, которую многие разработчики не затрагивают вовсе. Но порой использование C++ в приложениях намного упрощает/ускоряет разработку. В этой статье будут рассмотрены основные принципы работы с native кодом.
Предварительная настройка
Если у вас ещё не настроен Eclipse, то читаем как настроить Eclipse для работы с Android. Только помимо того, что в статье сказано, при установке ещё необходимо выбрать и установить NDK плагин.
Так же вам необходимо установить CDT для Eclipse. Под Виндой вам вроде как ещё понадобиться установить Cygwin.
Теперь необходимо создать проект и прописать пути.
В проекте будет создана папка jni, где вы должны размещать файлы с C++ кодом. В ранних версиях был баг, когда Eclipse не мог верно настроить пути до некоторых хэдеров из NDK. В последней версии всё нормально. Просто очистите проект (Clean project), а затем перестройте его (Build project).
Зачем нужен NDK?
Думаю, необходимо предварительно объяснить, когда вообще стоит (и стоит ли?) использовать ndk. Многие советуют использовать C++, когда требуются какие-то большие/сложные вычисления. Что значит сложно? =/ В общем, лучше назову конкретные случаи, когда использование NDK оправдано:
Возможности NDK огромны. Вы можете из Java вызывать C++ методы. В то же время, вам ничто не машет вызывать Java методы из C++. Даже есть возможность создавать приложение практически без использования Java, используя NativeActivity (API 9 и выше).
Java. Путешествие в Native (или туда и обратно).
Да простит меня профессор за упоминание его работы (: И так, рассмотреть всё в рамках одной статьи невозможно. Поэтому, для начала реализуем лишь вызов native методов из Java.
Перечислю кратко основные моменты при работе с native:
- Создание файлов с C++ кодом.
- Определение C++ методов для экспорта.
- Создание .mk файлов.
- Генерация библиотеки.
- Подключение библиотеки в Java и вызов C++ методов.
Создание файлов с C++ кодом
В native определим всего 3 метода: передача строки, изменение строки, получение строки.
Создадим для начала файл def.h, подключим пару нужных файлов и определим методы для вывода в консоль.
#include <android/log.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #ifdef __ANDROID__ #define LOG_TAG "MyNative" #define STRINGIFY(x) #x #define LOG_TAG __FILE__ ":" STRINGIFY(__MyNative__) #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) #endif
Создадим файл MyNative.h и определим в нём спецификации методов для экспорта, чтоб вызывать их из Java кода потом.
#include <def.h> #include <jni.h> char MyStr[80]; extern "C" { JNIEXPORT void Java_ru_suvitruf_androidndk_AndroidNDK_SetString(JNIEnv * env, jobject obj, jstring str); JNIEXPORT void Java_ru_suvitruf_androidndk_AndroidNDK_ChangeString(JNIEnv * env, jobject obj); JNIEXPORT jstring Java_ru_suvitruf_androidndk_AndroidNDK_GetString(JNIEnv * env, jobject obj); }
Теперь все три метода можно вызвать из Java кода. Я этот код ручками писал. Но можно заюзать javah, которая будет сама генерить эти заголовки. extern "C"
нужен, чтобы компилятор C++ не менял имена объявленных функций.
Стоит немного сказать про наименование методов. Java_ — обязательный префикс. ru_suvitruf_androidndk, так как у нас пакет ru.suvitruf.androidndk, ну а дальше наименование класса и метода на стороне Java. В каждой функции в качестве аргумента имеется JNIEnv*
— интерфейс для работы с Java, при помощи него можно вызывать Java-методы, создавать Java-объекты. Второй обязательный параметр — jobject
или jclass
— в зависимости от того, является ли метод статическим. Если метод статический, то аргумент будет типа jclass
(ссылка на класс объекта, в котором объявлен метод), если не статический — jobject
— ссылка на объект, у которого был вызван метод.
Ну и создадим MyNative.cpp с реализацией методов.
#include <MyNative.h> JNIEXPORT void Java_ru_suvitruf_androidndk_AndroidNDK_SetString(JNIEnv * env, jobject obj, jstring str){ jboolean isCopy; const char * Str; Str = env->GetStringUTFChars(str, &isCopy); strcpy(MyStr,Str); LOGI("string = \"%s\"",MyStr); } void ChangeStr(){ strcat(MyStr," and bb."); } JNIEXPORT void Java_ru_suvitruf_androidndk_AndroidNDK_ChangeString(JNIEnv * env, jobject obj){ ChangeStr(); LOGI("string after change = \"%s\"",MyStr); } JNIEXPORT jstring Java_ru_suvitruf_androidndk_AndroidNDK_GetString(JNIEnv * env, jobject obj){ LOGI("returned string = \"%s\"",MyStr); return env->NewStringUTF(MyStr); }
Работа с Application.mk
В этом файле описаны глобальные настройки для сборки либы.
# Без этой строчки ничего не будет работать (: APP_STL:=stlport_static # Список модулей/либ, которые нужна забилдить. Они будут такие же как в LOCAL_MODULE в Android.mk файле APP_MODULES := AndroidNDK # Указываем под какой arm собирать. Не обязательный параметр. APP_ABI := armeabi armeabi-v7a # Платформа, под которую билдим. Не обязательный параметр. APP_PLATFORM := android-10
Работа с Android.mk
Здесь указываем параметры/настройки по линковке и прочее, чтобы собрать либу.
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # имя нашего модуля, который будет вызываться в Java при помощи System.loadLibrary() LOCAL_MODULE := AndroidNDK LOCAL_DEFAULT_CPP_EXTENSION := cpp #список файлов, который нужно собрать LOCAL_SRC_FILES := MyNative.cpp #список библиотек из ndk, которые надо включить в сборку LOCAL_LDLIBS := -llog -landroid include $(BUILD_SHARED_LIBRARY)
В Android.mk вообще есть не мало всяких флагов и прочего. Можно добавлять в сборку уже готовые библиотеки и т.д. В следующих статьях напишу, как это делается.
После того, как вы создали C++ файлы и .mk сделали, можно забилдить проект, тогда в папке obj появится библиотека libAndroidNDK.so.
Подключение библиотеки в Java и вызов C++ методов.

Теперь остаётся только написать Java код. Сделаем простенькое приложение. Разместим поле для ввода текста, три кнопки (передача текста в native, изменение текста в native и возврат изменённой строки из native) и поле для нового текста, который получили из native кода.
Для того, чтобы использовать native методы создадим класс AndroidNDK.
public class AndroidNDK { // Загрузка модуля «AndroidNDK» — нативной библиотеки, в которой реализованы методы. // Название этого модуля задается в файле Android.mk. static { System.loadLibrary("AndroidNDK"); } public static native void SetString(String str); public static native void ChangeString(); public static native String GetString(); }
Ключевое тут:
static {
System.loadLibrary("AndroidNDK");
}
Этот код выполнит загрузку нашей библиотеки, в которой реализованы методы. Обратите ещё раз внимание на этот класс и методы и вспомните наименование методов при экспорте в native коде: Java_ru_suvitruf_androidndk_AndroidNDK_ChangeString.
Java код по обработке нажатий на кнопки писать не буду, ибо это тривиальная задача. В любом случае, если понадобиться, можете посмотреть в исходниках к статье. В логе будет вот что:

Для знакомства с native достаточно написал. Можете скачать исходники AndroidNDK.rar.
А будут ли дальше уроки по LibGDX? Очень хочу узнать как делать Runner на LibGDX.
Будут, но пока времени нет =/
А может просто «в лом»)))))
Если бы было лень, то этого блога совсем б не было)
Скажите пожалуйста, а когда будет продолжение?
По NDK?
Да. Хочется побольше узнать о добавлении готовых библиотек в сборку.
На этих выходных напишу вторую статью по NDK тогда )
О замечательно. Спасибо!)
Еще вопрос, вы развиваете эту тему как возможность использования с++ и сами эти пользуетесь в разработке приложений, или используете только яву? еще что очень интересно возможность интеграции с игровыми движками, например как мин3д, ну или libgdx, да впрочем с любыми…? Вопрос может не очень корректен, т.к. только начинаю изучать яву, начал с разбора движка мин3д…
Я сейчас портирую игрушки с iPhone, поэтому и стал статьи про ndk писать, ибо актуально. По поводу интеграции…вы хотите что-то свою дописать в библиотеку LibGDX? Или я вас не так понял? )
Уведомление: Основы Android NDK: вызов Java-методов из C/C++ кода при помощи JNI | Suvitruf's Blog
Дописать да, но не в либгдх, работаю с 3д приложениями…
Если вы либу собираете сами, то можно что угодно дописать.
Если вы используете уже собранную С++ библиотеку, то сложно сказать. Можно попробовать создать свою библиотеку, в которую вложить чью-то готовую и вызывать в своей либе методы той готовой библиотеки.
В принципе, я когда в native работаю с zip или OpenGL, то юзаю уже готовые библиотеки. Другое дело, что вам заголовочные файлы всё равно нужны.
Уведомление: Основы Android NDK: доступ к ресурсам директории assets из C++ | Suvitruf's Blog
Уведомление: Основы Android NDK: работа с OpenAL и форматами WAV, OGG | Suvitruf's Blog
Извиняюсь, за глупый вопрос. не подскажете а вот библиотеки которые мы подгружаем в коде на С++, они как подгружаются? Мне просто необходимо openssl включить и возник данный вопрос. Буду благодарен за ответ, если конечно вы еще просматриваете эту тему.
Примерно как тут. Собираете openssl как static библиотеку, а потом подключаете в Android.mk
openssl (немного урезанный) есть на каждом андроидном девайсе, так что если его вам достаточно, можно пользоваться системной библиотекой. В Андроиде работает обычный линуксовый dlopen(), но нельзя установить LD_LIBRARY_PATH.
Можно писать сразу на android- устройстве. Скопипастю новость (сори, что на англ) «I’d recommend CppDroid — new C/C++ IDE on Android. It has a lot of included C/C++ examples and tutorials. Blog: http://cppdroid.blogspot.com, Google Play: https://play.google.com/store/apps/details?id=name.antonsmirnov.android.cppdroid«
Eclipse -> Window -> Preferences -> Android -> NDK
У меня нет такой вкладки. У меня ADT Bundle, качал с офсайта неделю назад. Может с тех пор что-то изменилось? В PATH путь к NDK прописал.
У меня на работе такая же фигня была. Я удалил все компоненты ADT, CDT и все плагины для C++. Установил заново и всё заработало.