برمجة الأندرويد

درس Android Services

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

وبمجرد ذكرى لموضوع ان الـ Service يستخدم لاداء امر او عملية فى الخلفية سيتذكر البعض الـ Thread ويخبرنى لماذا الـ Service وأنا يمكننى اجراء العملية التى اريد اجراءها بالخلفية من خلال الـ Threads والحقيقة ان النقاش فى هذا الامر يطول وقد يحتاج الى تدوينة منفصلة عن الفرق لكن ببساطة نعم يمكنك اجراء اى عملية فى الخلفية من خلال الـ  Thread لكن الـ Thread يكون مربوطا بالمكون الذى بدأه ومحدود فى امكانياته مقارنة بالـ Service ويعتبر الـ Thread ليس من مكونات الاندرويد نفسه وأولويته و أهميته للأندرويد ليس بمثل أهمية الـ Service فالـService التى تعمل فى الخلفية بالنسبة لنظام الاندرويد اهم بكثير من الـ Thread وذلك فى حالة وجود ضغط على الـ Memory يستغنى الاندرويد ببساطه عن الـ Thread ويقتله لكن لا يستغنى عن الـ Service ببساطه ولو اضطر فتملك الـ Service القدرة على بدء نفسها من جديد اذا تم تدميرها او انهاءها فإذا كنا نطلق على القطط انها بسبعة أرواح فان الـ Service لديها ١٠٠٠ روح 😀 .

ببساطة فى عالم الأندرويد داخل السيستم أن تكون سيرفس يشبه أن تكون الفتاة التى تكتب منشورا على الجروب الخاص بالدفعة فيتفاعل معها الجميع وأن تكون Thread يشبه ان تكون الفتى الذى يكتب منشورا على جروب الدفعة ولا احد يجيبه وربما عطف عليه البعض باعطاءه لايك فالسيرفس له أهمية خاصة ومكون من مكونات الاندرويد نفسه ولديه القدرة على استعادة وتشغيل نفسه مرة اخرى اذا تم انهاءه بواسطة السيستم  وليس امر عام مثل الـ Thread وغالبا ما تستخدم الـ Threads بداخل الـ Services اصلا .

 

بشكل عام الـ Service تأخذ شكلين أو تتصرف بسلوكين يمكنك تنفيذها بواحد منهم الأول هو الـ StartedService والثانى هو الـ BoundServices  وسنقوم الان بالاطلاع على كيفية عملهم فهيا بنا نبدأ.

 

الـ Started Service 

هى عباره عن إنشاء Service يتم استدعائها بواسطة الامر startService() من مكون اخر من مكونات الاندرويد مثل الـ Activity فكما نفعل عند بدأ اكتيتيفى نقوم بعمل Intent واستخدام startActivity() واعطائها الـ Intent لتبدأ الاكتيتيفى نفس الامر تماما مع الـ Service ننشىء الـ Intent لكن نستخدم الميثود startService ونمرر لها الـ Intent وتبدأ مباشرة بالعمل وللعلم فإن الـ Service تبدأ فى نفس الـ MainThread واى عمل نقوم به بداخل الـ Service يتم داخل الـ MainThread لذلك اذا اردنا اجراء عملية تستغرق وقتا طويلا داخل الـ Service نقوم باستخدام الـ Threads ايضا ،

وبمجرد أن يتم بدء الـ Service تستمر فى العمل حتى ولو تم انهاء الاكتييتفى الذى بدءها حتى يقوم سيستم الاندرويد بايقافها او هى بايقاف نفسها عند انهاء العملية ويعتبر سلوك او نظام الـ StartedService لا يعود بالنتيجة او بالبيانات للمكون الذى ناداه او بدءه

 

مثال على الـ StartedService نقوم بإنشاء Service جديدة عن طريق الضغط بزر الماوس الايمن على ال packadge واختيار  New -> Service واختيار الـ Service العادية واعطائها اسم وقد تجد الخيار Exported والذى معناه السماح للتطبيقات الاخرى باستخدم هذه الـ Service  وفى هذه الحالة يتم عمل exported = true تلقائيا فى المنيفست ام قصرها على التطبيق فقط ونقوم بالمتابعة ويتم انشاء الـ Service كالتالى

والسيرفس عباره عن كلاس بسيط كما ترى يرث من Service ولديه Constructor وكذلك تم عمل Override للميثود onBind والتى اريدك ان تتجاهلها الان وسنأتى لها لاحقا لكن الان هناك اثنين من الـ Methods سنقوم بعمل Override لهما الان وهما onCreate() مثلما تراها فى الاكتييتفى او فى الفريجمنت والميثود الاخرة جديدة علينا قليلا وهى onStartCommand  لاحظ الاتى

فى داخل onCreate نقوم بكتابة الكود الذى سيتم تنفيذه عندما يتم إنشاء الـ Service وقمنا بكتابة Log ليطبع لنا فى الـ Logcat فقط ان هذه الميثود تمت مناداتها .

فى داخل onStartCommand يتم كتابة الكود الذى سيتم تنفيذه عند بدء الـ Service وهو الكود الذى نريد تنفيذه فى السيرفس بشكل عام ويتم بدئ السيرفس  باستخدام startService()  من خلال مكون اخر مثل الاكتيتفى كما سنفعل بعد قليل وهذه الميثود يجب ان نقوم فيها بعمل return لـ int  يمثل سلوك السيرفس فى التعامل مع ما تقوم بتنفيذه اذا ما واجهت اغلاقا من قبل النظام حيث يحدد نوع الـ return الخاص ب onStartCommand ماذا سيفعل النظام فى حال اضطراره لإغلاق هذه السيرفس وإذا كنت تتسائل لماذا قد يقوم النظام بقتل او انهاء السيرفس فإن ذلك ببساطه لتوفير مساحة فى الـ Memory فتخيل ان المستخدم الان يلعب احد الالعاب فى الاندرويد واللعبة تقوم باستهلاك الكثير من مساحة الرام فى هذه الحالة يعمل النظام فى خدمة المستخدم بتوفير مساحة الرام له وقتل وانهاء الـ Services التى تعمل فى الخلفية وغير متعلقة بالتطبيق الحالى حينها عندما يريد الاندرويد إنهاء السيرفس يفحص نوع الـ Return الخاص بـ onStartCommand فاذا كان Service.START_STICKY  يقوم السيستم باعادة تشغيل الـ Service فى اقرب وقت يستطيع  ويمرر قيمة الـ intent = null فاذا كنت ارسلت بيانات عبر الـ Intent فعند عمل restart للسيرفس تصبح غير متوفرة  واذا كان نوع الـ Return عبارة عن Service.START_NOT_STICKY  ففى هذه الحالة لا يقوم بالأندرويد باعادة تشغيل الـ Service مرة اخرة ان كانت قد انتهت من عملها لكنها لم تصل لمرحلة الايقاف واذا كان نوع الـ Return عبارة عن Service.START_REDELIVER_INTENT ففى هذه الحالة يقوم الاندرويد بإعادة تشغيل الـService وإعادة تمرير الـ Intent وليس ارساله null مثلما يحدث فى الحالة الخاصة بSTART_STICKY كما يوجد عدة Return types اخرى لكن هذه اشهرها  وعلى كل حال فهى مجرد رموز نخبر بها النظام مذا سيفعل فى حال قام بانهاء هذه الـ Service اثناء توفير Memory للموبايل فى حالة  عدم توفر مساحة كافية .

 

لتجربة السيرفس السابقة قمت بعمل زرين فى الاكتيتفيتى واحد لبدء السيرفس والاخر لايقافها كالتالى :

استخدمنا الميثود startService() لبدء الـ Service واستخدمنا الميثود stopService() لإيقاف السيرفس نقوم بتشغيل التطبيق ونجرب الضغط على زر Start Service سيقوم الزر ببدء السيرفس وكذلك نجرب الضغط على زر Stop Service لنرى ماذا يحدث

 

عند الضغط على زر Start Service نجد انه تم مناداة onCreate ثم مناداة onStartCommand   نشاهد الـ Logcat ونجده كالتالى

وعند الضغط على Stop Service نجد الـ Logcat كالتالى يعنى انه تم مناداة onDestroy

 

بالاضافة لذلك يمكنك ايقاف الـسيرفس فى اى وقت من داخل السيرفس نفسها عن طريق الميثود stopself() تعال لنقوم بتجربة معا سنقوم بعمل تايمر بعد ٣ ثوانى من بدء السيرفس تقوم بتدمير نفسها ذاتيا

سنضع الكود فى onStartCommand كالتالى

نقوم الان بالتجربة وتشغيل التطبيق والضغط على زر start service ومراقبة الـ logcat فقط وسنجده كالتالى

وسوف تلاحظ انه بعد٣ ثوانى وبدون أن تقوم أنت بالضغط على زر stop service ستجد أن السيرفس توقفت

 

وهذا ببساطة طريقة او اسلوب استخدام السيرفس بطريقة الـ Started Service وكما رأينا ان السيرفس لا تخبر الاكتيتيفى شيئا ولا ترسل له اى رسائل   والان سنتطرق للطريقة الاخرى

 

الـ Bound Service 

النوع السابق او الاسلوب السابق فى استخدام الـ Service وهو الـ Started Service لا يعود ببيانات للمكون الذى اطلقه ولا تربطه به اى صلة واحيانا تحتاج تنفيذ المهام واسترجاع النتئائج من الـ Service وقد يسارع احد المطورين المتحمسين ويخبرنى انه يستطيع ان يعود بالنتائج من الـ Started Servcie باستخدام وسيلة مثل الـ Interface او مكتبة EventBus مثلا لكن نحن هنا نتكلم عن الـ Native فى الـ Android وليس الطرق الاخرى والحيل التى تستطيع بها تنفيذ ما تريد .

الـ Bound Service لا يتم بدءها فقط مثلما يتم بدء الـStarted Service بل يتم بدءها وربط المكون الذى بدء الـ Service (الأكتيتيفى مثلا الذى قمنا فيه ببدء السيرفس ) مع السيرفس نفسها ويصبح بإمكان الاكتييتفى او المكون الذى بدأها  الاتصال بالسيرفس وجلب البيانات منها فى اى وقت كما سنرى بالمثال بعد قليل .

وبشكل عام الـ Bind تعنى الربط والـ Bound Service تعنى السيرفس المربوطة بمكون اخر لذلك عندما نبدأ الـ Bound Service من الاكتيتيفى مثلا لا نقوم باستخدام الميثود startServcie()  بل نستخدم الـميثود bindService () وفى حالة الـ Started Service لا يكون المكون الذى بدأها مرتبط بها ويمكن لاى مكون ايقافها عن طريق الـ Intent لكن الـ Bound Service يمكن ربطها كما قلنا بـ bindService() وكذلك فك هذا الربط باستخدام unbindService() .

هل تذكر الميثود onBind التى اخبرتك أن تتجاهلها فى الـ Started Service ؟ الان حان وقت التعرف عليها واستخدامها ، هذه الميثود يتم تنفيذ الكود الخاص بها عند استدعاء الـ Service او بدأها بواسطة الميثود bindService()  تعود بـ IBinder  وهذا الـ IBinder هو عباره عن Interface سيمكننا من العبور للسيرفس كما سنرى بعد قليل

نعود للسيرفس التى أنشأنها سابقا HendiwareService والتى استخدمناها وبدأناها باسلوب الـ Started Service وسأقوم بتعديلها استعدادا لاستخدامها بطريقة أو بشكل Bound Service

 

لقد أزلت onStartCommand لاننى الان لا استخدمها كـ Started Service ولن أستخدم الامر startServcie() لبدء تشغيلها الان عرفنا ان الميثود onBind يتم تنفيذها اثناء الربط ومن المفترض انها ستعود بـ IBinder لكن ستجد بداخلها سطر throw new UnspoortedOperationException وبه رسالة Not yet implemented  ومهمة هذا السطر انه اذا حاول احد ما عمل bind لهذه السيرفس الان فسيحدث Exception ويتوقف التطبيق ويظهر له رسالة فى الـ Logcat انه لم يتم تهيئة هذه الـ Service بعد لتعمل فى وضع bound وهى رسالة Not yet implemented لذلك هيا بنا الان لنقوم بتهيئة هذه السيرفس للعمل كـ bound Service .

 

إن أول شىء احتاجه هو الـ IBinder لاننى سأمسح سطر throw new Unsupp…..  من الميثود وسيطلب منى عمل return   ويمكننى عمل هذا الـ Binder او الرابط عن طريق عمل كلاس يرث من Binder ثم عمل NewObject منه وسيحتوى هذا الكلاس على ميثود تعود بالـسيرفس الحالية كما يتضح فى الكود التالى :

 

هذه الكلاس سميناه HendiBinder كلاس داخلى يرث من الـ Binder اى انه Binder الان ويحتوى ميثود واحده تسمى getService() وتعود بالـ Service الحالية وقد يسأل احد ما الا استطيع عمل الاتى ببساطة HendiwareService service = new HendiwareSrvice() ومن ثم لدى الان اكسس على الـ Service لكن بهذه الطريقة ياصديقى تقوم بعمل سيرفس جديدة وليس Access على السيرفس التى ستعمل حاليا لذلك قمنا بعمل هذا الـ Binder السابق الان لدينا الـ Binder سنقوم بعمل أوبجكت منه الان لانك تعرف ان الكلاس مجرد هيكل لا اكثر سنقوم بعمل الاوبجكت ونقوم بجعل الميثود onBind يرجع بهذا الـ Binder كالتالى :

 

وقد يتبادر الى ذهنك لماذا قمنا بعمل IBinder وليس HendiBinder كنوع الريفرنس binder هذا الامر يشبه ما تفعله عندما تقوم بعمل List<?> something= new ArrayList()  فالـ List عباره عن انترفيس اما الـ ArrayList فهى كلاس وبالتالى نحتاج الى IBinder هنا . ولاحظ اننا عدلنا الميثود onBind لتعود بالـ binder .

الان تصبح الـ BoundService جاهزة للربط مع مكون اخر يمكنك وضع الكود فى ميثود معينة هنا ليتم تنفيذها وميثود اخرى لتعود بالنتائج وميثود تعود لك بقيمة اى عنصر من الـ Service فى اى وقت اى فى التعامل الباقى مع الـ Service تعامل ويكأنها كلاس عادية فقط نقوم بتهيئتها للربط كما فعلنا بالاعلى والان  يمكنك عمل ميثود مؤقتا تقوم بعمل شىء ما وليكن طباعة Hello world  كمثال كالتالى

 

الان نريد تشغيل هذه السيرفس ولكن هذه المرة سنقوم باستخدام الميثود bindService()  لكن هذه الميثود لا تأخذ باراميتر واحد مثل الميثود startService التى تأخذ الـ Intent فقط بل تأخذ ٣ باراميترات

الباراميتر الاول هو الـ Intent ، الباراميتر الثانى هو عباره عن الـ Service Connection  وسنتطرق اليه بعد قليل ، الباراميتر الثالث هو عباره عن الـ Flag مثلما كنا نفعل فى الـ Started Service ونخبر نظام الاندرويد ماذا يفعل اذا اضطر لاغلاق السيرفس الخاصة بنا .

بالنسبة للانتنت كما هو لن يتغير

بالنسبة للـ ServiceConnection فهنا سوف نستقبل الـ Binder من خلاله وننشىء اتصال مع الـ Service لنستطيع الوصول اليها وارسال ما نرريد او جلب ما نريد من نتائج ويتم ذلك كالتالى :

 

نعرف ريفرنس للسيرفس

نقوم بعمل الـ  Service Connection فى اخر ملف الـ Main Activity كالتالى

 

بكل بساطه قمنا بعمل new Object ثم اجبرنا على عمل ovrride لـ onServiceConnected() وهى الميثود التى يتم استدعائها عندما يتم الربط ب bindService وكذلك الميثود onServiceDisconneted وهى الميثود التى يتم استدعاؤها عند فك الربط باستخدام unbindService()

بداخل الميثود onServiceConnected قمنا بتعرفي HendiBinder لان الـ binder القادم الينا من الكونكشن هو  binder عام من فئة IBinder وقمنا بعمل cast

فى السطر الثانى مباشرة نقوم بالوصول للريفرنس الذى قمنا بتعريفه فى الاعلى عن طريق MainActivity.this.service ونساويه بناتج ميثود getService() التى تعود لنا بالـ Service  نفسها هل تذكر اننا قمنا بعمل هذه الميثود داخل الـ HendiBinder فى السيرفيس ؟  بالضبط هى هذه الميثود .

فى onServiceDisconnected من المفترض ان نساوى الريفرنس  بـ null لانه تم قطع الاتصال بيننا وبين السيرفس .

 

واخيرا الخطوة الاخيرة سنقوم باستخدام الميثود bindService() ونعطيها الباراميترز كالتالى

الان عندما نضغط على زر startService فانه يتم الربط ولاحظ ان الباراميتر الاخير Service.Bind_AUTO_CREATE هذا الـ Flag يفحص اذا ما كانت السيرفس تعمل من قبل فيتم الربط فقط اذا كانت لا تعمل فيتم انشاؤها واتمام الربط كذلك .

 

الان الـ Service مربوطة يمكننا ببساطة استدعاء اى ميثود من السيرفس تقوم باداء مهمة ما او ترجع بنتيجة عمل ما

واذا لم تكن فهمت الموضوع جيدا فقد تستاء وتقول ماهذا بحق الجحيم ؟ ان الامر يشبه ما يحدث فى الكلاسات العادية ميثود واستدعيها وكان يمكننى الاستغناء عن هذا كله لكن الاجابة انه الـ BoundService يمكن ربطها مع اكثر من مكون فى اى وقت يعنى يمكن ربطها مع اكتييتفى اخر ايضا وجعل الاكتيتيفى الاخر يستقبل النتيجة او يرسل لها امر وهذا الاكتييتفى يرسل لها امرا وهكذا يمكن ان ترتبط بأكثر من أكتيتيفى باستخدام الميثود bindService يمكنك ايضا فك الربط بين السيرفس والمكون الحالى بسهولة باستخدام الميثود unbindService()والتى تأخذ باراميتر واحد فقط وهو الـ intent .

فى النهاية يجب أن تعلم انه ايضا اذا اجريت عملية طويلة المدى فيجب عليك استخدام الـ Thread بداخل السيرفس ويوجد كلاس اخر يمكنك ان ترث منه بدل الـ Service وهو الـ IntentService ويمتاز الـ IntentService بأنه يعمل من خلال Thread اخر وليس Thread المكون الذى فتحه .

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

 

السابق
الإستخدام المتقدم للـ RecylcerView
التالي
كوتلين – الأساسيات : الدرس الأول مقدمة

10 تعليقات

أضف تعليقا

  1. سيف قال:

    شكرا

  2. Amr قال:

    شكرا جدا

  3. Osama قال:

    شكرا درس رائع

  4. احمد قال:

    شكرا لكم

  5. targ قال:

    شئ رئع

  6. soos قال:

    يعطيكم العافيه و يعني هاذي الطريقه هل اقدر احط في سيرفر كوندشن يشيك عليها طول الوقت حتى والبرنامج مسكر

  7. Abou7mied قال:

    شرح مبسط وجميل، ارجو التنبيه على اضافة tag السيرفس في ال Android Manifest عشان تقدر تستخدم السيرفس

  8. علي احمد قال:

    جزاك الله خير ياهندسة

  9. Zaher Almatrud قال:

    شكرا لكم

اترك تعليقاً

This site uses Akismet to reduce spam. Learn how your comment data is processed.