Реалізація JNI callbacks в Android NDK

...
— Дістало мене це «всередині немає деталей, що обслуговуються користувачем». Хочеться подивитися, що ж там є.
...
— Російська матрьошка до самої глибини. Правда, Ороско? Хуан не став дивитися, що таке російська матрьошка.
— Та це ж сміття, професор Гу. Кому воно треба — з таким возитися?

«Кінець веселок» Вернор Віндж

Регулярно виникає потреба в реалізації патерну «Спостерігач» в проектах. Можна просто підключити ReactiveX або EventBus і не морочитися, але все-таки іноді хочеться скоротити кількість залежностей проекту. Та й кращий спосіб навчитися чого-небудь — зробити це своїми руками.

Трохи теорії та історії

Патерн «Спостерігач-Передплатник» — це механізм, який дозволяє об'єкту отримувати оповіщення про змінення стану інших об'єктів і тим самим спостерігати за ними. Робиться для зменшення зв'язності і залежностей між програмними компонентами, що дозволяє ефективніше їх використовувати і тестувати. Яскравий представник, в якому мовна концепція побудована на цьому всьому — Smalltalk, весь заснований на ідеї посилки повідомлень. Вплинув на Objective-C.

Припустимо, ми любимо і вміємо писати свої велосипеди. І що ми отримаємо в результаті:

Реалізація

Спробуємо в кращих традиціях 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