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

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

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

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

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

واجهة

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

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

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

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

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

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

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

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

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

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

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

إذا كان عليك تنفيذ عمل خارج سلسلة التعليمات الرئيسية، ولكن فقط أثناء تفاعل المستخدم مع تطبيقك، يجب عليك بدلاً من ذلك إنشاء سلسلة تعليمات جديدة في سياق مكوّن تطبيق آخر. على سبيل المثال، إذا كنت تريد تشغيل بعض الموسيقى، ولكن فقط أثناء تنفيذ نشاطك، يمكنك إنشاء سلسلة محادثات في onCreate() وبدء تشغيلها في onStart() وإيقافها في onStop(). يمكنك أيضًا استخدام مجموعات سلاسل المحادثات وأدوات التنفيذ من حزمة java.util.concurrent أو كوروتيني كوتلين بدلاً من فئة 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() في القسم حول إدارة دورة حياة الخدمة المرتبطة.