Service
هو
مكوّن تطبيق يمكنه تنفيذ
عمليات تستغرق وقتًا طويلاً في الخلفية. ولا توفّر واجهة مستخدم. بعد
بدء الخدمة، قد يستمر تشغيلها لبعض الوقت، حتى بعد أن ينتقل المستخدم إلى
تطبيق آخر. بالإضافة إلى ذلك، يمكن أن يرتبط المكوّن بخدمة للتفاعل معها وحتى تنفيذ
التواصل بين العمليات (IPC). على سبيل المثال، يمكن لخدمة ما معالجة المعاملات على الشبكة أو تشغيل الموسيقى أو نقل البيانات إلى الملفات أو منها أو التفاعل مع مقدّم محتوى، وكل ذلك من الخلفية.
تحذير: تعمل الخدمة في سلسلة التعليمات الرئيسية لعملية الاستضافة، ولا تنشئ الخدمة سلسلة تعليمات خاصة بها ولا تعمل في عملية منفصلة ما لم تحدِّد خلاف ذلك. يجب تنفيذ أي عمليات حظر على سلسلة محادثات منفصلة ضمن الخدمة لتجنُّب أخطاء "التطبيق لا يستجيب" (ANR).
أنواع الخدمات
في ما يلي الأنواع الثلاثة المختلفة من الخدمات:
- واجهة
-
تُنفِّذ الخدمة التي تعمل في المقدّمة بعض العمليات التي يلاحظها مستخدم التطبيق. على سبيل المثال، سيستخدم تطبيق صوتي خدمة تعمل في المقدّمة لتشغيل أغنية. يجب أن تعرِض الخدمات التي تعمل في المقدّمة إشعارًا. تستمر الخدمات التي تعمل في المقدّمة في العمل حتى عندما لا يتفاعل المستخدم مع التطبيق.
عند استخدام خدمة تعمل في المقدّمة، يجب عرض إشعار حتى يكون المستخدمون على دراية نشطة بأنّ الخدمة قيد التشغيل. لا يمكن إغلاق هذا الإشعار ما لم يتم إيقاف الخدمة أو إزالتها من المقدّمة.
اطّلِع على مزيد من المعلومات عن كيفية ضبط الخدمات التي تعمل في المقدّمة في تطبيقك.
ملاحظة: توفّر واجهة برمجة التطبيقات WorkManager طريقة مرنة لجدولة المهام، ويمكنها تنفيذ هذه المهام كخدمات تعمل في المقدّمة إذا لزم الأمر. في العديد من الحالات، يُفضّل استخدام واجهة برمجة التطبيقات WorkManager بدلاً من استخدام الخدمات التي تعمل في المقدّمة مباشرةً.
- خلفية
- تنفِّذ الخدمة التي تعمل في الخلفية عملية لا يلاحظها المستخدِم مباشرةً. على سبيل المثال، إذا كان التطبيق يستخدم خدمة لتقليل مساحة التخزين،
ستكون هذه الخدمة عادةً في الخلفية.
ملاحظة: إذا كان تطبيقك يستهدف المستوى 26 أو أعلى من واجهة برمجة التطبيقات، يفرض النظام قيودًا على تشغيل خدمات الخلفية عندما لا يكون التطبيق نفسه في المقدّمة. في معظم الحالات، على سبيل المثال، يجب عدم الوصول إلى معلومات الموقع الجغرافي من الخلفية. بدلاً من ذلك، حدِّد جدولاً زمنيًا للمهام باستخدام واجهة برمجة التطبيقات WorkManager.
- Bound
- يتم ربط الخدمة عندما يرتبط بها مكوّن تطبيق من خلال استدعاء
bindService()
. يوفّر الخدمة المرتبطة واجهة بين العميل والخادم تتيح للمكونات التفاعل مع الخدمة وإرسال الطلبات وتلقّي النتائج، وحتى إجراء ذلك على مستوى العمليات من خلال ميزة "التواصل بين العمليات" (IPC). لا يتم تشغيل الخدمة المرتبطة إلا طالما أنّ مكوّن تطبيق آخر مرتبط بها. يمكن ربط مكونات متعددة بالخدمة في آنٍ واحد، ولكن عند إلغاء ربطها جميعًا، يتم إلغاء الخدمة.
على الرغم من أنّ هذه المستندات تتناول بشكل عام الخدمات التي تم بدء تشغيلها وربطها بشكل منفصل،
يمكن أن تعمل خدمتك بالطريقتين: يمكن بدء تشغيلها (لتشغيلها إلى أجل غير مسمى) والسماح أيضًا
بربطها. ما عليك سوى تنفيذ طريقتَي استدعاء: onStartCommand()
للسماح للمكونات ببدء الإجراء وonBind()
للسماح بالربط.
بغض النظر عمّا إذا كانت خدمتك قد بدأت أو تم ربطها أو كليهما، يمكن لأي مكوّن من مكوّنات التطبيق
استخدام الخدمة (حتى من تطبيق منفصل) بالطريقة نفسها التي يمكن لأي مكوّن استخدام
نشاط بها، وذلك من خلال بدؤه باستخدام Intent
. ومع ذلك، يمكنك الإفصاح عن
أنّ الخدمة خاصة في ملف البيان وحظر الوصول إليها من التطبيقات الأخرى.
يمكنك الاطّلاع على مزيد من المعلومات حول هذا الموضوع في القسم المخصص لتعريف الخدمة فيملف البيان.
الاختيار بين خدمة وسلسلة محادثات
الخدمة هي ببساطة مكوّن يمكن تشغيله في الخلفية، حتى عندما لا يتفاعل المستخدم مع تطبيقك، لذا يجب إنشاء خدمة فقط إذا كان ذلك هو ما تحتاجه.
إذا كان عليك تنفيذ عمل خارج سلسلة المهام الرئيسية، ولكن فقط عندما يتفاعل المستخدِم
مع تطبيقك، عليك إنشاء سلسلة مهام جديدة في سياق عنصر
تطبيق آخر بدلاً من ذلك. على سبيل المثال، إذا كنت تريد تشغيل بعض الموسيقى، ولكن فقط أثناء تنفيذ نشاطك،
يمكنك إنشاء سلسلة محادثات في onCreate()
،
وبدء تشغيلها في onStart()
،
وإيقافها في onStop()
.
ننصحك أيضًا باستخدام مجموعات مؤشرات الترابط وأدوات التنفيذ من حزمة java.util.concurrent
أو عمليات التنسيق المتعدّد في Kotlin بدلاً من فئة
Thread
التقليدية. اطّلِع على مستند
استخدام مؤشرات الترابط في Android للحصول على مزيد من المعلومات حول
نقل التنفيذ إلى مؤشرات الترابط في الخلفية.
تذكَّر أنّه في حال استخدام خدمة، ستظلّ تعمل في سلسلة المهام الرئيسية لتطبيقك بشكلٍ تلقائي، لذا عليك إنشاء سلسلة مهام جديدة داخل الخدمة إذا كانت تُجري عمليات مكثفة أو عمليات حظر.
الأساسيات
لإنشاء خدمة، عليك إنشاء فئة فرعية من Service
أو استخدام إحدى
فئاتها الفرعية الحالية. في عملية التنفيذ، عليك إلغاء بعض طرق الاستدعاء التي تعالج
الجانبات الرئيسية لدورة حياة الخدمة وتوفير آلية تسمح للمكونات
بالربط بالخدمة، إذا كان ذلك مناسبًا. في ما يلي أهم طرق الاستدعاء التي يجب
استبدالها:
onStartCommand()
- يُستدعي النظام هذه الطريقة من خلال استدعاء
startService()
عندما يطلب مكوّن آخر (مثل نشاط) بدء الخدمة. عند تنفيذ هذه الطريقة، يتم بدء الخدمة ويمكن تشغيلها في الخلفية إلى أجل غير مسمى. في حال تنفيذ ذلك، تكون أنت المسؤول عن إيقاف الخدمة عند اكتمال عملها من خلال الاتصال بالرقمstopSelf()
أوstopService()
. إذا أردت فقط توفير عملية الربط، ليس عليك تنفيذ هذه الطريقة. onBind()
- يُستدعي النظام هذه الطريقة من خلال استدعاء
bindService()
عندما يريد مكوّن آخر الربط بالخدمة (مثل تنفيذ طلب إجراء برمجي). عند تنفيذ هذه الطريقة، يجب توفير واجهة يستخدمها العملاء للتواصل مع الخدمة من خلال عرضIBinder
. يجب تنفيذ هذه الطريقة دائمًا، ولكن إذا كنت لا تريد السماح بالربط، يجب إرجاع قيمة ملف شخصي فارغ. onCreate()
- يستدعي النظام هذه الطريقة لتنفيذ إجراءات الإعداد لمرة واحدة عند إنشاء الخدمة
في البداية (قبل استدعاء
onStartCommand()
أوonBind()
). وإذا كانت الخدمة قيد التشغيل، لن يتم استدعاء هذه الطريقة. onDestroy()
- يُستخدِم النظام هذه الطريقة عندما لا يتم استخدام الخدمة بعد الآن ويتم إزالتها. يجب أن تنفِّذ خدمتك ذلك لتنظيف أي موارد، مثل سلاسل المحادثات أو المُستمعِين المسجَّلين أو المستلِمين. هذه هي آخر مكالمة تتلقّاها الخدمة.
إذا بدأ أحد المكوّنات الخدمة من خلال استدعاء startService()
(ما يؤدي إلى استدعاء onStartCommand()
)، يستمر تنفيذ الخدمة إلى أن توقف نفسها باستخدام stopSelf()
أو يوقفها مكوّن
آخر من خلال استدعاء stopService()
.
إذا استدعى المكوّن bindService()
لإنشاء الخدمة ولم يتم استدعاء onStartCommand()
، يتم تشغيل الخدمة
فقط ما دام المكوّن مرتبطًا بها. بعد إلغاء ربط الخدمة بجميع عملائها،
يزيلها النظام.
لا يوقف نظام Android أي خدمة إلا عندما تكون الذاكرة منخفضة ويجب استرداد موارد النظام
للنشاط الذي يركز عليه المستخدم. إذا كانت الخدمة مرتبطة بنشاط يجذب تركيز المستخدِم، يقل احتمال إغلاقها. وإذا تم الإعلان عن الخدمة على أنّها تعمل في المقدّمة، نادرًا ما يتم إغلاقها.
إذا تم تشغيل الخدمة واستمر تشغيلها لفترة طويلة، يخفض النظام ترتيبها
في قائمة المهام التي تعمل في الخلفية بمرور الوقت، وتصبح الخدمة عرضة بشكل كبير لمحاولة
إيقافها. إذا تم تشغيل خدمتك، يجب تصميمها للتعامل بشكل سلس مع عمليات إعادة التشغيل التي يجريها النظام. إذا أوقف النظام خدمتك، سيعيد تشغيلها فور توفّر الموارد، ولكن يعتمد ذلك أيضًا على القيمة التي تعرضها من onStartCommand()
. لمزيد من المعلومات
حول الحالات التي قد يُزيل فيها النظام خدمة، يُرجى الاطّلاع على مستند العمليات وعمليات إنشاء المواضيع.
في الأقسام التالية، ستتعرّف على كيفية إنشاء أسلوبَي الخدمة
startService()
و
bindService()
، بالإضافة إلى كيفية استخدام
هما من مكوّنات التطبيق الأخرى.
تحديد خدمة في البيان
يجب الإفصاح عن جميع الخدمات فيملف بيان تطبيقك، تمامًا كما تفعل مع الأنشطة والمكوّنات الأخرى.
للإفصاح عن خدمتك، أضِف عنصر <service>
كعنصر فرعي لعنصر <application>
. يُرجى الاطّلاع على المثال أدناه:
<manifest ... > ... <application ... > <service android:name=".ExampleService" /> ... </application> </manifest>
اطّلِع على <service>
عنصر
المرجع للحصول على مزيد من المعلومات عن الإفصاح عن خدمتك في البيان.
هناك سمات أخرى يمكنك تضمينها في عنصر <service>
لتحديد خصائص مثل الأذونات المطلوبة لبدء الخدمة والعملية التي يجب أن تعمل فيها الخدمة. سمة android:name
هي السمة الوحيدة المطلوبة، وهي تحدّد اسم فئة الخدمة. بعد
نشر تطبيقك، اترك هذا الاسم بدون تغيير لتجنُّب خطر تعطُّل
الرمز البرمجي بسبب الاعتماد على النوايا الصريحة لبدء الخدمة أو ربطها (اطّلِع على مشاركة المدونة الأشياء التي
لا يمكن تغييرها).
تحذير: لضمان أمان تطبيقك، استخدِم دائمًا
نية صريحة عند بدء Service
ولا تحدِّد فلاتر نية
لخدماتك. يشكّل استخدام نية ضمنية لبدء خدمة خطرًا على الأمان لأنّه لا يمكنك
التأكّد من الخدمة التي تستجيب للنية، ولا يمكن للمستخدم معرفة الخدمة التي
تبدأ. بدءًا من الإصدار 5.0 من Android (المستوى 21 لواجهة برمجة التطبيقات)، يُرسِل النظام استثناءً في حال استدعاء
bindService()
باستخدام نية ضمنية.
يمكنك التأكّد من أنّ خدمتك متاحة لتطبيقك فقط من خلال
تضمين السمة android:exported
وضبطها على false
. يؤدي ذلك إلى إيقاف التطبيقات الأخرى بشكل فعّال عن بدء
خدمتك، حتى عند استخدام نية صريحة.
ملاحظة:
يمكن للمستخدمين الاطّلاع على الخدمات التي تعمل على أجهزتهم. إذا رأوا
خدمة لا يعرفونها أو لا يثقون بها، يمكنهم إيقافها. لتجنُّب إيقاف المستخدمين لخدمتك عن طريق الخطأ، عليك
إضافة السمة
android:description
إلى العنصر
<service>
في بيان التطبيق. في الوصف،
قدِّم جملة قصيرة توضّح ما تفعله الخدمة والمزايا التي تقدّمها.
إنشاء خدمة مفعَّلة
الخدمة التي تمّ بدؤها هي خدمة يبدأها مكوّن آخر من خلال استدعاء startService()
، ما يؤدي إلى استدعاء onStartCommand()
في الخدمة.
عند بدء الخدمة، تكون لها دورة حياة مستقلة عن
المكوّن الذي بدأها. يمكن تشغيل الخدمة في الخلفية إلى أجل غير مسمى، حتى إذا
تم إنهاء المكوّن الذي بدأها. وبناءً على ذلك، من المفترض أن توقف الخدمة نفسها عند اكتمال مهمتها
من خلال استدعاء stopSelf()
، أو يمكن لمكوّن آخر
إيقافها من خلال استدعاء stopService()
.
يمكن لمكوّن التطبيق، مثل النشاط، بدء الخدمة من خلال استدعاء startService()
وضبط Intent
الذي يحدّد الخدمة ويتضمن أي بيانات لاستخدامها. تتلقّى الخدمة
هذا Intent
في طريقة onStartCommand()
.
على سبيل المثال، لنفترض أنّ أحد الأنشطة يحتاج إلى حفظ بعض البيانات في قاعدة بيانات على الإنترنت. يمكن للنشاط
بدء خدمة مصاحبة وإرسال البيانات التي يجب حفظها إليها من خلال تمرير نية إلى startService()
. تتلقّى الخدمة الطلب في onStartCommand()
، وتتصل بالإنترنت، وتُجري
معاملة قاعدة البيانات. عند اكتمال المعاملة، تتوقف الخدمة تلقائيًا ويتم
إزالتها.
تحذير: يتم تشغيل الخدمة في العملية نفسها التي يتم فيها تعريف التطبيق ، وفي سلسلة المهام الرئيسية لهذا التطبيق تلقائيًا. إذا كانت خدمتك تُجري عمليات مكثفة أو عمليات حظر أثناء تفاعل المستخدم مع نشاط من التطبيق نفسه، تبطئ الخدمة أداء النشاط. لتجنُّب التأثير في أداء التطبيق، ابدأ سلسلة محادثات جديدة داخل الخدمة.
فئة Service
هي فئة dasar
لجميع الخدمات. عند إضافة فئة فرعية إلى هذه الفئة، من المهم إنشاء سلسلة محادثات جديدة يمكن للخدم
ة إكمال جميع أعمالها فيها، إذ تستخدم الخدمة سلسلة المحادثات الرئيسية لتطبيقك بشكلٍ default، ما قد يؤدي إلى إبطاء أداء أي نشاط يشغّله تطبيقك.
يقدّم إطار عمل Android أيضًا الفئة الفرعية IntentService
من Service
التي تستخدِم سلسلتَي عمليات Service
لمعالجة جميع طلبات البدء، واحدًا تلو الآخر. لا يُنصح باستخدام هذه الفئة للتطبيقات الجديدة لأنّها لن تعمل بشكل جيد بدءًا من Android 8 Oreo، وذلك بسبب
طرح حدود التنفيذ في الخلفية.
بالإضافة إلى ذلك، تم إيقاف هذه الميزة نهائيًا اعتبارًا من Android 11.
يمكنك استخدام JobIntentService كبديل لIntentService
متوافق مع الإصدارات الأحدث من Android.
توضِّح الأقسام التالية كيفية تنفيذ الخدمة المخصّصة، ولكن يجب التفكير جديًا في استخدام WorkManager بدلاً من ذلك في معظم حالات الاستخدام. يمكنك الرجوع إلى دليل المعالجة في الخلفية على Android لمعرفة ما إذا كان هناك حلّ يناسب احتياجاتك.
توسيع نطاق فئة الخدمة
يمكنك توسيع فئة Service
للتعامل مع كل نية واردة. في ما يلي مثال على عملية تنفيذ أساسية:
Kotlin
class HelloService : Service() { private var serviceLooper: Looper? = null private var serviceHandler: ServiceHandler? = null // Handler that receives messages from the thread private inner class ServiceHandler(looper: Looper) : Handler(looper) { override fun handleMessage(msg: Message) { // Normally we would do some work here, like download a file. // For our sample, we just sleep for 5 seconds. try { Thread.sleep(5000) } catch (e: InterruptedException) { // Restore interrupt status. Thread.currentThread().interrupt() } // Stop the service using the startId, so that we don't stop // the service in the middle of handling another job stopSelf(msg.arg1) } } override fun onCreate() { // Start up the thread running the service. Note that we create a // separate thread because the service normally runs in the process's // main thread, which we don't want to block. We also make it // background priority so CPU-intensive work will not disrupt our UI. HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND).apply { start() // Get the HandlerThread's Looper and use it for our Handler serviceLooper = looper serviceHandler = ServiceHandler(looper) } } override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show() // For each start request, send a message to start a job and deliver the // start ID so we know which request we're stopping when we finish the job serviceHandler?.obtainMessage()?.also { msg -> msg.arg1 = startId serviceHandler?.sendMessage(msg) } // If we get killed, after returning from here, restart return START_STICKY } override fun onBind(intent: Intent): IBinder? { // We don't provide binding, so return null return null } override fun onDestroy() { Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show() } }
Java
public class HelloService extends Service { private Looper serviceLooper; private ServiceHandler serviceHandler; // Handler that receives messages from the thread private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { // Normally we would do some work here, like download a file. // For our sample, we just sleep for 5 seconds. try { Thread.sleep(5000); } catch (InterruptedException e) { // Restore interrupt status. Thread.currentThread().interrupt(); } // Stop the service using the startId, so that we don't stop // the service in the middle of handling another job stopSelf(msg.arg1); } } @Override public void onCreate() { // Start up the thread running the service. Note that we create a // separate thread because the service normally runs in the process's // main thread, which we don't want to block. We also make it // background priority so CPU-intensive work doesn't disrupt our UI. HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); // Get the HandlerThread's Looper and use it for our Handler serviceLooper = thread.getLooper(); serviceHandler = new ServiceHandler(serviceLooper); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show(); // For each start request, send a message to start a job and deliver the // start ID so we know which request we're stopping when we finish the job Message msg = serviceHandler.obtainMessage(); msg.arg1 = startId; serviceHandler.sendMessage(msg); // If we get killed, after returning from here, restart return START_STICKY; } @Override public IBinder onBind(Intent intent) { // We don't provide binding, so return null return null; } @Override public void onDestroy() { Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show(); } }
يعالج مثال الرمز البرمجي جميع المكالمات الواردة في onStartCommand()
وينشر العمل على Handler
الذي يعمل في سلسلة محادثات في الخلفية. ويعمل هذا الإجراء تمامًا مثل IntentService
ويعالج جميع الطلبات بشكل تسلسلي، واحدًا تلو الآخر.
يمكنك تغيير الرمز لتشغيل العمل في تجمع مؤشرات الترابط، على سبيل المثال، إذا كنت تريد تنفيذ طلبات متعددة في وقت واحد.
يُرجى العلم أنّ الطريقة onStartCommand()
يجب أن تُرجع عددًا تكامليًا. ويمثّل هذا العدد الصحيح قيمة تصف كيفية مواصلة النظام لتوفير الخدمة في حال إيقافه. يجب أن تكون القيمة المعروضة
من onStartCommand()
إحدى القيمة
الثابتة التالية:
START_NOT_STICKY
- إذا أوقف النظام الخدمة بعد إرجاع
onStartCommand()
، لا تُعدّل الخدمة ما لم تكن هناك طلبات إرسال في انتظار المراجعة. هذا هو الخيار الأكثر أمانًا لتجنُّب تشغيل الخدمة عندما لا يكون ذلك ضروريًا وعندما يمكن لتطبيقك إعادة تشغيل أي مهام غير مكتملة بسهولة. START_STICKY
- إذا أوقف النظام الخدمة بعد عودة
onStartCommand()
، أعِد إنشاء الخدمة واتصل بـonStartCommand()
، ولكن لا تُعِد إرسال الطلب الأخير. بدلاً من ذلك، يستدعي النظامonStartCommand()
باستخدام intent null ما لم تكن هناك نوايا في انتظار المراجعة لبدء الخدمة. في هذه الحالة، يتم إرسال تلك النوايا. هذا مناسب لمشغلات الوسائط (أو الخدمات المشابهة) التي لا изпълнي الأوامر ولكنها تعمل إلى أجل غير مسمى في انتظار مهمة. START_REDELIVER_INTENT
- إذا أوقف النظام الخدمة بعد عودة
onStartCommand()
، أعِد إنشاء الخدمة واتصل بـonStartCommand()
باستخدام آخر نية تم إرسالها إلى الخدمة. ويتم تسليم أيّ نوايا معلّقة بدورها. هذا مناسب للخدمات التي تؤدي مهمة بشكلٍ نشط ويجب استئنافها على الفور، مثل تنزيل ملف.
لمزيد من التفاصيل عن قيم الإرجاع هذه، اطّلِع على مستندات المرجع المرتبط لكلّ ثابت.
بدء خدمة
يمكنك بدء خدمة من نشاط أو مكوّن تطبيق آخر من خلال
تمرير Intent
إلى startService()
أو startForegroundService()
. يُطلِق
نظام Android طريقة onStartCommand()
للخدمة ويمرّر إليها Intent
،
الذي يحدّد الخدمة التي سيتم بدؤها.
ملاحظة: إذا كان تطبيقك يستهدف المستوى 26 من واجهة برمجة التطبيقات أو المستويات الأحدث، يفرض النظام
قيودًا على استخدام الخدمات التي تعمل في الخلفية أو إنشائها ما لم يكن التطبيق
نفسه في المقدّمة. إذا كان التطبيق بحاجة إلى إنشاء خدمة تعمل في المقدّمة،
يجب أن يستدعي التطبيق startForegroundService()
. تنشئ هذه الطريقة خدمة تعمل في الخلفية، ولكن تشير طريقة
إلى النظام بأنّ الخدمة ستنقل نفسها إلى foreground. بعد إنشاء الخدمة، يجب أن تستدعي الخدمة startForeground()
في غضون خمس ثوانٍ.
على سبيل المثال، يمكن أن يبدأ النشاط مثال الخدمة في القسم السابق (HelloService
) باستخدام نية صريحة مع startService()
، كما هو موضّح هنا:
Kotlin
startService(Intent(this, HelloService::class.java))
Java
startService(new Intent(this, HelloService.class));
تُرجع طريقة startService()
القيمة على الفور، ويُطلِق نظام Android طريقة onStartCommand()
في الخدمة. إذا لم تكن الخدمة قيد التشغيل، يتصل النظام أولاً بـ onCreate()
، ثم يتصل بـ
onStartCommand()
.
إذا لم توفّر الخدمة أيضًا عملية ربط، يكون الإجراء الذي يتم إرساله باستخدام startService()
هو طريقة التواصل الوحيدة بين
مكوّن التطبيق والخدمة. ومع ذلك، إذا كنت تريد أن ترسل الخدمة نتيجة،
يمكن للبرنامج الذي يبدأ الخدمة إنشاء PendingIntent
للبث
(مع getBroadcast()
) وإرساله إلى الخدمة
في Intent
الذي يبدأ الخدمة. ويمكن للخدمتَين بعد ذلك استخدام البث لعرض نتيجة.
تؤدي الطلبات المتعدّدة لبدء الخدمة إلى إجراء مكالمات متعدّدة مقابلة لمحاولة بدء الخدمة
onStartCommand()
. ومع ذلك، لا يلزم سوى طلب واحد لإيقاف
الخدمة (باستخدام stopSelf()
أو stopService()
).
إيقاف خدمة
يجب أن تدير الخدمة التي تم تشغيلها دورة حياتها. وهذا يعني أنّ النظام لا يوقف الخدمة أو
يدمّرها ما لم يكن عليه استرداد ذاكرة النظام، ويستمر تشغيل الخدمة
بعد إرجاع onStartCommand()
. يجب أن توقف العبارة
الخدمة بنفسها من خلال استدعاء stopSelf()
، أو يمكن أن يوقفها عنصر
آخر من خلال استدعاء stopService()
.
بعد طلب الإيقاف باستخدام stopSelf()
أو stopService()
، يُنهي النظام الخدمة في أقرب وقت ممكن.
إذا كانت خدمتك تعالج طلبات متعددة إلى onStartCommand()
بشكل متزامن، يجب عدم إيقاف
ا عند الانتهاء من معالجة طلب بدء، لأنّه قد يكون قد وصلك طلب بدء جديد (سيؤدي الإيقاف في نهاية الطلب الأول إلى إنهاء الطلب الثاني). لتجنُّب
هذه المشكلة، يمكنك استخدام stopSelf(int)
لضمان أن يستند طلبك ل stopped الخدمة دائمًا إلى أحدث طلب بدء. وهذا يعني أنّه عند الاتصال برقم stopSelf(int)
، يتم تمرير معرّف طلب البدء (startId
الذي تم إرساله إلى onStartCommand()
) الذي يتوافق مع طلب الإيقاف. بعد ذلك، إذا تلقّت الخدمة طلب بدء جديد قبل أن تتمكّن من الاتصال بـ stopSelf(int)
، لن يتطابق المعرّف ولن تتوقف الخدمة.
تحذير: لتجنُّب إهدار موارد النظام واستهلاك
طاقة البطارية، تأكَّد من أنّ تطبيقك يوقف خدماته عند الانتهاء من العمل.
يمكن للمكونات الأخرى إيقاف الخدمة عند الضرورة من خلال الاتصال بالرقم stopService()
. حتى في حال تفعيل الربط للخدمة،
يجب إيقاف الخدمة بنفسك في حال تلقّيها طلبًا للاتصال بالرقم onStartCommand()
.
لمزيد من المعلومات عن دورة حياة الخدمة، اطّلِع على القسم أدناه حول إدارة دورة حياة الخدمة.
إنشاء خدمة مرتبطة
الخدمة المرتبطة هي خدمة تسمح لمكونات التطبيق بالارتباط بها من خلال الاتصال بـ bindService()
لإنشاء اتصال دائم.
ولا يسمح بشكل عام للمكونات بتشغيله من خلال الاتصال بـ startService()
.
أنشئ خدمة مرتبطة عندما تريد التفاعل مع الخدمة من الأنشطة والمكونات الأخرى في تطبيقك أو لعرض بعض وظائف تطبيقك أمام التطبيقات الأخرى من خلال ميزة "التواصل بين العمليات" (IPC).
لإنشاء خدمة مرتبطة، نفِّذ طريقة معاودة الاتصال onBind()
لعرض IBinder
يحدد واجهة التواصل مع الخدمة. يمكن بعد ذلك لمكونات التطبيق الأخرى الاتصال بـ
bindService()
لاسترداد الواجهة و
بدء طرق الاتصال في الخدمة. لا تُستخدَم الخدمة إلا لتقديم مكوّن التطبيق الذي
يكون مرتبطًا بها، لذا عندما لا تتوفّر مكوّنات مرتبطة بالخدمة، يدمرها النظام.
لست بحاجة إلى إيقاف خدمة مرتبطة بالطريقة نفسها التي يجب بها عند بدء الخدمة
من خلال onStartCommand()
.
لإنشاء خدمة مرتبطة، عليك تحديد الواجهة التي تحدِّد كيفية تواصل العميل
مع الخدمة. يجب أن تكون هذه الواجهة بين الخدمة
والعميل عملية تنفيذ IBinder
، وهي ما يجب أن تُرجعه خدمتك
من طريقة طلب onBind()
. بعد أن يتلقّى العميل IBinder
، يمكنه بدء
التفاعل مع الخدمة من خلال هذه الواجهة.
يمكن ربط عدة عملاء بالخدمة في الوقت نفسه. عندما ينتهي العميل من التفاعل مع
الخدمة، يتصل بـ unbindService()
لإلغاء الربط.
عندما لا يكون هناك عملاء مرتبطون بالخدمة، يُزيل النظام الخدمة.
هناك طرق متعدّدة لتنفيذ خدمة مرتبطة، ويكون التنفيذ أكثر صعوبة مقارنةً بالخدمة التي تم تشغيلها. لهذه الأسباب، تظهر مناقشة الخدمة المرتبطة في مستند منفصل عن الخدمات المرتبطة.
إرسال إشعارات إلى المستخدم
عندما تكون الخدمة قيد التشغيل، يمكنها إرسال إشعارات إلى المستخدم بشأن الأحداث باستخدام إشعارات شريط الإشعارات المنبثق أو إشعارات شريط الحالة.
إشعار شريط المعلومات هو رسالة تظهر على سطح النافذة الحالية لعدة ثوانٍ فقط قبل أن تختفي. يعرض إشعار شريط الحالة رمزًا في شريط الحالة مع رسالة يمكن للمستخدم اختيارها لاتّخاذ إجراء (مثل بدء نشاط).
عادةً ما يكون إشعار شريط الحالة هو أفضل أسلوب يمكن استخدامه عند اكتمال عمل في الخلفية، مثل تنزيل ملف، ويمكن للمستخدم الآن اتّخاذ إجراء بشأنه. عندما يختار المستخدم الإشعار من طريقة العرض الموسّعة، يمكن أن يبدأ الإشعار نشاطًا (مثل عرض الملف الذي تم تنزيله).
إدارة دورة حياة خدمة
تكون دورة حياة الخدمة أبسط بكثير من دورة حياة النشاط. ومع ذلك، من المُهم جدًا أن تهتم بشكلٍ وثيق بطريقة إنشاء الخدمة وإزالتها لأنّه يمكن تنفيذ الخدمة في الخلفية بدون علم المستخدم.
يمكن أن تتّبع دورة حياة الخدمة، بدءًا من إنشائها وحتى إزالتها، أحد هذين المسارَين:
- خدمة تم بدء تشغيلها
يتم إنشاء الخدمة عندما يستدعي مكوّن آخر
startService()
. بعد ذلك، تعمل الخدمة إلى أجل غير مسمى ويجب إيقافها بنفسها من خلال الاتصال بالرقمstopSelf()
. يمكن أيضًا لمكوّن آخر إيقاف الخدمة من خلال الاتصال بالرقمstopService()
. عند إيقاف الخدمة، يدمرها النظام. - خدمة مرتبطة
يتم إنشاء الخدمة عندما يستدعي مكوّن آخر (عميل)
bindService()
. يتواصل العميل بعد ذلك مع الخدمة من خلال واجهةIBinder
. يمكن للعميل إغلاق الاتصال من خلال الاتصال بالرقمunbindService()
. يمكن لعدة عملاء الربط بالخدمة نفسها، وعندما يُلغي جميعهم الربط، يُزيل النظام الخدمة. لا تحتاج الخدمة إلى إيقاف نفسها.
لا يكون هذان المساران منفصلَين تمامًا. يمكنك الربط بخدمة سبق أن
تم تشغيلها باستخدام startService()
. على سبيل المثال، يمكنك
بدء خدمة تشغيل موسيقى في الخلفية من خلال الاتصال بخدمة startService()
باستخدام Intent
يحدِّد الموسيقى المطلوب تشغيلها. لاحقًا،
ربما عندما يريد المستخدم ممارسة بعض التحكّم في المشغّل أو الحصول على معلومات عن
الأغنية الحالية، يمكن ربط نشاط بالخدمة من خلال استدعاء bindService()
. في مثل هذه الحالات، لا يوقف stopService()
أو stopSelf()
الخدمة فعليًا إلى أن يتم إلغاء ربط جميع العملاء.
تنفيذ وظائف الاستدعاء في مراحل النشاط
مثل النشاط، تتضمّن الخدمة طرقًا لطلب معاودة الاتصال بالنشاط يمكنك تنفيذها لتتبُّع التغييرات في حالة الخدمة وتنفيذ المهام في الأوقات المناسبة. يوضّح الهيكل التالي service كلّ طريقة من طرق دورة الحياة:
Kotlin
class ExampleService : Service() { private var startMode: Int = 0 // indicates how to behave if the service is killed private var binder: IBinder? = null // interface for clients that bind private var allowRebind: Boolean = false // indicates whether onRebind should be used override funonCreate
() { // The service is being created } override funonStartCommand
(intent: Intent?, flags: Int, startId: Int): Int { // The service is starting, due to a call to startService() return startMode } override funonBind
(intent: Intent): IBinder? { // A client is binding to the service with bindService() return binder } override funonUnbind
(intent: Intent): Boolean { // All clients have unbound with unbindService() return allowRebind } override funonRebind
(intent: Intent) { // A client is binding to the service with bindService(), // after onUnbind() has already been called } override funonDestroy
() { // The service is no longer used and is being destroyed } }
Java
public class ExampleService extends Service { int startMode; // indicates how to behave if the service is killed IBinder binder; // interface for clients that bind boolean allowRebind; // indicates whether onRebind should be used @Override public voidonCreate
() { // The service is being created } @Override public intonStartCommand
(Intent intent, int flags, int startId) { // The service is starting, due to a call tostartService()
return startMode; } @Override public IBinderonBind
(Intent intent) { // A client is binding to the service withbindService()
return binder; } @Override public booleanonUnbind
(Intent intent) { // All clients have unbound withunbindService()
return allowRebind; } @Override public voidonRebind
(Intent intent) { // A client is binding to the service withbindService()
, // after onUnbind() has already been called } @Override public voidonDestroy
() { // The service is no longer used and is being destroyed } }
ملاحظة: على عكس طرق الاستدعاء المتعلّقة بدورة حياة النشاط، ليس عليك استدعاء تنفيذ الفئة الأساسية لهذه الطرق.
يوضّح الشكل 2 طرق ردّ الاتصال المعتادة لخدمة معيّنة. على الرغم من أنّ الشكل يفصل بين
الخدمات التي أنشأها startService()
وتلك التي
أنشأها bindService()
، يُرجى
العلم أنّ أي خدمة، بغض النظر عن كيفية بدؤها، يمكن أن تسمح للعملاء بالربط بها.
الخدمة التي تمّ بدءها في البداية باستخدام onStartCommand()
(من خلال عميل يتصل بالرقم startService()
)
يمكنها أيضًا تلقّي مكالمة على onBind()
(عندما يتصل العميل بالرقم
bindService()
).
من خلال تنفيذ هذه الطرق، يمكنك تتبُّع هاتين الدورتَين المتداخلتَين في مراحل تطوير الخدمة:
- تحدث دورة حياة الخدمة بالكامل بين وقت استدعاء
onCreate()
ووقت عرضonDestroy()
. مثل النشاط، تُجري الخدمة عملية الإعداد الأولي في مرحلةonCreate()
وتُطلق جميع الموارد المتبقية في مرحلةonDestroy()
. على سبيل المثال، يمكن للخدمة لتشغيل الموسيقى إنشاء سلسلة المحادثات التي يتم فيها تشغيل الموسيقى فيonCreate()
، ثم يمكنها إيقاف سلسلة المحادثات فيonDestroy()
.ملاحظة: يتمّ استدعاء الطريقتَين
onCreate()
وonDestroy()
لجميع الخدمات، سواء كانت من إنشاءstartService()
أوbindService()
. - تبدأ مدة النشاط لأي خدمة من خلال طلب إلى
onStartCommand()
أوonBind()
. يتم تسليمIntent
إلى كل طريقة تم تمريرها إلىstartService()
أوbindService()
.في حال بدء الخدمة، تنتهي الفترة النشطة في الوقت نفسه الذي تنتهي فيه الفترة الكلية (تظل الخدمة نشطة حتى بعد إرجاع
onStartCommand()
). إذا كانت الخدمة مرتبطة، تنتهي الفترة النشطة عند عودةonUnbind()
.
ملاحظة: على الرغم من أنّ الخدمة التي تمّ بدؤها يتم إيقافها من خلال مكالمة إلى
stopSelf()
أو stopService()
، لا تتوفّر مكالمة تأكيدية مقابلة للخدمة (لا تتوفّر مكالمة تأكيدية onStop()
). ما لم تكن الخدمة مرتبطة بخادم،
يزيل النظامها عند إيقاف الخدمة، ويكون onDestroy()
هو طلب الاستدعاء الوحيد الذي يتم تلقّيه.
لمزيد من المعلومات عن إنشاء خدمة توفّر عملية الربط، يُرجى الاطّلاع على مستند الخدمات المرتبطة، الذي يحتوي على مزيد من المعلومات عن طريقة طلب onRebind()
في القسم عن إدارة دورة حياة
الخدمة المرتبطة.