Реалізація JNI callbacks в Android NDK
...
— Дістало мене це «всередині немає деталей, що обслуговуються користувачем». Хочеться подивитися, що ж там є.
...
— Російська матрьошка до самої глибини. Правда, Ороско? Хуан не став дивитися, що таке російська матрьошка.
— Та це ж сміття, професор Гу. Кому воно треба — з таким возитися?
«Кінець веселок» Вернор Віндж
Регулярно виникає потреба в реалізації патерну «Спостерігач» в проектах. Можна просто підключити ReactiveX або EventBus і не морочитися, але все-таки іноді хочеться скоротити кількість залежностей проекту. Та й кращий спосіб навчитися чого-небудь — зробити це своїми руками.
Трохи теорії та історії
Патерн «Спостерігач-Передплатник» — це механізм, який дозволяє об'єкту отримувати оповіщення про змінення стану інших об'єктів і тим самим спостерігати за ними. Робиться для зменшення зв'язності і залежностей між програмними компонентами, що дозволяє ефективніше їх використовувати і тестувати. Яскравий представник, в якому мовна концепція побудована на цьому всьому — Smalltalk, весь заснований на ідеї посилки повідомлень. Вплинув на Objective-C.
Припустимо, ми любимо і вміємо писати свої велосипеди. І що ми отримаємо в результаті:
- що-небудь типу зворотного пересилання з C++ коду підписалися;
- управління обробкою в нативному коді, тобто ми можемо не заганятися з приводу розрахунків, коли немає передплатників і нікому їх відправляти.
- може знадобитися і пересилання даних між різними JVM;
- і щоб два рази не вставати, заодно і пересилання повідомлень всередині потоків проекту.
Реалізація
Спробуємо в кращих традиціях DIY, так би мовити, «помигати світлодіодом». Якщо ви використовуєте JNI, в світі Android NDK ви можете запитати метод Java асинхронно, в будь-якому потоці. Це ми і використовуємо для побудови свого «Спостерігача».
Демопроект побудований на шаблоні нового проекту з Android Studio.
Це автосгенерированный метод. Він прокоментував.
// Used to load the 'native-lib' library on application startup. static { System.дзвінки на loadlibrary("native-lib"); }
А тепер самі. Перший крок — метод nsubscribeListener.
private native void nsubscribeListener(JNIListener JNIListener);
Він дозволяє C++ коду отримати посилання на java-код для включення зворотного виклику до обєкта, що реалізує інтерфейс JNIListener.
public interface JNIListener { void onAcceptMessage(String string); void onAcceptMessageVal(int messVal); }
В реалізацію його методів і будуть передаватися значення.
Для ефективного кешування посилань на віртуальну машину зберігаємо і посилання на об'єкт. Отримуємо для нього глобальну посилання.
Java_ua_zt_mezon_myjnacallbacktest_mainactivity_nsubscribelistener(JNIEnv *env, jobject instance, jobject listener) { env->GetJavaVM(&jvm); //store jvm reference for later call store_env = env; jweak store_Wlistener = env->NewWeakGlobalRef(listener);
Відразу розраховуємо і зберігаємо посилання на методи. Це означає менше операцій потрібно для виконання зворотного виклику.
jclass clazz = env->GetObjectClass(store_Wlistener); jmethodID store_method = env->GetMethodID(clazz, "onAcceptMessage", "(Ljava/lang/String;)V"); jmethodID store_methodVAL = env->GetMethodID(clazz, "onAcceptMessageVal", "(I)V");
Дані про абонента зберігаються записи класу ObserverChain.
class ObserverChain { public: ObserverChain(jweak pJobject, jmethodID pID, jmethodID pJmethodID); jweak store_Wlistener = NULL; jmethodID store_method = NULL; jmethodID store_methodVAL = NULL; };
Зберігаємо передплатника в динамічний масив store_Wlistener_vector.
ObserverChain *tmpt = new ObserverChain(store_Wlistener, store_method, store_methodVAL); store_Wlistener_vector.push_back(tmpt);
Тепер про те, як будуть передаватися повідомлення з Java-коду.
Повідомлення, відправлене в метод nonNext, буде розіслано всім підписалися.
private native void nonNext(String message);
Реалізація.
Java_ua_zt_mezon_myjnacallbacktest_mainactivity_nonnext(JNIEnv *env, jobject instance, jstring message_) { txtCallback(env, message_); }
Функція txtCallback(env, message_); розсилає повідомлення всім підписалися.
void txtCallback(JNIEnv *env, const _jstring *message_) { if (!store_Wlistener_vector.empty()) { for (int i = 0; i < store_Wlistener_vector.size(); i++) { env->CallVoidMethod(store_Wlistener_vector[i]->store_Wlistener, store_Wlistener_vector[i]->store_method, message_); } } }
Для пересилання повідомлень З с++ або З коду використовуємо функцію test_string_callback_fom_c
void test_string_callback_fom_c(char *val)
Вона прямо зі старту перевіряє, чи є передплатники взагалі.
if (store_Wlistener_vector.empty()) return;
Легко побачити, що для посилки повідомлень використовується все та ж функція txtCallback.
void test_string_callback_fom_c(char *val) { if (store_Wlistener_vector.empty()) return; __android_log_print(ANDROID_LOG_VERBOSE, "GetEnv:", "Callback to start JNL [%d] ", val); JNIEnv *g_env; if (NULL == jvm) { __android_log_print(ANDROID_LOG_ERROR, "GetEnv:", "No VM "); return; } // double check it's all ok JavaVMAttachArgs args; args.version = JNI_VERSION_1_6; // set your version JNI args.name = NULL; // you might want to give the java thread a name args.group = NULL; // you might want to assign the java thread to a ThreadGroup int getEnvStat = jvm->GetEnv((void **) &g_env, JNI_VERSION_1_6); if (getEnvStat == JNI_EDETACHED) { __android_log_print(ANDROID_LOG_ERROR, "GetEnv:", "not attached "); if (jvm->AttachCurrentThread(&g_env, &args) != 0) { __android_log_print(ANDROID_LOG_ERROR, "GetEnv:", "Failed to attach "); } } else if (getEnvStat == JNI_OK) { __android_log_print(ANDROID_LOG_VERBOSE, "GetEnv:", "JNI_OK "); } else if (getEnvStat == JNI_EVERSION) { __android_log_print(ANDROID_LOG_ERROR, "GetEnv:", "version not supported "); } jstring message = g_env->NewStringUTF(val);// txtCallback(g_env, message); if (g_env->ExceptionCheck()) { g_env->ExceptionDescribe(); } if (getEnvStat == JNI_EDETACHED) { jvm->DetachCurrentThread(); } }
У MainActivity створюємо два TextView і одне EditView.
<TextView android:id="@+id/sample_text_from_Presenter" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:padding="25dp" android:text="Hello World!from_Presenter" app:layout_constraintBottom_toTopOf="parent" app:layout_constraintEnd_toStartOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/sample_text_from_act" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="Hello World from_ac!" app:layout_constraintBottom_toTopOf="parent" app:layout_constraintStart_toStartOf="@+id/sample_text_from_Presenter" app:layout_constraintTop_toTopOf="parent" /> <EditText android:id="@+id/edit_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toTopOf="parent" app:layout_constraintStart_toStartOf="@+id/sample_text_from_act" app:layout_constraintTop_toTopOf="parent" />
У OnCreate пов'язуємо View з змінними і описуємо, що при зміні тексту в EditText, буде розсилатися повідомлення передплатникам.
mEditText = findViewById(R. id.edit_text); mEditText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { } @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { nonNext(charSequence.toString()); } @Override public void afterTextChanged(Editable editable) { } }); tvPresenter = (TextView) findViewById(R. id.sample_text_from_Presenter); tvAct = (TextView) findViewById(R. id.sample_text_from_act);
Заводимо і реєструємо передплатників.
mPresenter = new MainActivityPresenterImpl(this); nsubscribeListener((MainActivityPresenterImpl) mPresenter); nlistener = new JNIListener() { @Override public void onAcceptMessage(String string) { printTextfrActObj(string); } @Override public void onAcceptMessageVal(int messVal) { } }; nsubscribeListener(nlistener);
Виглядає це приблизно так:
Код прикладу лежить тут: github.com/NickZt/MyJNACallbackTest
Загалом поки все, sapienti sat.
Опубліковано: 27/07/18 @ 10:00
Розділ Різне
Рекомендуємо:
В ІТ без диплома: історії JavaScript, PHP і Scala розробників
Вдосконалюємо навички через міграцію проектів: способи і приклади
Що таке корпоративна культура і як вона впливає на вас
PHP дайджест #15: що буде в PHP 8, історія перепису перших версій PHP
Безкоштовні онлайн-курси з програмування, алгоритмами і Data Science