خيوط Android: كل ما تريد معرفته

نشرت: 2022-03-11

يحتاج كل مطور Android ، في وقت أو آخر ، إلى التعامل مع سلاسل الرسائل في تطبيقاتهم.

عندما يتم تشغيل تطبيق في Android ، فإنه يُنشئ سلسلة التنفيذ الأولى ، والمعروفة باسم سلسلة المحادثات "الرئيسية". الخيط الرئيسي مسؤول عن إرسال الأحداث إلى أدوات واجهة المستخدم المناسبة بالإضافة إلى التواصل مع المكونات من مجموعة أدوات واجهة مستخدم Android.

للحفاظ على استجابة تطبيقك ، من الضروري تجنب استخدام الخيط الرئيسي لإجراء أي عملية قد تنتهي بإبقائه محظورًا.

تعد عمليات الشبكة واستدعاءات قواعد البيانات ، بالإضافة إلى تحميل مكونات معينة ، أمثلة شائعة للعمليات التي يجب تجنبها في السلسلة الرئيسية. عندما يتم استدعاؤها في سلسلة المحادثات الرئيسية ، يتم استدعاؤها بشكل متزامن ، مما يعني أن واجهة المستخدم ستظل غير مستجيبة تمامًا حتى تكتمل العملية. لهذا السبب ، يتم إجراؤها عادةً في سلاسل منفصلة ، مما يؤدي بالتالي إلى تجنب حظر واجهة المستخدم أثناء تنفيذها (على سبيل المثال ، يتم إجراؤها بشكل غير متزامن من واجهة المستخدم).

يوفر Android العديد من الطرق لإنشاء سلاسل الرسائل وإدارتها ، كما توجد العديد من مكتبات الجهات الخارجية التي تجعل إدارة سلاسل الرسائل أكثر متعة. ومع ذلك ، مع وجود العديد من الأساليب المختلفة في متناول اليد ، قد يكون اختيار الطريقة الصحيحة أمرًا مربكًا للغاية.

في هذه المقالة ، ستتعرف على بعض السيناريوهات الشائعة في تطوير Android حيث يصبح الترابط ضروريًا وبعض الحلول البسيطة التي يمكن تطبيقها على تلك السيناريوهات والمزيد.

خيوط في Android

في Android ، يمكنك تصنيف جميع مكونات الترابط إلى فئتين أساسيتين:

  1. الخيوط المرفقة بنشاط / جزء: هذه الخيوط مرتبطة بدورة حياة النشاط / الجزء ويتم إنهاؤها بمجرد تدمير النشاط / الجزء.
  2. الخيوط التي لم يتم إرفاقها بأي نشاط / جزء: يمكن أن تستمر سلاسل الرسائل هذه في العمل إلى ما بعد عمر النشاط / الجزء (إن وجد) الذي تم إنتاجها منه.

مكونات الترابط التي تعلق على نشاط / جزء

AsyncTask

AsyncTask هو المكون الأساسي لنظام التشغيل Android للترابط. إنه سهل الاستخدام ويمكن أن يكون مفيدًا للسيناريوهات الأساسية.

استخدام العينة:

 public class ExampleActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new MyTask().execute(url); } private class MyTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... params) { String url = params[0]; return doSomeWork(url); } @Override protected void onPostExecute(String result) { super.onPostExecute(result); // do something with result } } }

ومع ذلك ، فإن AsyncTask يقصر إذا كنت بحاجة إلى مهمتك المؤجلة لتتجاوز عمر النشاط / الجزء. تجدر الإشارة إلى أنه حتى شيء بسيط مثل تدوير الشاشة يمكن أن يتسبب في تدمير النشاط.

لوادر

اللوادر هي الحل للمشكلة المذكورة أعلاه. يمكن أن تتوقف اللوادر تلقائيًا عند تدمير النشاط ، ويمكنها أيضًا إعادة تشغيل نفسها بعد إعادة إنشاء النشاط.

هناك نوعان رئيسيان من اللوادر: AsyncTaskLoader و CursorLoader . سوف تتعلم المزيد عن CursorLoader لاحقًا في هذه المقالة.

AsyncTaskLoader مشابه لـ AsyncTask ، لكنه أكثر تعقيدًا بعض الشيء.

استخدام العينة:

 public class ExampleActivity extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getLoaderManager().initLoader(1, null, new MyLoaderCallbacks()); } private class MyLoaderCallbacks implements LoaderManager.LoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { return new MyLoader(ExampleActivity.this); } @Override public void onLoadFinished(Loader loader, Object data) { } @Override public void onLoaderReset(Loader loader) { } } private class MyLoader extends AsyncTaskLoader { public MyLoader(Context context) { super(context); } @Override public Object loadInBackground() { return someWorkToDo(); } } }

مكونات الترابط التي لا ترتبط بنشاط / جزء

خدمة

Service عبارة عن مكون مفيد لإجراء عمليات طويلة (أو طويلة محتملة) بدون أي واجهة مستخدم.

تعمل Service في الخيط الرئيسي لعملية الاستضافة الخاصة بها ؛ لا تقوم الخدمة بإنشاء مؤشر ترابط خاص بها ولا يتم تشغيلها في عملية منفصلة ما لم تحدد خلاف ذلك.

استخدام العينة:

 public class ExampleService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { doSomeLongProccesingWork(); stopSelf(); return START_NOT_STICKY; } @Nullable @Override public IBinder onBind(Intent intent) { return null; } }

مع Service ، تقع على عاتقك مسؤولية إيقافها عند اكتمال عملها عن طريق استدعاء طريقة stopSelf() أو stopService() .

IntentService

مثل Service ، تعمل IntentService على مؤشر ترابط منفصل ، وتوقف نفسها تلقائيًا بعد أن تكمل عملها.

تُستخدم IntentService عادةً للمهام القصيرة التي لا تحتاج إلى إرفاقها بأي واجهة مستخدم.

استخدام العينة:

 public class ExampleService extends IntentService { public ExampleService() { super("ExampleService"); } @Override protected void onHandleIntent(Intent intent) { doSomeShortWork(); } }

سبعة أنماط خيوط في Android

حالة الاستخدام رقم 1: إجراء طلب عبر الشبكة دون الحاجة إلى استجابة من الخادم

قد ترغب أحيانًا في إرسال طلب API إلى خادم دون الحاجة إلى القلق بشأن استجابته. على سبيل المثال ، قد ترسل رمز تسجيل دفع مميز إلى النهاية الخلفية لتطبيقك.

نظرًا لأن هذا ينطوي على تقديم طلب عبر الشبكة ، فيجب عليك القيام بذلك من سلسلة رسائل غير السلسلة الرئيسية.

الخيار 1: AsyncTask أو اللوادر

يمكنك استخدام AsyncTask أو اللوادر لإجراء المكالمة ، وستعمل.

ومع ذلك ، يعتمد كل من AsyncTask على دورة حياة النشاط. هذا يعني أنك ستحتاج إما إلى انتظار تنفيذ المكالمة ومحاولة منع المستخدم من مغادرة النشاط ، أو الأمل في أن يتم تنفيذه قبل تدمير النشاط.

الخيار 2: الخدمة

قد تكون Service مناسبة بشكل أفضل لحالة الاستخدام هذه لأنها غير مرتبطة بأي نشاط. لذلك سيكون قادرًا على متابعة مكالمة الشبكة حتى بعد تدمير النشاط. بالإضافة إلى ذلك ، نظرًا لعدم الحاجة إلى الاستجابة من الخادم ، فلن تكون الخدمة محدودة هنا أيضًا.

ومع ذلك ، نظرًا لأن الخدمة ستبدأ في التشغيل على مؤشر ترابط واجهة المستخدم ، فستظل بحاجة إلى إدارة الترابط بنفسك. ستحتاج أيضًا إلى التأكد من إيقاف الخدمة بمجرد اكتمال مكالمة الشبكة.

سيتطلب هذا جهدًا أكبر مما ينبغي لمثل هذا الإجراء البسيط.

الخيار 3: IntentService

هذا ، في رأيي ، سيكون الخيار الأفضل.

نظرًا لأن IntentService لا يرتبط بأي نشاط ويتم تشغيله على مؤشر ترابط غير تابع لواجهة المستخدم ، فإنه يخدم احتياجاتنا بشكل مثالي هنا. علاوة على ذلك ، تتوقف IntentService عن نفسها تلقائيًا ، لذلك ليست هناك حاجة لإدارتها يدويًا أيضًا.

حالة الاستخدام رقم 2: إجراء مكالمة عبر الشبكة والحصول على الاستجابة من الخادم

من المحتمل أن تكون حالة الاستخدام هذه أكثر شيوعًا. على سبيل المثال ، قد ترغب في استدعاء API في النهاية الخلفية واستخدام استجابتها لتعبئة الحقول على الشاشة.

الخيار 1: الخدمة أو IntentService

على الرغم من نجاح إحدى Service أو IntentService في حالة الاستخدام السابقة ، إلا أن استخدامها هنا لن يكون فكرة جيدة. إن محاولة إخراج البيانات من إحدى Service أو IntentService في مؤشر ترابط واجهة المستخدم الرئيسي سيجعل الأمور معقدة للغاية.

الخيار 2: AsyncTask أو اللوادر

للوهلة الأولى ، يبدو أن AsyncTask أو اللوادر هي الحل الواضح هنا. إنها سهلة الاستخدام - بسيطة ومباشرة.

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

هذا كثير جدًا من العمل الذي يتعين القيام به لكل مكالمة واحدة. لحسن الحظ ، يتوفر الآن حل أفضل وأبسط: RxJava.

الخيار 3: RxJava

ربما سمعت عن RxJava ، المكتبة التي طورتها Netflix. يكاد يكون السحر في جافا.

يتيح لك RxAndroid استخدام RxJava في Android ، ويجعل التعامل مع المهام غير المتزامنة أمرًا سهلاً. يمكنك معرفة المزيد عن RxJava على نظام Android هنا.

يوفر Subscriber مكونين: Observer ومشترك.

المراقب هو مكون يحتوي على بعض الإجراءات. يقوم بتنفيذ هذا الإجراء وإرجاع النتيجة إذا نجحت أو حدث خطأ إذا فشل.

من ناحية أخرى ، يعتبر المشترك مكونًا يمكنه تلقي النتيجة (أو الخطأ) من عنصر يمكن ملاحظته ، من خلال الاشتراك فيه.

باستخدام RxJava ، تقوم أولاً بإنشاء ما يلي:

 Observable.create((ObservableOnSubscribe<Data>) e -> { Data data = mRestApi.getData(); e.onNext(data); })

بمجرد إنشاء ما يمكن ملاحظته ، يمكنك الاشتراك فيه.

باستخدام مكتبة RxAndroid ، يمكنك التحكم في الخيط الذي تريد تنفيذ الإجراء فيه ، والموضوع الذي تريد الحصول على الاستجابة فيه (أي النتيجة أو الخطأ).

تتسلسل في ما يمكن ملاحظته باستخدام هاتين الوظيفتين:

 .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()

المبرمجون عبارة عن مكونات تقوم بتنفيذ الإجراء في سلسلة محادثات معينة. AndroidSchedulers.mainThread() هو المجدول المرتبط بسلسلة المحادثات الرئيسية.

نظرًا لأن استدعاء API الخاص بنا هو mRestApi.getData() وإرجاع كائن Data ، يمكن أن تبدو المكالمة الأساسية كما يلي:

 Observable.create((ObservableOnSubscribe<Data>) e -> { try { Data data = mRestApi.getData(); e.onNext(data); } catch (Exception ex) { e.onError(ex); } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(match -> Log.i(“rest api, "success"), throwable -> Log.e(“rest api, "error: %s" + throwable.getMessage()));

بدون الخوض في الفوائد الأخرى لاستخدام RxJava ، يمكنك بالفعل أن ترى كيف يسمح لنا RxJava بكتابة كود أكثر نضجًا عن طريق التخلص من تعقيد خيوط المعالجة.

حالة الاستخدام رقم 3: تسلسل مكالمات الشبكة

بالنسبة لمكالمات الشبكة التي يجب إجراؤها بالتسلسل (على سبيل المثال ، حيث تعتمد كل عملية على استجابة / نتيجة العملية السابقة) ، يجب أن تكون حذرًا بشكل خاص بشأن إنشاء رمز السباغيتي.

على سبيل المثال ، قد تضطر إلى إجراء مكالمة API برمز مميز تحتاج إلى جلبه أولاً من خلال استدعاء API آخر.

الخيار 1: AsyncTask أو اللوادر

سيؤدي استخدام AsyncTask أو اللوادر بشكل شبه مؤكد إلى رمز السباغيتي. سيكون من الصعب الحصول على الوظيفة العامة بشكل صحيح وستتطلب قدرًا هائلاً من الشفرات المعيارية الزائدة عن الحاجة في جميع أنحاء مشروعك.

الخيار 2: RxJava باستخدام flatMap

في RxJava ، يأخذ عامل التشغيل flatMap قيمة منبعثة من المصدر يمكن ملاحظتها ويعيد قيمة أخرى يمكن ملاحظتها. يمكنك إنشاء عنصر يمكن ملاحظته ، ثم إنشاء آخر يمكن ملاحظته باستخدام القيمة المنبعثة من القيمة الأولى ، والتي ستعمل على ربطها بشكل أساسي.

الخطوة الأولى: قم بإنشاء ما يمكن ملاحظته يجلب الرمز المميز:

 public Observable<String> getTokenObservable() { return Observable.create(subscriber -> { try { String token = mRestApi.getToken(); subscriber.onNext(token); } catch (IOException e) { subscriber.onError(e); } }); }

الخطوة الثانية : قم بإنشاء ما يمكن ملاحظته والذي يحصل على البيانات باستخدام الرمز المميز:

 public Observable<String> getDataObservable(String token) { return Observable.create(subscriber -> { try { Data data = mRestApi.getData(token); subscriber.onNext(data); } catch (IOException e) { subscriber.onError(e); } }); }

الخطوة الثالثة. سلسلة الملاحظتين معًا واشترك:

 getTokenObservable() .flatMap(new Function<String, Observable<Data>>() { @Override public Observable<Data> apply(String token) throws Exception { return getDataObservable(token); } }) .subscribe(data -> { doSomethingWithData(data) }, error -> handleError(e));

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

جميع حالات الاستخدام المذكورة أعلاه بسيطة للغاية. لا يحدث التبديل بين سلاسل الرسائل إلا بعد انتهاء كل منها من مهمتها. يمكن دعم السيناريوهات الأكثر تقدمًا - على سبيل المثال ، حيث يلزم وجود خيطين أو أكثر للتواصل بنشاط مع بعضهما البعض - من خلال هذا الأسلوب أيضًا.

حالة الاستخدام رقم 4: تواصل مع مؤشر ترابط واجهة المستخدم من سلسلة رسائل أخرى

ضع في اعتبارك سيناريو حيث ترغب في تحميل ملف وتحديث واجهة المستخدم بمجرد اكتمالها.

نظرًا لأن تحميل ملف قد يستغرق وقتًا طويلاً ، فلا داعي لإبقاء المستخدم في الانتظار. يمكنك استخدام خدمة ، وربما IntentService ، لتنفيذ الوظيفة هنا.

ومع ذلك ، في هذه الحالة ، يتمثل التحدي الأكبر في القدرة على استدعاء طريقة على مؤشر ترابط واجهة المستخدم بعد اكتمال تحميل الملف (الذي تم إجراؤه في سلسلة منفصلة).

الخيار 1: RxJava داخل الخدمة

قد لا تكون RxJava ، سواء بمفردها أو داخل IntentService ، مثالية. ستحتاج إلى استخدام آلية قائمة على رد الاتصال عند الاشتراك في Observable ، وقد تم IntentService لإجراء مكالمات متزامنة بسيطة ، وليس عمليات رد نداء.

من ناحية أخرى ، مع Service ، ستحتاج إلى إيقاف الخدمة يدويًا ، الأمر الذي يتطلب المزيد من العمل.

الخيار 2: جهاز استقبال البث

يوفر Android هذا المكون ، الذي يمكنه الاستماع إلى الأحداث العالمية (على سبيل المثال ، أحداث البطارية ، وأحداث الشبكة ، وما إلى ذلك) بالإضافة إلى الأحداث المخصصة. يمكنك استخدام هذا المكون لإنشاء حدث مخصص يتم تشغيله عند اكتمال التحميل.

للقيام بذلك ، تحتاج إلى إنشاء فئة مخصصة تقوم بتوسيع BroadcastReceiver ، وتسجيلها في البيان ، واستخدام Intent و IntentFilter لإنشاء حدث مخصص. لبدء الحدث ، ستحتاج إلى التابع sendBroadcast .

قائمة:

 <receiver android:name="UploadReceiver"> <intent-filter> <action android:name="com.example.upload"> </action> </intent-filter> </receiver>

المتلقي:

 public class UploadReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getBoolean(“success”, false) { Activity activity = (Activity)context; activity.updateUI(); } }

مرسل:

 Intent intent = new Intent(); intent.setAction("com.example.upload"); sendBroadcast(intent);

هذا النهج هو خيار قابل للتطبيق. ولكن كما لاحظت ، فإنه ينطوي على بعض العمل ، ويمكن أن يؤدي الكثير من عمليات البث إلى إبطاء الأمور.

الخيار 3: استخدام المعالج

Handler هو مكون يمكن إرفاقه بمؤشر ترابط ثم إجراء بعض الإجراءات على هذا الموضوع عبر رسائل بسيطة أو مهام Runnable . إنه يعمل جنبًا إلى جنب مع مكون آخر ، Looper ، وهو المسؤول عن معالجة الرسائل في سلسلة محادثات معينة.

عندما يتم إنشاء Handler ، يمكنه الحصول على كائن Looper في المُنشئ ، مما يشير إلى الخيط الذي يتم إرفاق المعالج به. إذا كنت تريد استخدام معالج متصل بالخيط الرئيسي ، فأنت بحاجة إلى استخدام الحلقات المرتبط بالخيط الرئيسي عن طريق استدعاء Looper.getMainLooper() .

في هذه الحالة ، لتحديث واجهة المستخدم من مؤشر ترابط في الخلفية ، يمكنك إنشاء معالج مرفق بمؤشر ترابط واجهة المستخدم ، ثم نشر إجراء كإجراء Runnable :

 Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { // update the ui from here } });

هذا الأسلوب أفضل كثيرًا من الطريقة الأولى ، ولكن هناك طريقة أبسط للقيام بذلك ...

الخيار 3: استخدام EventBus

EventBus ، مكتبة شهيرة من GreenRobot ، تمكن المكونات من التواصل بأمان مع بعضها البعض. نظرًا لأن حالة الاستخدام الخاصة بنا هي إحدى الحالات التي نريد فيها تحديث واجهة المستخدم فقط ، فقد يكون هذا هو الخيار الأبسط والأكثر أمانًا.

الخطوة 1. إنشاء فئة الحدث. على سبيل المثال ، UIEvent .

الخطوة 2. اشترك في الحدث.

 @Subscribe(threadMode = ThreadMode.MAIN) public void onUIEvent(UIEvent event) {/* Do something */}; register and unregister eventbus : @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); }

الخطوة الثالثة. انشر الحدث: EventBus.getDefault().post(new UIEvent());

باستخدام معلمة ThreadMode في التعليق التوضيحي ، فأنت تحدد الموضوع الذي ترغب في الاشتراك فيه لهذا الحدث. في مثالنا هنا ، نختار الخيط الرئيسي ، لأننا نريد أن يتمكن متلقي الحدث من تحديث واجهة المستخدم.

يمكنك هيكلة فئة UIEvent بحيث تحتوي على معلومات إضافية حسب الضرورة.

في الخدمة:

 class UploadFileService extends IntentService { // … Boolean success = uploadFile(File file); EventBus.getDefault().post(new UIEvent(success)); // ... }

في النشاط / الجزء:

 @Subscribe(threadMode = ThreadMode.MAIN) public void onUIEvent(UIEvent event) {//show message according to the action success};

باستخدام EventBus library ، يصبح التواصل بين الخيوط أسهل بكثير.

حالة الاستخدام رقم 5: اتصال ثنائي الاتجاه بين الخيوط بناءً على إجراءات المستخدم

لنفترض أنك تقوم ببناء مشغل وسائط وتريد أن يتمكن من متابعة تشغيل الموسيقى حتى عندما تكون شاشة التطبيق مغلقة. في هذا السيناريو ، ستحتاج إلى أن تكون واجهة المستخدم قادرة على التواصل مع مؤشر ترابط الوسائط (على سبيل المثال ، التشغيل والإيقاف المؤقت والإجراءات الأخرى) وستريد أيضًا أن يقوم مؤشر ترابط الوسائط بتحديث واجهة المستخدم استنادًا إلى أحداث معينة (مثل الخطأ وحالة التخزين المؤقت ، إلخ).

المثال الكامل لمشغل الوسائط خارج نطاق هذه المقالة. ومع ذلك ، يمكنك العثور على برامج تعليمية جيدة هنا وهنا.

الخيار 1: استخدام EventBus

يمكنك استخدام EventBus هنا. ومع ذلك ، من غير الآمن عمومًا نشر حدث من مؤشر ترابط واجهة المستخدم واستلامه في إحدى الخدمات. هذا لأنه ليس لديك طريقة لمعرفة ما إذا كانت الخدمة قيد التشغيل أم لا عندما قمت بإرسال الرسالة.

الخيار 2: استخدام BoundService

BoundService هي Service مرتبطة بنشاط / جزء. هذا يعني أن النشاط / الجزء يعرف دائمًا ما إذا كانت الخدمة تعمل أم لا ، بالإضافة إلى أنه يمكنه الوصول إلى الأساليب العامة للخدمة.

لتنفيذه ، تحتاج إلى إنشاء Binder مخصص داخل الخدمة وإنشاء طريقة تُرجع الخدمة.

 public class MediaService extends Service { private final IBinder mBinder = new MediaBinder(); public class MediaBinder extends Binder { MediaService getService() { // Return this instance of LocalService so clients can call public methods return MediaService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder; } }

لربط النشاط بالخدمة ، تحتاج إلى تنفيذ ServiceConnection ، وهي الفئة التي تراقب حالة الخدمة ، واستخدام الطريقة bindService لإجراء الربط:

 // in the activity MediaService mService; // flag indicates the bound status boolean mBound; @Override protected void onStart() { super.onStart(); // Bind to LocalService Intent intent = new Intent(this, MediaService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { MediaBinder binder = (MediaBinder) service; mService = binder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName arg0) { mBound = false; } };

يمكنك العثور على مثال تنفيذ كامل هنا.

للتواصل مع الخدمة عندما ينقر المستخدم على زر التشغيل أو الإيقاف المؤقت ، يمكنك الالتزام بالخدمة ثم الاتصال بالطريقة العامة ذات الصلة على الخدمة.

عندما يكون هناك حدث إعلامي وترغب في نقل ذلك مرة أخرى إلى النشاط / الجزء ، يمكنك استخدام إحدى الأساليب السابقة (على سبيل المثال ، BroadcastReceiver أو Handler أو EventBus ).

حالة الاستخدام رقم 6: تنفيذ الإجراءات بالتوازي والحصول على النتائج

لنفترض أنك تنشئ تطبيقًا سياحيًا وتريد إظهار مناطق الجذب على خريطة تم جلبها من مصادر متعددة (موفرو بيانات مختلفون). نظرًا لأنه قد لا تكون جميع المصادر موثوقة ، فقد ترغب في تجاهل المصادر التي فشلت والاستمرار في عرض الخريطة على أي حال.

لموازاة العملية ، يجب أن تتم كل مكالمة API في سلسلة رسائل مختلفة.

الخيار 1: استخدام RxJava

في RxJava ، يمكنك دمج العديد من العناصر المرئية في واحدة باستخدام عوامل merge() أو concat() . يمكنك بعد ذلك الاشتراك في "المدمجة" التي يمكن ملاحظتها وانتظار جميع النتائج.

ومع ذلك ، لن يعمل هذا النهج كما هو متوقع. في حالة فشل استدعاء واجهة برمجة التطبيقات (API) ، سيقوم الشخص المدمج الذي يمكن ملاحظته بالإبلاغ عن فشل شامل.

الخيار 2: استخدام مكونات Java الأصلية

تقوم ExecutorService في Java بإنشاء عدد ثابت (قابل للتكوين) من مؤشرات الترابط وتنفيذ المهام عليها بشكل متزامن. تقوم الخدمة بإرجاع كائن Future الذي يقوم في النهاية بإرجاع جميع النتائج عبر طريقة invokeAll() .

يجب تضمين كل مهمة ترسلها إلى ExecutorService في واجهة Callable ، وهي واجهة لإنشاء مهمة يمكنها طرح استثناء.

بمجرد الحصول على النتائج من invokeAll() ، يمكنك التحقق من كل نتيجة والمتابعة وفقًا لذلك.

لنفترض ، على سبيل المثال ، أن لديك ثلاثة أنواع من عوامل الجذب تأتي من ثلاث نقاط نهاية مختلفة وتريد إجراء ثلاث مكالمات متوازية:

 ExecutorService pool = Executors.newFixedThreadPool(3); List<Callable<Object>> tasks = new ArrayList<>(); tasks.add(new Callable<Object>() { @Override public Integer call() throws Exception { return mRest.getAttractionType1(); } }); // ... try { List<Future<Object>> results = pool.invokeAll(tasks); for (Future result : results) { try { Object response = result.get(); if (response instance of AttractionType1... {} if (response instance of AttractionType2... {} ... } catch (ExecutionException e) { e.printStackTrace(); } } } catch (InterruptedException e) { e.printStackTrace(); }

بهذه الطريقة ، تقوم بتشغيل جميع الإجراءات بالتوازي. لذلك ، يمكنك التحقق من وجود أخطاء في كل إجراء على حدة وتجاهل حالات الفشل الفردية حسب الاقتضاء.

هذا الأسلوب أسهل من استخدام RxJava. إنه أبسط وأقصر ولا يفشل في جميع الإجراءات بسبب استثناء واحد.

استخدام الحالة رقم 7: الاستعلام عن قاعدة بيانات SQLite المحلية

عند التعامل مع قاعدة بيانات SQLite محلية ، يوصى باستخدام قاعدة البيانات من مؤشر ترابط في الخلفية ، نظرًا لأن استدعاءات قاعدة البيانات (خاصة مع قواعد البيانات الكبيرة أو الاستعلامات المعقدة) يمكن أن تستغرق وقتًا طويلاً ، مما يؤدي إلى تجميد واجهة المستخدم.

عند الاستعلام عن بيانات SQLite ، تحصل على عنصر Cursor الذي يمكن استخدامه لاحقًا لجلب البيانات الفعلية.

 Cursor cursor = getData(); String name = cursor.getString(<colum_number>);

الخيار 1: استخدام RxJava

يمكنك استخدام RxJava والحصول على البيانات من قاعدة البيانات ، تمامًا كما نحصل على البيانات من النهاية الخلفية لدينا:

 public Observable<Cursor> getLocalDataObservable() { return Observable.create(subscriber -> { Cursor cursor = mDbHandler.getData(); subscriber.onNext(cursor); }); }

يمكنك استخدام المرصد الذي تم إرجاعه بواسطة getLocalDataObservable() على النحو التالي:

 getLocalDataObservable() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(cursor -> String name = cursor.getString(0), throwable -> Log.e(“db, "error: %s" + throwable.getMessage()));

في حين أن هذا هو بالتأكيد نهج جيد ، إلا أن هناك منهجًا أفضل ، نظرًا لوجود مكون تم إنشاؤه لهذا السيناريو بالذات.

الخيار 2: استخدام CursorLoader + ContentProvider

يوفر Android CursorLoader ، وهو مكون أصلي لتحميل بيانات SQLite وإدارة الخيط المقابل. إنه أداة Loader تقوم بإرجاع Cursor ، والذي يمكننا استخدامه للحصول على البيانات عن طريق استدعاء طرق بسيطة مثل getString() ، و getLong() ، وما إلى ذلك.

 public class SimpleCursorLoader extends FragmentActivity implements LoaderManager.LoaderCallbacks<Cursor> { public static final String TAG = SimpleCursorLoader.class.getSimpleName(); private static final int LOADER_ID = 0x01; private TextView textView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_cursor_loader); textView = (TextView) findViewById(R.id.text_view); getSupportLoaderManager().initLoader(LOADER_ID, null, this); } public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { return new CursorLoader(this, Uri.parse("content://com.github.browep.cursorloader.data") , new String[]{"col1"}, null, null, null); } public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) { if (cursor != null && cursor.moveToFirst()) { String text = textView.getText().toString(); while (cursor.moveToNext()) { text += "<br />" + cursor.getString(1); cursor.moveToNext(); } textView.setText(Html.fromHtml(text) ); } } public void onLoaderReset(Loader<Cursor> cursorLoader) { } }

يعمل CursorLoader مع مكون ContentProvider . يوفر هذا المكون عددًا كبيرًا من ميزات قاعدة البيانات في الوقت الفعلي (على سبيل المثال ، إخطارات التغيير ، والمشغلات ، وما إلى ذلك) التي تمكن المطورين من تنفيذ تجربة مستخدم أفضل بسهولة أكبر.

لا يوجد حل Silver Bullet لـ Threading في Android

يوفر Android العديد من الطرق للتعامل مع سلاسل الرسائل وإدارتها ، ولكن ليس أي منها رصاصة فضية.

يمكن أن يؤدي اختيار نهج الخيوط الصحيح ، اعتمادًا على حالة الاستخدام الخاصة بك ، إلى إحداث فرق كبير في جعل الحل الشامل سهل التنفيذ والفهم. المكونات الأصلية مناسبة بشكل جيد لبعض الحالات ، ولكن ليس للجميع. الأمر نفسه ينطبق على حلول الطرف الثالث الفاخرة.

آمل أن تجد هذه المقالة مفيدة عند العمل في مشروع Android التالي. شارك معنا تجربتك في استخدام الترابط في Android أو أي حالة استخدام حيث تعمل الحلول المذكورة أعلاه بشكل جيد - أو لا تعمل ، في هذا الصدد - في التعليقات أدناه.