
Нелегко на рынке найти senior разработчика для конкретной сферы. Каждый из них имеет уникальные знания в своем языке и фреймворке, будь это Java, Kotlin, С++, JS или Swift. Каждый накапливал свои знания годами. Но найти senior разработчика, который одинаково хорош и C++ и в Kotlin, утром переписывает openssl, а по вечерам пинает мидлов за неправильное использование корутин, почти нереально.
Для каждого проекта нужны высококлассные специалисты, поддерживающие проект на плаву, развивающие его, предоставляя клиенту новые фичи, а заказчику большие деньги. Уход каждого специалиста для компании приносит новые убытки, но вот уход ndk специалиста может привести проект к краху. Ладно, не все так драматично. Если у вас все под контролем.
Почему вообще мы говорим о ndk, и для чего нужен мост C++ в Android. Мир нативных разработок и библиотек огромен, а некоторые вещи порой просто так не сделаешь полностью в Protobuf vs Jni. И раз тут интриги нет, то 
Все начинается, как ни странно, с рефлексии. С использованием обфускаторов и оптимизаторов байт кода, любой метод может потеряться и в непредвиденном месте вызвать падение, прям в руках преданного пользователя.
jclass jCounterClass = env->FindClass("com/github/klee0kai/kotlinndktest/JniCounter"); jmethodID initMethod = env->GetMethodID(jClassIndex->jCounterClass, "<init>", "()V");
Для kotlin разработчиков рефлексия может вызвать еще большую боль. При использовании вы можете теперь встретить различные преобразования типов в коде и в реалтайме. Тип kotlinx.Int может быть представлен как примитивный тип int, а вот kotlinx.Int? будет представлен как java.lang.Integer. Различные методы, поля, в особенности те, которые используют уникальные для котлина типы: UByte, UInt, могут в реалтайме приобрести уже уникальные имена.
Кроме рефлексии, вы можете столкнуться с проблемами контроля памяти, поиском утечек памяти. К примеру, для jvm можно выполнять слепки памяти, просматривать графы использования экземпляра класса до GC Root, в Android можно даже реализовать утилиту leakcanary.
В C++ мире отработаны механизмы подобно ASan. Но если у вас в одном процессе и C++ и jvm, то отследить утечки памяти сложнее. По jvm у вас объект удерживается сразу GC Root, а как это соотнести с кодом. ASan — тоже особо информации не предоставляет.
Подобные утечки ресурсов могут появится на ровном месте. Для любого маппинга модели из jvm в C++ и обратно может потребоваться копирование текстовых переменных, временных массивов и много другого. Все это проскочит ревью, не проявится на тестах, и только у пользователя с особым желанием потыкать приложение может выскочить ошибка памяти. Особым видам к таким ошибкам может быть к примеру использование указателей на объекты был представлен его отображением в нативной библиотеке. Вообще «Бруклин» возник не на пустом месте. Автору довелось неделями заниматься маппингом моделей из Kotlin в C++, проверяя все вызовы, все преобразования строк и массивов. В конченом итоге, написывая одно и тоже по несколько раз, формируется уже четкий шаблон однообразных действий. Но так как такого кода в любом случае получается много, и даже набитая рука сможет наделать ошибок. Лучше, если за нас будет писать плагин. Каждая итерация компиляции проекта теперь позволяет отслеживать все изменения для дата классов и зеркалированных классов. Kotlin compiler plugin, подключаемый напрямую в gradle сборку, теперь самостоятельно генерирует файлы для индексирования jvm. Конечно мы не можем избавиться от падений в реалтайме при использовании рефлексии, но свести к минимуму, перенеся всю рефлексию только на этап инициализации библиотеки — можно. А вот реалтайм ошибки типа Остается последнее действие. Скорректировать последовательность сборки проекта, к примеру для Android проектов. Надеюсь бруклинский мост кому-нибудь спасет горящие релизы. Главное позволит полноценно разделить подпроекты на самостоятельные тестируемые модули. Так kotlin код можно тестировать в kotlin, C++ код можно тестировать посредством GTest или других инструментов плюсов. Мост же уже протестирован в библиотеке, а значит ваше приложение будет ближе к стабильности. Пет-проект ждет вашего участия. Планируем развивать инкрементальную сборку, поддержку обфускации и вложенных массивов. jobject, jclass не по ЖЦ. Это приводит не к привычной ошибке @JniMirror object NdkEngine { init { Brooklyn.load("kotlinndktest") } fun helloFromJvm(): String = "hello from jvm" external fun helloFromCppPro(): String }
class ComGithubKlee0kaiKotlinndktestNdkEngine { public: static std::string helloFromJvm(); static std::string helloFromCppPro(); private: static jobject jvmSelf; };int init(JNIEnv *env) { if (comGithubKlee0kaiKotlinndktestNdkEngineIndex) return 0; comGithubKlee0kaiKotlinndktestNdkEngineIndex = std::make_shared<ComGithubKlee0kaiKotlinndktestNdkEngineIndexStructure>(); comGithubKlee0kaiKotlinndktestNdkEngineIndex->cls = (jclass) env->NewGlobalRef( env->FindClass("com/github/klee0kai/kotlinndktest/NdkEngine")); comGithubKlee0kaiKotlinndktestNdkEngineIndex->init1 = env->GetMethodID( comGithubKlee0kaiKotlinndktestNdkEngineIndex->cls, "<init>", "()V"); if (!comGithubKlee0kaiKotlinndktestNdkEngineIndex->init1) return -1; comGithubKlee0kaiKotlinndktestNdkEngineIndex->helloFromJvm1 = env->GetMethodID( comGithubKlee0kaiKotlinndktestNdkEngineIndex->cls, "helloFromJvm", "()Ljava/lang/String;"); if (!comGithubKlee0kaiKotlinndktestNdkEngineIndex->helloFromJvm1) return -1; comGithubKlee0kaiKotlinndktestNdkEngineIndex->helloFromCppPro1 = env->GetMethodID( comGithubKlee0kaiKotlinndktestNdkEngineIndex->cls, "helloFromCppPro", "()Ljava/lang/String;"); if (!comGithubKlee0kaiKotlinndktestNdkEngineIndex->helloFromCppPro1) return -1; comGithubKlee0kaiKotlinndktestNdkEngineIndex->hashCode1 = env->GetMethodID( comGithubKlee0kaiKotlinndktestNdkEngineIndex->cls, "hashCode", "()I"); if (!comGithubKlee0kaiKotlinndktestNdkEngineIndex->hashCode1) return -1; comGithubKlee0kaiKotlinndktestNdkEngineIndex->toString1 = env->GetMethodID( comGithubKlee0kaiKotlinndktestNdkEngineIndex->cls, "toString", "()Ljava/lang/String;"); if (!comGithubKlee0kaiKotlinndktestNdkEngineIndex->toString1) return -1; return 0; }No implementation found for можно вообще не боятся. При любом переносе exported метода генерируется новый интерфейсный метод для нативной библиотеки, и дальнейший проброс вызова в stub класс в C++.extern "C" JNIEXPORT jstring JNICALL Java_com_github_klee0kai_kotlinndktest_NdkEngine_helloFromCppPro( JNIEnv *env, jobject jObject ) { brooklyn::bindEnv(env); auto mirror = brooklyn::ComGithubKlee0kaiKotlinndktestNdkEngine(jObject); auto jvmResultObj = mirror.helloFromCppPro(); return brooklyn::mapper::mapToJString(env, std::make_shared<std::string>(jvmResultObj)); }afterEvaluate { val kotlinCompileTasks = tasks.filterIsInstance<JavaCompile>() val cmakeTasks = (tasks.filterIsInstance<com.android.build.gradle.tasks.ExternalNativeBuildTask>() + tasks.filterIsInstance<com.android.build.gradle.tasks.ExternalNativeBuildJsonTask>() ) cmakeTasks.forEach { cmakeTask -> kotlinCompileTasks.forEach { kotlinTask -> cmakeTask.mustRunAfter(kotlinTask) } } }Сухой остаток
ссылка на оригинал статьи https://habr.com/ru/articles/773434/
Добавить комментарий