Дмитрий Юницкий. «android ndk или как я перестал бояться и...
TRANSCRIPT
Android NDK, или как я перестал бояться и полюбил
нативную разработку.
Android NDK.• JNI - набор инструментов для запуска кода на C/
C++/Ассемблере из виртуальной машины Java
• Вспомогательная нативная библиотека(скомпилированная для каждой поддерживаемой ABI) или полностью нативное приложение используя NativeActivity
• Java -> нативный код, нативный код -> Java, Java -> нативный код -> Java…
Зачем?
• Критичные по производительности участки кода
• Использование написанного ранее кода или существующих библиотек (OpenCV, ffmpeg, …)
• Написание кроссплатформенного кода для нескольких платформ(бизнес-логика, алгоритмы и тд)
Какие возникают сложности?
• Работа с jni из нативных потоков
• Отладка
• Возрастающий размер результирующей apk
• Локальные/глобальные ссылки
• Дорогой переход из java кода в нативный (и обратно)
• Прочие приятные сюрпризы от jni)
Базовый пример.public class IntegrationActivity extends AppCompatActivity{ … @Override protected void onCreate(Bundle savedInstanceState) { … textView.setText("Value from native code: " + nativeGetBooleanValue()); } public native boolean nativeGetBooleanValue(); static { System.loadLibrary("ndk_integration"); }}
#include <jni.h>#include "SomeCppClass.hpp"extern "C"{ JNIEXPORT jboolean JNICALLJava_my_test_integration_IntegrationActivity_nativeGetBooleanValue(JNIEnv *env, jobject instance) { SomeCppClass object; return (jboolean) object.getSomeValue();} } // extern "C"
Java
C++
Типы данных JNI.#ifdef HAVE_INTTYPES_H# include <inttypes.h> /* C99 */typedef uint8_t jboolean; /* unsigned 8 bits */ typedef int8_t jbyte; /* signed 8 bits */typedef uint16_t jchar; /* unsigned 16 bits */ typedef int16_t jshort; /* signed 16 bits */typedef int32_t jint; /* signed 32 bits */typedef int64_t jlong; /* signed 64 bits */typedef float jfloat; /* 32-bit IEEE 754 */ typedef double jdouble; /* 64-bit IEEE 754 */ #elsetypedef unsigned char jboolean; /* unsigned 8 bits */ typedef signed char jbyte; /* signed 8 bits */typedef unsigned short jchar; /* unsigned 16 bits */ typedef short jshort; /* signed 16 bits */typedef int jint; /* signed 32 bits */typedef long long jlong; /* signed 64 bits */typedef float jfloat; /* 32-bit IEEE 754 */ typedef double jdouble; /* 64-bit IEEE 754 */ #endif
typedef void* jobject;typedef jobject jclass;typedef jobject jstring;typedef jobject jarray;typedef jarray jobjectArray;typedef jarray jbooleanArray;typedef jarray jbyteArray;typedef jarray jcharArray;typedef jarray jshortArray;typedef jarray jintArray;typedef jarray jlongArray;typedef jarray jfloatArray;typedef jarray jdoubleArray;typedef jobject jthrowable;typedef jobject jweak;
Примитивные Ссылочный
JNIEnv и функции JNI.
• JNIEnv
• IsSameObject, Call*Method, Get*Field, Set*Field, New*Array…
• NewGlobalRef, DeleteGlobalRef, DeleteLocalRef
• AttachCurrentThread, DetachCurrentThread
• JNI_OnLoad, JNI_OnUnload
Локальные и глобальные ссылки.
• Объекты, на которые есть ссылки - не могут быть очищены GC
• Локальные ссылки - в пределах жизни метода в рамках одного потока
• Java VM автоматически очищает ссылки при возврате из нативного метода
• Есть предел на количество создаваемых локальных ссылок
• Глобальные ссылки действительны вплоть до явного освобождения
Таблица локальных и глобальных ссылок.
void DumpDalvikReferenceTables(){ JNIEnv * env = jni::GetEnv(); jclass vm_class = env->FindClass("dalvik/system/VMDebug"); jmethodID dump_mid = env->GetStaticMethodID(vm_class, "dumpReferenceTables", "()V"); env->CallStaticVoidMethod(vm_class, dump_mid); env->DeleteLocalRef(vm_class);}
Нативные потоки.• AttachCurrentThread для работы с jni
• DetachCurrentThread перед завершением
• Локальные ссылки НЕ очищаются - нужно всегда(!) очищать все созданные ссылки с помощью DeleteLocalRef либо создавать отдельный пул ссылок при входе в метод через PushLocalFrame/PopLocalFrame
• FindClass НЕ работает
Нативные потоки. FindClass.
• Простой вариант решения - кешировать класс в JNI_OnLoad.
• Более сложный, но гибкий - кешировать сам ClassLoader.
Кеширование ClassLoader.
JNIEXPORT jint JNICALLJNI_OnLoad(JavaVM *jvm, void *) { g_jvm = jvm; JNIEnv *env = jni::GetEnv(); auto randomClass = env->FindClass("my/test/integration/IntegrationActivity"); auto classClass = env->GetObjectClass(randomClass); auto classLoaderClass = env->FindClass("java/lang/ClassLoader"); auto getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader", "()Ljava/lang/ClassLoader;"); g_classLoader = env->NewGlobalRef(env->CallObjectMethod(randomClass, getClassLoaderMethod)); g_findClassMethod = env->GetMethodID(classLoaderClass, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;"); return JNI_VERSION_1_6;
Кеширование jClass, jMethodId, jFieldId.
• jClass -локальные ссылки, для кеширования необходимо преобразовывать в глобальные.
• jMethodId, jFieldId - просто структуры, можно записывать без преобразования в статические или глобальные переменные и использовать в любых потоках.
Кеширование jClass, jMethodId, jFieldId.
• jClass -локальные ссылки, для кеширования необходимо преобразовывать в глобальные.
• jMethodId, jFieldId - просто структуры, можно записывать без преобразования в статические или глобальные переменные и использовать в любых потоках.
• В некоторых случаях работа через jni может быть ЗНАЧИТЕЛЬНО медленнее аналогичного кода на Java.
Запуск native метода из Java.
• Создать новый стекфрейм
• Передать аргументы согласно ABI
• Передать JNIEnv* и jclass(jobject)
• Synchronized
• Проверить исключения
• …
Сборка проекта. Как было раньше.
• MAKEFILES
• Javah
• SWIG
• Ecplise plugin Sequoyah
• …
New experimental Gradle plugin.
Раз.
Два.
Три.
More info.• http://developer.android.com/training/articles/perf-jni.html
• https://developer.android.com/ndk/guides/concepts.html
• http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html
• http://tools.android.com/tech-docs/new-build-system/gradle-experimental
• http://normanmaurer.me/blog/2014/01/07/JNI-Performance-Welcome-to-the-dark-side/
• http://compmus.ime.usp.br/sbcm/2013/pt/docs/pos_tec_4.pdf
• http://janet-project.sourceforge.net/papers/jnibench.pdf