تنفيذ نمط "المراقب-المشترك" باستخدام استدعاءات JNI في Android (NDK)

تطبيق Android (NDK) لردود اتصال JNI ، نمط المراقب - المشترك مع NDK ورد الاتصال ، EventBus أو Rx المكتوبة ذاتيًا


... حصلت عليه "لا توجد أجزاء يمكن للمستخدم صيانتها في الداخل." أريد أن أرى ما هو موجود.
- دمى التعشيش الروسية حتى الأعماق. حقا ، أوروزكو؟ لم ينظر خوان إلى ما هي دمية التعشيش الروسية.
"إنها قمامة يا أستاذ قو." من يحتاجها - للتلاعب بهذا؟
"نهاية قوس قزح" فينج فيرنور

هناك عدد غير قليل من تطبيقات Android التي تجمع بين C ++ و Java code. تقوم Java بتنفيذ منطق الأعمال ، ويقوم C ++ بجميع أعمال الحوسبة ، غالبًا ما توجد في المعالجة الصوتية. تتم معالجة دفق الصوت في مكان ما بالداخل ، ويتم عرض الفرامل مع الغاز والقابض في الطابق العلوي ، والبيانات لجميع أنواع الصور المضحكة.
حسنًا ، منذ ReactiveX ، إنه أمر مألوف بالفعل ، حتى لا تغير يدك ، والعمل مع زنزانة JNI بطرق مألوفة ، تحتاج بانتظام إلى تنفيذ نمط Observer في المشاريع مع NDK. حسنًا ، في الوقت نفسه ، فهم الرمز علماء الآثار أولئك الذين هم غير محظوظين "لفهم رمز شخص آخر" يزداد.
لذا ، فإن أفضل طريقة لتعلم شيء ما هي أن تفعل ذلك بنفسك.
لنفترض أننا نحب ونعرف كيف نكتب دراجاتنا. وماذا سنحصل نتيجة لذلك:


  • شيء من هذا القبيل إعادة النشر من كود C ++ إلى الموقعين ؛
  • إدارة المعالجة في التعليمات البرمجية الأصلية ، أي أننا قد لا نضطر إلى المستوطنات عندما لا يكون هناك مشتركون ولا يوجد أحد لإرسالهم ؛
  • قد يكون من الضروري نقل البيانات بين JVMs المختلفة ؛
  • ولكي لا تستيقظ مرتين ، في نفس الوقت ترسل رسائل داخل تدفقات المشروع.

كود العمل الكامل متاح على جيثب . تقدم المقالة مقتطفات منه فقط.


القليل من النظرية والتاريخ


لقد كنت مؤخرًا في اجتماع RX واستغربت عدد الأسئلة حول: مدى سرعة ReactiveX وكيف يعمل على الإطلاق.
بالنسبة لـ ReactiveX ، لا يسعني إلا أن أقول أن سرعته بالنسبة لـ Java تعتمد إلى حد كبير على مدى معقولية استخدامها ؛ إذا تم استخدامها بشكل صحيح ، فإن سرعتها كافية.
دراجتنا خفيفة الوزن كثيرًا ، ولكن إذا كنت بحاجة ، على سبيل المثال ، إلى قائمة انتظار الرسائل (مثل التدفق) ، فأنت بحاجة إلى كتابتها بنفسك. لأنك تعلم أن كل مواطن الخلل هي لك وحدك.
القليل من النظرية: نمط المراقب - المشترك هو آلية تسمح لكائن بتلقي تنبيهات حول التغييرات في حالة الأشياء الأخرى وبالتالي مراقبتها. يتم ذلك لتقليل الاتصال والتبعيات بين مكونات البرامج ، مما يسمح باستخدامها واختبارها بشكل أكثر كفاءة. يمثل كل شيء ممثلًا حيًا يتم فيه بناء مفهوم اللغة على هذا - Smalltalk ، وكلها تستند إلى فكرة إرسال الرسائل. الهدف المتأثر - ج.


التنفيذ


دعنا نجرب في أفضل تقاليد DIY ، إذا جاز التعبير ، "وميض LED". إذا كنت تستخدم 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 ++ بالحصول على رابط إلى كود جافا لتمكين رد الاتصال بكائن يقوم بتنفيذ واجهة 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_); } } } 

لإعادة توجيه الرسائل من C ++ أو C code ، نستخدم وظيفة 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:", " 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(); } } 

في MainActivity ، نقوم بإنشاء عرضين للنص وواحد 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 ، نربط العرض بمتغيرات ونصف أنه عندما يتم تغيير النص في 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 https://github.com/NickZt/MyJNACallbackTest
اسمحوا لنا أن نعرف في التعليقات ما يمكن وصفه بمزيد من التفصيل.

Source: https://habr.com/ru/post/ar420389/


All Articles