
Использование нативного кода, написанного на 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.