There are many applications for Android which combine C ++ and Java code. Java implements business logic, and C ++ does all the work for calculations, often it occurs in audio processing. The audio stream is processed somewhere inside, and the brake is released to the top with gas and clutch, and data for some funny pictures.
Well, since ReactiveX, it's already customary, then, in order not to change the hand, and to work with the JNI underground in familiar ways. There is a regular need for implementing the pattern "Observer" in projects with NDK. Well, at the same time, the comprehensibility of the code for archaeologists, those, who are unlucky "to understand a strangers code" is increasing.
So, the best way to learn something is to do it yourself.
Let's say we love and know how to write our bicycles. And what do we get as a result:
- Something like a type of return from a C ++ code to a subscriber;
- management of processing in the native code, that is, we can not be bothered about the calculations, when we have not subscribers to send them to;
- You may also need to transfer the data between different JVMs;
- and not to get up twice, along with sending messages inside the project flows.
Full working code is available on GitHub. The article gives only extracts from it.
A bit of theory and historyRecently I have been at the meetup on RX and was amazed at the number of questions about: how fast ReactiveX is, and how it works.
For ReactiveX I will only say that for Java its speed depends very much on how intelligently it is applied, with proper application, its speed is quite enough.
Our bike is much more lightweight, but if you need, for example, a message queue (as flowable) then you write it should yourself. So, 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 messages about changing of the state of other objects and therefore to observe them. It is done to reduce connectivity and dependencies between the software components, which allow to use and test them more efficiently. A vivid representative in which the language concept is built on this all - Smalltalk, which is all based on the idea of sending messages. Affected the Objective-C.
ImplementationLet's try in the best traditions of DIY, so to speak, "flash by LED". If you use JNI, in the Android NDK world, you can request the Java method asynchronously, in any thread. This is what we use to build our "Observer".
The demo project is built on a new project template from Android Studio.
This is an auto-generated method. It is commented:
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
And now your. The first step is the nsubscribeListener method.
private native void nsubscribeListener(JNIListener JNIListener);
It allows C ++ code to get a link to java-code to enable a callback to an object which implements the JNIListener interface.
public interface JNIListener {
void onAcceptMessage(String string);
void onAcceptMessageVal(int messVal);
}
The data will be sent directly to the implement its methods.
To cache the references to a virtual machine efficiently, we also save the reference to the object. We get a global reference for it.
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 we calculate and store the references to methods. This means that fewer operations are required to perform 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");
The subscribers data are stored as the ObserverChain class 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 dynamic array of store_Wlistener_vector.
ObserverChain *tmpt = new ObserverChain(store_Wlistener, store_method, store_methodVAL);store_Wlistener_vector.push_back(tmpt);
And now, how the messages from a Java-code will be transferred.
A message which was 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 the 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 the 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 we 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 link Views with variables and describe that when you change the text in EditText, the 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 set up and register the 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, approximately, like this:
The full working code is available at GitHub https://github.com/NickZt/MyJNACallbackTest
Comments
Please log in or sign up to comment.