نظرة عامة حول الخدمات

Service هو مكوِّن تطبيق يمكنه تنفيذ عمليات طويلة الأمد في الخلفية. لا يوفر واجهة مستخدم. بعد بدء تشغيل الخدمة، قد تستمر الخدمة لبعض الوقت، حتى بعد انتقال المستخدم إلى تطبيق آخر. بالإضافة إلى ذلك، يمكن ربط المكون بخدمة للتفاعل معه وإجراء اتصال بين العمليات (IPC). على سبيل المثال، يمكن للخدمة معالجة معاملات الشبكة أو تشغيل الموسيقى أو تنفيذ إدخال/إخراج للملف أو التفاعل مع موفر محتوى، وكل ذلك من الخلفية.

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

أنواع الخدمات

في ما يلي الأنواع الثلاثة المختلفة للخدمات:

واجهة

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

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

يمكنك الاطّلاع على مزيد من المعلومات حول كيفية ضبط الخدمات التي تعمل في المقدّمة في تطبيقك.

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

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

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

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

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

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

الاختيار بين خدمة وسلسلة محادثات

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

إذا كان يتعين عليك تنفيذ عمل خارج سلسلة التعليمات الرئيسية ولكن فقط أثناء تفاعل المستخدم مع تطبيقك، يجب عليك بدلاً من ذلك إنشاء سلسلة محادثات جديدة في سياق مكون تطبيق آخر. على سبيل المثال، إذا أردت تشغيل بعض الموسيقى، ولكن فقط أثناء تنفيذ نشاطك، يمكنك إنشاء سلسلة محادثات في onCreate() والبدء بتشغيلها في onStart() وإيقافها في onStop(). يمكنك أيضًا استخدام مجموعات سلاسل المحادثات وبرامج التنفيذ من حزمة java.util.concurrent أو مجموعات الكوروتين من Kotlin بدلاً من الفئة Thread التقليدية. راجِع مستند Threading على Android لمزيد من المعلومات عن نقل التنفيذ إلى سلاسل المحادثات في الخلفية.

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

الأساسيات

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

onStartCommand()
يستدعي النظام هذه الطريقة من خلال استدعاء startService() عندما يطلب مكوّن آخر (مثل نشاط) بدء الخدمة. عند تنفيذ هذه الطريقة، يتم بدء الخدمة ويمكن تشغيلها في الخلفية إلى أجل غير مسمى. في حال تنفيذ ذلك، تقع على عاتقك مسؤولية إيقاف الخدمة عند اكتمال عملها من خلال الاتصال بـ stopSelf() أو stopService(). إذا كنت تريد توفير الربط فقط، فلن تحتاج إلى تنفيذ هذه الطريقة.
onBind()
يستدعي النظام هذه الطريقة من خلال استدعاء bindService() عندما يريد مكوّن آخر الربط بالخدمة (مثل تنفيذ RPC). لتنفيذ هذه الطريقة، يجب توفير واجهة يستخدمها العملاء للتواصل مع الخدمة من خلال عرض 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 هي السمة الوحيدة المطلوبة، وهي تحدّد اسم فئة الخدمة. بعد نشر التطبيق، اترك هذا الاسم بدون تغيير لتجنّب خطر اختراق الرمز بسبب الاعتماد على أغراض صريحة لبدء الخدمة أو ربطها (يُرجى الاطّلاع على مشاركة المدونة بعنوان Things That not Change).

تنبيه: لضمان أمان تطبيقك، يجب دائمًا استخدام هدف صريح عند بدء Service وعدم الإفصاح عن فلاتر الأهداف لخدماتك. يشكّل استخدام نية ضمنية لبدء الخدمة خطرًا أمنيًا، لأنّه لا يمكنك أن تكون متأكدًا من استجابة الخدمة للقصد، ولا يستطيع المستخدم معرفة الخدمة التي تبدأ. بدايةً من Android 5.0 (المستوى 21 من واجهة برمجة التطبيقات)، يطرح النظام استثناءً في حال طلب الرمز bindService() بهدف ضمني.

يمكنك ضمان توفُّر الخدمة لتطبيقك فقط من خلال تضمين السمة android:exported وضبطها على false. ويؤدي هذا إلى منع التطبيقات الأخرى من بدء خدمتك، حتى عند استخدام هدف صريح.

ملاحظة: يمكن للمستخدمين الاطّلاع على الخدمات قيد التشغيل على أجهزتهم. وإذا رأى المستخدم خدمة لا يتعرّف عليها أو يثق بها، يمكنه إيقاف الخدمة. لتجنّب إيقاف الخدمة عن غير قصد من قِبل المستخدمين، عليك إضافة السمة android:description إلى العنصر <service> في بيان التطبيق. في الوصف، قدِّم جملة قصيرة توضّح المزايا التي تقدّمها الخدمة والمزايا التي تقدّمها.

إنشاء خدمة تم بدؤها

الخدمة التي يتم تشغيلها هي الخدمة التي يبدأ مكوّن آخر من خلال طلبها باستدعاء startService()، ما يؤدي إلى طلب طريقة onStartCommand() الخاصة بالخدمة.

عند بدء تشغيل خدمة، تكون لها دورة حياة مستقلة عن المكون الذي بدأها. يمكن تشغيل الخدمة في الخلفية إلى أجل غير مسمى، حتى إذا تم تدمير المكون الذي بدأها. وبناءً على ذلك، يجب أن تتوقّف الخدمة عن نفسها عند اكتمال المهمّة من خلال طلب الرمز stopSelf()، أو يمكن لمكوِّن آخر إيقافها عن طريق طلب stopService().

يمكن لمكوِّن تطبيق، مثل نشاط، بدء الخدمة من خلال استدعاء startService() وتمرير Intent الذي يحدّد الخدمة ويتضمّن أي بيانات حتى تستخدمها الخدمة. تتلقّى الخدمة هذه السمة Intent في طريقة onStartCommand().

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

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

الفئة Service هي الفئة الأساسية لجميع الخدمات. عند تمديد هذه الفئة، من المهم إنشاء سلسلة محادثات جديدة يمكن للخدمة من خلالها إكمال جميع أعمالها، إذ تستخدم الخدمة سلسلة التعليمات الرئيسية لتطبيقك تلقائيًا، ما قد يؤدي إلى إبطاء أداء أي نشاط يُشغِّله تطبيقك.

يوفّر إطار عمل Android أيضًا الفئة الفرعية IntentService من 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() بدون هدف ما لم تكن هناك نوايا معلّقة لبدء الخدمة. في هذه الحالة، يتم تحقيق هذه الأهداف. ويناسب ذلك مشغّلات الوسائط (أو الخدمات المماثلة) التي لا تنفّذ الأوامر ولكنها تعمل إلى أجل غير مسمى وتنتظر المهام.
START_REDELIVER_INTENT
إذا أنهى النظام الخدمة بعد عودة "onStartCommand()"، عليك إعادة إنشاء الخدمة والاتصال بـ "onStartCommand()" بالهدف الأخير الذي تم تسليمه إلى الخدمة. ويتم تسليم أي نوايا في انتظار المراجعة بالترتيب. وهذا مناسب للخدمات التي تؤدي بشكل نشط مهمة يجب استئنافها على الفور، مثل تنزيل ملف.

للمزيد من التفاصيل حول القيم المعروضة هذه، يُرجى الاطّلاع على الوثائق المرجعية المرتبطة لكل ثابت.

بدء خدمة

يمكنك بدء خدمة من نشاط أو مكوِّن آخر في التطبيق عن طريق تمرير Intent إلى startService() أو startForegroundService(). يستدعي نظام Android طريقة onStartCommand() الخاصة بالخدمة ويمرّرها بالرمز Intent، الذي يحدّد الخدمة التي سيتم تشغيلها.

ملاحظة: إذا كان تطبيقك يستهدف المستوى 26 من واجهة برمجة التطبيقات أو المستويات الأعلى، يفرض النظام قيودًا على استخدام الخدمات في الخلفية أو إنشائها ما لم يكن التطبيق نفسه في المقدّمة. إذا كان أحد التطبيقات بحاجة إلى إنشاء خدمة تعمل في المقدّمة، يجب أن يطلب التطبيق الاتصال بـ startForegroundService(). تنشئ هذه الطريقة خدمة تُشغَّل في الخلفية، إلا أنّها تُرسِل إشارة إلى النظام بأنّ الخدمة ستقوم بالترويج لنفسها في المقدّمة. بعد إنشاء الخدمة، يجب أن تستدعي الخدمة طريقة 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) للتأكّد من أنّ طلبك لإيقاف الخدمة يستند دائمًا إلى أحدث طلب بدء. أي عند الاتصال بـ 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() الخدمة فعليًا حتى يتم إلغاء ربط جميع البرامج.

تنفيذ دورة حياة معاودة الاتصال

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

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 fun onCreate() {
        // The service is being created
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // The service is starting, due to a call to startService()
        return startMode
    }

    override fun onBind(intent: Intent): IBinder? {
        // A client is binding to the service with bindService()
        return binder
    }

    override fun onUnbind(intent: Intent): Boolean {
        // All clients have unbound with unbindService()
        return allowRebind
    }

    override fun onRebind(intent: Intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }

    override fun onDestroy() {
        // 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 void onCreate() {
        // The service is being created
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // The service is starting, due to a call to startService()
        return startMode;
    }
    @Override
    public IBinder onBind(Intent intent) {
        // A client is binding to the service with bindService()
        return binder;
    }
    @Override
    public boolean onUnbind(Intent intent) {
        // All clients have unbound with unbindService()
        return allowRebind;
    }
    @Override
    public void onRebind(Intent intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }
    @Override
    public void onDestroy() {
        // The service is no longer used and is being destroyed
    }
}

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

الشكل 2. دورة حياة الخدمة. يوضّح المخطّط على اليسار دورة الحياة عند إنشاء الخدمة باستخدام startService()، ويعرض الرسم البياني على اليمين دورة الحياة عند إنشاء الخدمة باستخدام bindService().

ويوضح الشكل 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() في القسم المتعلّق بـ إدارة دورة حياة الخدمة المرتبطة.