New Android NDK & JNI
-
Upload
studio-stfalconcom -
Category
Software
-
view
347 -
download
2
Transcript of New Android NDK & JNI
Я очень рад снова оказаться в
Хмельницком :)
Что такое JNI/NDK? Быстродействие, много готовых либ, платформозависимость, не- "Write once, run anywhere" (WORA), аналогия с Reflection, используемые типы и вызовы
Настройка окружения (NDK x 2), отладка, поддержка и тестирование всех платформ
Особенности выполнения кода (BlackBerry10 (mktime()), IntelAtom, х64), tmpdir(), флаги оптимизации, порезаный Bionic и прочие либы в Андроид, удаление SO-шек на Sony при апдейте
Рассмотрим….
Wiki: Java Native Interface (JNI) — стандартный механизм для запуска кода, под управлением виртуальной машины Java (JVM), который написан на языках С/С++ или Ассемблера, и скомпонован в виде динамических библиотек, позволяет не использовать статическое связывание. Это даёт возможность вызова функции С/С++ из программы на Java, и наоборот. Более ранние интерфейсы, в отличие от JNI, не удовлетворяли условию двоичной совместимости.Wiki: В 2009 году в дополнение к ADT был опубликован Android Native Development Kit (NDK) — пакет инструментариев и библиотек, позволяющий реализовать часть приложения на языке С/С++. NDK рекомендуется использовать для разработки участков кода, критичных к скорости.
JNI/NDK
Реально же наиболее частое применение:Работа с OpenGL ESИспользование кросс-платформенных игровых движков, например Cocos2DxИспользование уже написанного на C/C++ кода (а его ох как дофига написано!). Часто, это работа с мультимедиа, например FFMPEG, libpng или наукоемкие вещи типа openCV«Обход» «бутылочного горлышка» в программе (UP! «тяжелых» процессов)Кроссплатформенное (повторное) использование кода
JNI/NDK
Работа с 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 и выше).
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++.
Именование
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
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 -
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 );
Методы
Внутренности JNI в Android
Устаналивается APKЕсли внутри находятся SO-файлы (аналог DLL) они копируются в
/data/data/apppackage/app_lib/*.soПри первом обращении к классу, использующему нативные библиотеки,
последние загружаются через System.load(«name»)Библиотека «живет» пока не будет завершено приложение
Как работает взаимодействие между
Java и Native
Качаем 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
javah создает файлы заголовков и исходники C из Java класса. Эти файлы обеспечивают связь, которая позволит взаимодействовать вашему Java и C коду
javah -classpath bin/classes -jni -d jni com.my.javaclass
javah
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
APP_PLATFORM := android-9APP_STL := stlport_staticAPP_ABI := all32APP_CPPFLAGS += -std=c++11
Before (Ant/Eclipse) Application.mk
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']) }
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
http://tools.android.com/tech-docs/new-build-system/gradle-experimental
Standart Template Library
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
@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
Сборка из командной строки
- Логгирование __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)
Отладка
- 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
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
http://www.learnopengles.com/a-performance-comparison-between-java-and-c-on-the-nexus-5/
Benchmarks
Blackberry10 и mktimeAndroid 5 (копирование массивов)tmpdir()/tmpfile()флаги оптимизации ( -03,-Ofast, -SSE etc) порезаный Bionic и прочие либы в Андроид*.so на SonyПроблема 01.01.2037TTF
Грабли
Code Examples
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); }
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);
}
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);
}
/* 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
#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;}
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; }
}
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);
}