36
Android NDK & JNI Sergey Komlach GDG Kremenchuk, StickyPassword Skype: s.komlach sergey.komlach@stickypassword. com

New Android NDK & JNI

Embed Size (px)

Citation preview

Page 1: New Android NDK & JNI

Android NDK & JNI

Sergey KomlachGDG Kremenchuk, StickyPassword

Skype: [email protected]

Page 2: New Android NDK & JNI

Я очень рад снова оказаться в

Хмельницком :)

Page 3: New Android NDK & JNI

Что такое JNI/NDK? Быстродействие, много готовых либ, платформозависимость, не- "Write once, run anywhere" (WORA), аналогия с Reflection, используемые типы и вызовы

Настройка окружения (NDK x 2), отладка, поддержка и тестирование всех платформ

Особенности выполнения кода (BlackBerry10 (mktime()), IntelAtom, х64), tmpdir(), флаги оптимизации, порезаный Bionic и прочие либы в Андроид, удаление SO-шек на Sony при апдейте

Рассмотрим….

Page 4: New Android NDK & JNI

Wiki: Java Native Interface (JNI) — стандартный механизм для запуска кода, под управлением виртуальной машины Java (JVM), который написан на языках С/С++ или Ассемблера, и скомпонован в виде динамических библиотек, позволяет не использовать статическое связывание. Это даёт возможность вызова функции С/С++ из программы на Java, и наоборот. Более ранние интерфейсы, в отличие от JNI, не удовлетворяли условию двоичной совместимости.Wiki: В 2009 году в дополнение к ADT был опубликован Android Native Development Kit (NDK) — пакет инструментариев и библиотек, позволяющий реализовать часть приложения на языке С/С++. NDK рекомендуется использовать для разработки участков кода, критичных к скорости.

JNI/NDK

Page 5: New Android NDK & JNI

Реально же наиболее частое применение:Работа с OpenGL ESИспользование кросс-платформенных игровых движков, например Cocos2DxИспользование уже написанного на C/C++ кода (а его ох как дофига написано!). Часто, это работа с мультимедиа, например FFMPEG, libpng или наукоемкие вещи типа openCV«Обход» «бутылочного горлышка» в программе (UP! «тяжелых» процессов)Кроссплатформенное (повторное) использование кода

JNI/NDK

Page 6: New Android NDK & JNI

Работа с NDK на порядок усложняет разработку.- Разработчик должен понимать Java (само собой)- С/С++ (особенно внимание на память, указатели, треды/семафоры и т.д)- команды и принципы работы JVM (очень пригодится если вы уже работали с Reflection) Class cls = sample1.getClass(); try { cls.getDeclaredMethod("print", String.class).invoke(sample1, "sample class"); cls.getDeclaredMethod("print", String.class).invoke(sample1, "test string"); cls.getDeclaredMethod("print", null).invoke(sample2, null); cls.getDeclaredMethod("print", null).invoke(sample3, null); } catch (Exception e) {}

- Нужно учитывать большое количество ограничений JNI в Android (порезаные библиотека, размеры типов, «пустышки» реализаций)- Сложная настройка среды и особенно отладка

Таким образом, работа с NDK чаще всего представляем из себя процесс (часто — мучительный) сборки некой библиотеки и написание оберток (wrappers) на нативные методы. В тоже время, сейчас есть возможность ваять приложение практически без использования Java, используя NativeActivity (API 9 и выше).

Page 7: New Android NDK & JNI

package com.example; public class NativeTest{ static { System.loadLibrary("nativetest"); // libs/armeabi-v7a/libnativetest.so }

public native boolean testMethod(int arg); }

JNIEXPORT jboolean JNICALL Java_com_example_NativeTest_testMethod(JNIEnv *env, jobject caller, jint arg);

JNIEXPORT — необходимый для JNI модификатор. Типы данных с префиксом «j»: jdouble, jobject, jstring etc — это «отражения» объектов и типов Java в C/C++.

Именование

Page 8: New Android NDK & JNI

jstring JAVA_JNI_This_1Is_1Native_00024Wrapper_00024_000408_000413_000397(...)

Дело в том, что _1 это аналог нижнего подчёркивания._00024 это символ $, то есть может как разделитель внутреннего класса использоваться. _00408, 0xxxx, это код в юникоде.В итоге получается:

class JNI { static class This_Is_Native { static class Wrapper$ { static String Юникод(...) } }}

Именование, часть 2

Page 9: New Android NDK & JNI

Java JNI JNI array Code Array Code

boolean jboolean jbooleanArray Z [Z

byte jbyte jbyteArray B [B

char jchar jcharArray C [C

double jdouble jdoubleArray D [D

float jfloat jfloatArray F [F

int jint jintArray I [I

long jlong jlongArray J [J

short jshort jshortArray S [S

Object/Class/String

jobject/jclass/ jstring

jobjectArray/-/-

L/L/L [L/[L/[L

void void - V -

Page 10: New Android NDK & JNI

https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html Методы зеркальны Java: NewString, GetStringLength, GetStringChars, ReleaseStringChars, NewStringUTF, GetStringUTFLength, GetStringUTFChars, ReleaseStringUTFChars,

jstring NewString(JNIEnv *env, const jchar *unicodeChars,jsize len);

char* cchars = “test me plz”; size_t clength = strlen(cchars);

return env->NewString((jchar*) cchars, (jsize) clength ); //equels NewString(env, (jchar*) cchars, (jsize) clength );

Методы

Page 11: New Android NDK & JNI

Внутренности JNI в Android

Page 12: New Android NDK & JNI

Устаналивается APKЕсли внутри находятся SO-файлы (аналог DLL) они копируются в

/data/data/apppackage/app_lib/*.soПри первом обращении к классу, использующему нативные библиотеки,

последние загружаются через System.load(«name»)Библиотека «живет» пока не будет завершено приложение

Как работает взаимодействие между

Java и Native

Page 13: New Android NDK & JNI

Качаем NDK. Если вам нужна поддержка только 32-битных архитектур, то нужно NDK x32 (для arm6, arm7a, x86 & mips), иначе можно исполользовать NDK x64 (arm8, x86_64 & mips_64 и все х32)

Устанавливаем окружение (путь к папке NDK) «цепляем» в IDEАльтернативные компиляторы: GCC, Clang, MinGWАльтернатива - Crysta X NDK (https://www.crystax.net/ru/android/ndk ) (wide

chars, C localizations, full math, C++11/C++14, Boost, Object-C/C++ etc.)Android* NDK for Intel® ArchitectureВыбираем STL

С чего начинается NDK

Page 14: New Android NDK & JNI

javah создает файлы заголовков и исходники C из Java класса. Эти файлы обеспечивают связь, которая позволит взаимодействовать вашему Java и C коду

javah -classpath bin/classes -jni -d jni com.my.javaclass

javah

Page 15: New Android NDK & JNI

Before (Ant/Eclipse) Android.mk

LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_CPP_FEATURES += rtti # Enable exceptions in Android.mkLOCAL_CPP_FEATURES += exceptions # Enable exceptions in Android.mkLOCAL_LDLIBS := -llog -lzLOCAL_MODULE := nativeTestLOCAL_CFLAGS := -DANDROID -O3 -pipeLOCAL_CXXFLAGS := -DANDROID -O3 -pipeLOCAL_SRC_FILES :=com_test.cpp

Page 16: New Android NDK & JNI

APP_PLATFORM := android-9APP_STL := stlport_staticAPP_ABI := all32APP_CPPFLAGS += -std=c++11

Before (Ant/Eclipse) Application.mk

Page 17: New Android NDK & JNI

Now: Like a pro (see NDKSamples)

Gradle/Android Studio

ndk { moduleName = 'sanangeles' CFlags.addAll(['-DANDROID_NDK', '-DDISABLE_IMPORTGL']) CFlags.addAll(['-Wall', '-Werror'])

ldLibs.addAll(['android', 'log', 'dl', 'GLESv1_CM']) abiFilters.addAll(['armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64']) }

Page 18: New Android NDK & JNI

Migrating from Traditional Android Gradle Plugin

A typical Android Studio project may have a directory structure as follows. File that needs to be change is highlighted in red:

There are some significant changes in the DSL between the new plugin and the traditional one.

.

├── app/

│ ├── app.iml

│ ├── build.gradle

│ └── src/

├── build.gradle

├── gradle/

│ └── wrapper/

│ ├── gradle-wrapper.jar

│ └── gradle-wrapper.properties

├── gradle.properties

├── gradlew*

├── gradlew.bat

├── local.properties

├── MyApplication.iml

└── settings.gradle

Page 20: New Android NDK & JNI

Standart Template Library

Page 21: New Android NDK & JNI

Standart Template Librarylibstdc++(default) The default minimal system C++ runtime library. N/A

gabi++_static The GAbi++ runtime (static). C++ Exceptions and RTTI

gabi++_shared The GAbi++ runtime (shared). C++ Exceptions and RTTI

stlport_static The STLport runtime (static). C++ Exceptions and RTTI; Standard Library

stlport_shared The STLport runtime (shared). C++ Exceptions and RTTI; Standard Library

gnustl_static The GNU STL (static). C++ Exceptions and RTTI; Standard Library

gnustl_shared The GNU STL (shared). C++ Exceptions and RTTI; Standard Library

c++_static The LLVM libc++ runtime (static). C++ Exceptions and RTTI; Standard Library

c++_shared The LLVM libc++ runtime (shared). C++ Exceptions and RTTI; Standard Library

Page 22: New Android NDK & JNI

@echo offecho Build

set ROOT=%CD%echo %%ROOT%% = %ROOT%del build.log 2>nulcall D:\android\android-ndk\ndk-build.cmd cleancall D:\android\android-ndk\ndk-build.cmd >>build.log 2>&1pause

Сборка из командной строки

Page 23: New Android NDK & JNI
Page 24: New Android NDK & JNI

- Логгирование __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)- StackTrace в LogCatDalvik:F/libc (17861): invalid address or address of corrupt block 0x7f51ce50 passed to dlfreeF/libc (17861): Fatal signal 11 (SIGSEGV) at 0xdeadbaad (code=1), thread 29266 (Thread-9488)ART:A/art(21149): sart/runtime/check_jni.cc:65] JNI DETECTED ERROR IN APPLICATION: input is not valid Modified UTF-8: illegal start byte 0xa3….A/art(21149): sart/runtime/check_jni.cc:65] native: #07 pc 000bfaad /system/lib/libart.so (art::CheckJNI::NewStringUTF(_JNIEnv*, char const*)+44)A/art(21149): sart/runtime/check_jni.cc:65] native: #08 pc 00008fbb /data/app/com.stickypassword.android-1/lib/arm/libSPCAgent.so (setValue(_jobject*, int, long, char*, char*)+202)A/art(21149): sart/runtime/check_jni.cc:65] native: #09 pc 00009ac7 /data/app/com.stickypassword.android-1/lib/arm/libSPCAgent.so (Java_com_spc_SPCManager_GetAuthCredentialsV2+82)

Отладка

Page 25: New Android NDK & JNI

- GDB (GNU Debugger) — переносимый отладчик проекта GNU, который работает на многих UNIX-подобных системах и умеет производить отладку многих языков программирования, включая Си, C++, Free Pascal, FreeBASIC, Ada и Фортран. GDB — свободное программное обеспечение, распространяемое по лицензии GPL.

- NVIDIA Debug Manager for Android NDK https://developer.nvidia.com/nvidia-debug-manager-android-ndk

- CoffeeCatch (https://github.com/xroche/coffeecatch) - небольшая библиотека для перехвата POSIX сигналов и имитирующая работу try{} catch(){} из Java COFFEE_TRY() { recurse_madness(42); *fault = 0; } COFFEE_CATCH() { const char*const message = coffeecatch_get_message(); snprintf(string_buffer, sizeof(string_buffer), "%s", message); *fault = 1; } COFFEE_END();

GDB & CoffeCatch

Page 26: New Android NDK & JNI

http://forum.xda-developers.com/showthread.php?t=2754997 - Optimized for speed yet more all instructions - ARM and THUMB (-O3)- Optimized for speed also parts which are compiled with Clang (-O3)- Turned off all debugging code (lack of -g)- Eliminated redundant loads that come after stores to the same memory location, both partial and full redundancies (-fgcse-las)- Ran a store motion pass after global common subexpression elimination. This pass attempts to move stores out of loops (-fgcse-sm)- Performed interprocedural pointer analysis and interprocedural modification and reference analysis (-fipa-pta)- Performed induction variable optimizations (strength reduction, induction variable merging and induction variable elimination) on trees (-fivopts)- Didn't keep the frame pointer in a register for functions that don't need one. This avoids the instructions to save, set up and restore frame pointers; it also makes an extra register available in many functions (-fomit-frame-pointer)- Attempted to avoid false dependencies in scheduled code by making use of registers left over after register allocation. This optimization most benefits processors with lots of registers (-frename-registers)

Flags

Page 28: New Android NDK & JNI

Blackberry10 и mktimeAndroid 5 (копирование массивов)tmpdir()/tmpfile()флаги оптимизации ( -03,-Ofast, -SSE etc) порезаный Bionic и прочие либы в Андроид*.so на SonyПроблема 01.01.2037TTF

Грабли

Page 29: New Android NDK & JNI

Code Examples

Page 30: New Android NDK & JNI

package com.example.testsimplycall;

public class NativeTest { static { try{ System.loadLibrary("testSimplyCall");

} catch (UnsatisfiedLinkError err){ //need catch exception err.printStackTrace();}

}

public native void testMePlz(String msg); }

Page 31: New Android NDK & JNI

project/jni/com_example_testsimplycall_NativeTest.h/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>#ifndef _Included_com_example_testsimplycall_NativeTest#define _Included_com_example_testsimplycall_NativeTest#ifdef __cplusplusextern "C" {#endifJNIEXPORT void JNICALL Java_com_example_testsimplycall_NativeTest_testMePlz(JNIEnv *env,jobject jobj, jstring msg);#ifdef __cplusplus}#endif#endif

project/jni/com_example_testsimplycall_NativeTest.cpp#include <def.h>#include <jni.h>#include "com_example_testsimplycall_NativeTest.h"JNIEXPORT void JNICALL Java_com_example_testsimplycall_NativeTest_testMePlz(JNIEnv *env,

jobject jobj, jstring msg){

jboolean isCopy;const char * Str = env->GetStringUTFChars(msg, &isCopy);LOGI("string = \"%s\"",Str);

}

Page 32: New Android NDK & JNI

package com.example.testcallback;import android.util.Log;

public interface NativeCallback { public void print(String str);}

public class NativeTest { static { try{ System.loadLibrary("testCallback");

} catch (UnsatisfiedLinkError err){ err.printStackTrace(); }

}

public NativeCallback nativecallback = new NativeCallback(){@Overridepublic void print(String str) {

Log.d("JAVA_CALLBACK", str);

}};public native void testMePlz(String msg); public native void SetListener(NativeCallback javacallback);

}

Page 33: New Android NDK & JNI

/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>#ifndef _Included_com_example_testcallback_NativeTest#define _Included_com_example_testcallback_NativeTest#ifdef __cplusplusextern "C" {#endif

JNIEXPORT void JNICALL Java_com_example_testcallback_NativeTest_testMePlz(JNIEnv *env,

jobject jobj, jstring msg);

JNIEXPORT void JNICALL Java_com_example_testcallback_NativeTest_SetListener(JNIEnv *env, jobject jobj, jobject callback);

#ifdef __cplusplus}#endif#endif

Page 34: New Android NDK & JNI

#include <jni.h>#include <string.h>#include <stdio.h>#include <stdlib.h>#include "com_example_testcallback_NativeTest.h"jobject javaCallback;JavaVM* mJVM;

/* Reference to Java-object*/JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved){ mJVM = jvm; return JNI_VERSION_1_2;}

JNIEnv* getJniEnv() { JavaVMAttachArgs attachArgs; attachArgs.version = JNI_VERSION_1_2; attachArgs.name = ">>>NativeThread__Any"; attachArgs.group = NULL;

JNIEnv* env;

if (mJVM->AttachCurrentThread(&env, &attachArgs) != JNI_OK) {env = NULL;

}

return env;}

Page 35: New Android NDK & JNI

void printToLogcat(const char* msg) {

JNIEnv* pEnv = getJniEnv();

jclass javaClass = pEnv->GetObjectClass(javaCallback);

if (javaClass != NULL) {

jmethodID javaMethodID = pEnv->GetMethodID(javaClass,"print","(Ljava/lang/String;)V");

jstring logmsg = pEnv->NewStringUTF(msg);

if (logmsg != NULL) {

pEnv->CallVoidMethod(javaCallback, javaMethodID, logmsg);

pEnv->DeleteLocalRef(logmsg);

logmsg = NULL; }

pEnv->DeleteLocalRef(javaClass);

javaClass = NULL; }

}

Page 36: New Android NDK & JNI

JNIEXPORT void JNICALL Java_com_example_testcallback_NativeTest_SetListener(JNIEnv *env, jobject jobj, jobject callback) {

if(javaCallback) env->DeleteGlobalRef(javaCallback); javaCallback = env->NewGlobalRef(callback); }

JNIEXPORT void JNICALL Java_com_example_testcallback_NativeTest_testMePlz(JNIEnv *env,jobject jobj, jstring msg){

jboolean isCopy;const char * Str = env->GetStringUTFChars(msg, &isCopy);printToLogcat(Str);

}