Implementation in Android (NDK) JNI callbacks, " Observer -Subscriber" pattern with NDK and callback, self-written EventBus or Rx
... It got me "inside there are no user serviceable parts." I want to see what is there.
- Russian matryoshka to the very depths. True, Orozco? Juan did not begin to look what a Russian nesting doll is.
- Yes, it's garbage, Professor Gu. Who needs it - to mess with it?
"The End of the Rainbows" Vinge Vernor
There are quite a few Android applications that combine C ++ and Java code. Java implements business logic, and C ++ does all the work of computation, which is often the case in audio processing. The audio stream is processed somewhere inside, and the brake with gas and clutch and the data for all the funny pictures are brought up.
Well, since ReactiveX is already familiar, then, so to speak, not to change a hand, and to work with the JNI dungeon in familiar ways, there is a regular need to implement the “Observer” pattern in projects with NDK. Well, at the same time clear code for archaeologists those who are not lucky to "understand someone else's code" increases.
So the best way to learn something is to do it yourself.
Suppose we love and know how to write their bikes. And what we get as a result:
Full working code is available on GitHub . The article contains only excerpts from it.
A bit of theory and history
I was recently at the RX mit and was amazed at the number of questions about: how fast ReactiveX is, and how it works at all.
For ReactiveX, I can only say that for Java its speed is very dependent on how reasonably used it is, with proper use of its speed is quite enough.
Our bike is much more lightweight, but if you need, for example, a message queue (as flowable), then you have to write it yourself. For that, you know that all the glitches are just yours.
A bit of theory: the " Observer- Subscriber" pattern is a mechanism that allows an object to receive alerts about changes in the state of other objects and thus monitor them. It is done to reduce connectivity and dependencies between software components, which allows them to be used and tested more efficiently. A prominent representative in which the language concept is built on this all - Smalltalk, all based on the idea of sending messages. Affected Objective-C.
Implementation
Let's try in the best traditions of DIY, so to speak, “to flash with a LED”. If you are using JNI, in the Android NDK world, you can query the Java method asynchronously, in any stream. This is what we use to build our “Observer”.
The demo project is built on the template of a new project from Android Studio.
This is an auto-generated method. He commented:
// Used to load the 'native-lib' library on application startup. static { System.loadLibrary("native-lib"); }
And now yourself. The first step is the nsubscribeListener
method.
private native void nsubscribeListener(JNIListener JNIListener);
It allows C ++ code to get a link to the java-code to enable callback to an object that implements the JNIListener.
interface JNIListener.
public interface JNIListener { void onAcceptMessage(String string); void onAcceptMessageVal(int messVal); }
In the implementation of its methods and values will be transferred.
For effective caching of links to the virtual machine, we also save the link to the object. We get for it a global link.
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);
Immediately calculate and save references to methods. This means fewer operations are required to make a callback.
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");
Subscriber data is stored as ObserverChain
records.
class ObserverChain { public: ObserverChain(jweak pJobject, jmethodID pID, jmethodID pJmethodID); jweak store_Wlistener=NULL; jmethodID store_method = NULL; jmethodID store_methodVAL = NULL; };
Store the subscriber in the store_Wlistener_vector dynamic array.
ObserverChain *tmpt = new ObserverChain(store_Wlistener, store_method, store_methodVAL); store_Wlistener_vector.push_back(tmpt);
Now how messages from a Java-code will be transferred.
A message sent to the nonNext method will be sent to all subscribers.
private native void nonNext(String message);
Implementation:
Java_ua_zt_mezon_myjnacallbacktest_MainActivity_nonNext(JNIEnv *env, jobject instance, jstring message_) { txtCallback(env, message_); }
Function txtCallback (env, message_); sends messages to all subscribers.
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_); } } }
For forwarding messages from C ++ or C code, use the test_string_callback_fom_c function.
void test_string_callback_fom_c(char *val)
It checks right from the start whether there are any subscribers at all.
if (store_Wlistener_vector.empty()) return;
It is easy to see that the same txtCallback function is used for sending messages:
void test_string_callback_fom_c(char *val) { if (store_Wlistener_vector.empty()) return; __android_log_print(ANDROID_LOG_VERBOSE, "GetEnv:", " start Callback to JNL [%d] \n", val); JNIEnv *g_env; if (NULL == jvm) { __android_log_print(ANDROID_LOG_ERROR, "GetEnv:", " No VM \n"); return; } // double check it's all ok JavaVMAttachArgs args; args.version = JNI_VERSION_1_6; // set your JNI version 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\n"); if (jvm->AttachCurrentThread(&g_env, &args) != 0) { __android_log_print(ANDROID_LOG_ERROR, "GetEnv:", " Failed to attach\n"); } } else if (getEnvStat == JNI_OK) { __android_log_print(ANDROID_LOG_VERBOSE, "GetEnv:", " JNI_OK\n"); } else if (getEnvStat == JNI_EVERSION) { __android_log_print(ANDROID_LOG_ERROR, "GetEnv:", " version not supported\n"); } jstring message = g_env->NewStringUTF(val);// txtCallback(g_env, message); if (g_env->ExceptionCheck()) { g_env->ExceptionDescribe(); } if (getEnvStat == JNI_EDETACHED) { jvm->DetachCurrentThread(); } }
In the MainActivity create two textview and one 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" />
In OnCreate, we associate the View with variables and describe that when the text changes in EditText, a message will be sent to subscribers.
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);
We get and register subscribers:
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);
It looks like this:
Full working code available on GitHub https://github.com/NickZt/MyJNACallbackTest
Tell in the comments what to describe in more detail.
Source: https://habr.com/ru/post/420389/
All Articles