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 JNICALL

Java_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
*/

#else

typedef 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 JNICALL

JNI_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.
Раз.
Два.
Три.
Download and enjoy!
https://goo.gl/yn4ZmC
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

Дмитрий Юницкий. «Android NDK или как я перестал бояться и полюбил нативную разработку».

  • 1.
    Android NDK, иликак я перестал бояться и полюбил нативную разработку.
  • 2.
    Android NDK. • JNI- набор инструментов для запуска кода на C/ C++/Ассемблере из виртуальной машины Java • Вспомогательная нативная библиотека(скомпилированная для каждой поддерживаемой ABI) или полностью нативное приложение используя NativeActivity • Java -> нативный код, нативный код -> Java, Java -> нативный код -> Java…
  • 3.
    Зачем? • Критичные попроизводительности участки кода • Использование написанного ранее кода или существующих библиотек (OpenCV, ffmpeg, …) • Написание кроссплатформенного кода для нескольких платформ(бизнес-логика, алгоритмы и тд)
  • 4.
    Какие возникают сложности? • Работас jni из нативных потоков • Отладка • Возрастающий размер результирующей apk • Локальные/глобальные ссылки • Дорогой переход из java кода в нативный (и обратно) • Прочие приятные сюрпризы от jni)
  • 5.
    Базовый пример. public classIntegrationActivity 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 JNICALL
 Java_my_test_integration_IntegrationActivity_nativeGetBooleanValue(JNIEnv *env, jobject instance) {
 SomeCppClass object;
 return (jboolean) object.getSomeValue();
 }
 
 } // extern "C" Java C++
  • 6.
    Типы данных JNI. #ifdefHAVE_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 */
 #else
 typedef 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; Примитивные Ссылочный
  • 7.
    JNIEnv и функции JNI. •JNIEnv • IsSameObject, Call*Method, Get*Field, Set*Field, New*Array… • NewGlobalRef, DeleteGlobalRef, DeleteLocalRef • AttachCurrentThread, DetachCurrentThread • JNI_OnLoad, JNI_OnUnload
  • 8.
    Локальные и глобальные ссылки. •Объекты, на которые есть ссылки - не могут быть очищены GC • Локальные ссылки - в пределах жизни метода в рамках одного потока • Java VM автоматически очищает ссылки при возврате из нативного метода • Есть предел на количество создаваемых локальных ссылок • Глобальные ссылки действительны вплоть до явного освобождения
  • 9.
    Таблица локальных и глобальныхссылок. 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);
 }
  • 10.
    Нативные потоки. • AttachCurrentThreadдля работы с jni • DetachCurrentThread перед завершением • Локальные ссылки НЕ очищаются - нужно всегда(!) очищать все созданные ссылки с помощью DeleteLocalRef либо создавать отдельный пул ссылок при входе в метод через PushLocalFrame/PopLocalFrame • FindClass НЕ работает
  • 11.
    Нативные потоки. FindClass. • Простойвариант решения - кешировать класс в JNI_OnLoad. • Более сложный, но гибкий - кешировать сам ClassLoader.
  • 12.
    Кеширование ClassLoader. JNIEXPORT jintJNICALL
 JNI_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;
  • 13.
    Кеширование jClass, jMethodId, jFieldId.•jClass -локальные ссылки, для кеширования необходимо преобразовывать в глобальные. • jMethodId, jFieldId - просто структуры, можно записывать без преобразования в статические или глобальные переменные и использовать в любых потоках.
  • 14.
    Кеширование jClass, jMethodId, jFieldId.•jClass -локальные ссылки, для кеширования необходимо преобразовывать в глобальные. • jMethodId, jFieldId - просто структуры, можно записывать без преобразования в статические или глобальные переменные и использовать в любых потоках. • В некоторых случаях работа через jni может быть ЗНАЧИТЕЛЬНО медленнее аналогичного кода на Java.
  • 15.
    Запуск native методаиз Java. • Создать новый стекфрейм • Передать аргументы согласно ABI • Передать JNIEnv* и jclass(jobject) • Synchronized • Проверить исключения • …
  • 16.
    Сборка проекта. Какбыло раньше. • MAKEFILES • Javah • SWIG • Ecplise plugin Sequoyah • …
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
    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