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

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

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

الأساسيات

خدمة الربط هي تنفيذ لفئة Service التي تتيح للتطبيقات الأخرى الربط بها والتفاعل معها. لتوفير الربط لخدمة، يمكنك تنفيذ طريقة رد الاتصال onBind(). تعرض هذه الطريقة كائن IBinder يحدّد واجهة البرمجة التي يمكن للعملاء استخدامها للتفاعل مع الخدمة.

الربط بخدمة تم تشغيلها

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

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

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

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

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

عندما ينشئ نظام Android الاتصال بين العميل والخدمة، يستدعي onServiceConnected() في ServiceConnection. تتضمّن طريقة onServiceConnected() الوسيطة IBinder التي يستخدمها العميل بعد ذلك للتواصل مع خدمة الربط.

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

عندما يلغي آخر عميل الربط بالخدمة، يدمّر النظام الخدمة، ما لم يتم بدء الخدمة باستخدام startService().

إنّ أهم جزء في تنفيذ خدمة الربط هو تحديد الواجهة التي تعرضها طريقة رد الاتصال onBind(). يتناول القسم التالي عدة طرق يمكنك من خلالها تحديد واجهة IBinder الخاصة بخدمتك.

إنشاء خدمة ربط

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

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

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

استخدام Messenger
إذا كنت بحاجة إلى أن تعمل واجهتك في عمليات مختلفة، يمكنك إنشاء واجهة للخدمة باستخدام Messenger. بهذه الطريقة، تحدّد الخدمة Handler يستجيب لأنواع مختلفة من عناصر Message.

هذا Handler هو الأساس لـ Messenger يمكنه بعد ذلك مشاركة IBinder مع العميل، ما يتيح للعميل إرسال الأوامر إلى الخدمة باستخدام عناصر Message. بالإضافة إلى ذلك، يمكن للعميل تحديد Messenger خاص به، حتى تتمكّن الخدمة من إرسال الرسائل مرة أخرى.

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

استخدام AIDL
تعمل لغة تعريف واجهة نظام Android‏ (AIDL) على تحليل العناصر إلى عناصر أساسية يمكن لنظام التشغيل فهمها وتنظيمها عبر العمليات لتنفيذ الاتصال بين العمليات (IPC). تعتمد الطريقة السابقة التي تستخدم Messenger على AIDL كبنية أساسية لها.

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

لاستخدام AIDL مباشرةً، أنشئ ملف .aidl يحدّد واجهة البرمجة. تستخدم أدوات حزمة تطوير البرامج (SDK) لنظام التشغيل Android هذا الملف لإنشاء فئة مجردة تنفّذ الواجهة وتتعامل مع IPC، والتي يمكنك بعد ذلك توسيعها داخل خدمتك.

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

توسيع فئة Binder

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

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

لإعداد الأداة، يجب:

  1. في الخدمة، أنشئ مثيلاً من Binder ينفّذ أحد الإجراءات التالية:
    • يحتوي على طرق عامة يمكن للعميل استدعاؤها.
    • تعرض هذه الدالة مثيل Service الحالي الذي يتضمّن طرقًا عامة يمكن للعميل استدعاؤها.
    • تعرض مثيلاً لفئة أخرى تستضيفها الخدمة مع طرق عامة يمكن للعميل استدعاؤها.
  2. إرجاع مثيل Binder هذا من طريقة رد الاتصال onBind().
  3. في العميل، يمكنك تلقّي Binder من طريقة رد الاتصال onServiceConnected() وإجراء مكالمات مع خدمة الربط باستخدام الطرق المقدَّمة.

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

على سبيل المثال، إليك خدمة تتيح للعملاء الوصول إلى الطرق في الخدمة من خلال تنفيذ Binder:

Kotlin

class LocalService : Service() {
    // Binder given to clients.
    private val binder = LocalBinder()

    // Random number generator.
    private val mGenerator = Random()

    /** Method for clients.  */
    val randomNumber: Int
        get() = mGenerator.nextInt(100)

    /**
     * Class used for the client Binder. Because we know this service always
     * runs in the same process as its clients, we don't need to deal with IPC.
     */
    inner class LocalBinder : Binder() {
        // Return this instance of LocalService so clients can call public methods.
        fun getService(): LocalService = this@LocalService
    }

    override fun onBind(intent: Intent): IBinder {
        return binder
    }
}

Java

public class LocalService extends Service {
    // Binder given to clients.
    private final IBinder binder = new LocalBinder();
    // Random number generator.
    private final Random mGenerator = new Random();

    /**
     * Class used for the client Binder.  Because we know this service always
     * runs in the same process as its clients, we don't need to deal with IPC.
     */
    public class LocalBinder extends Binder {
        LocalService getService() {
            // Return this instance of LocalService so clients can call public methods.
            return LocalService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    /** Method for clients. */
    public int getRandomNumber() {
      return mGenerator.nextInt(100);
    }
}

توفّر السمة LocalBinder الطريقة getService() للعملاء لاسترداد النسخة الحالية من LocalService. يتيح ذلك للعملاء استدعاء الطرق العامة في الخدمة. على سبيل المثال، يمكن للعملاء الاتصال بالرقم getRandomNumber() من الخدمة.

في ما يلي نشاط يرتبط بـ LocalService ويستدعي getRandomNumber() عند النقر على زر:

Kotlin

class BindingActivity : Activity() {
    private var mService: LocalService? = null
    private var mBound: Boolean = false

    /** Defines callbacks for service binding, passed to bindService().  */
    private val connection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance.
            val binder = service as LocalService.LocalBinder
            mService = binder.getService()
            mBound = true
        }

        override fun onServiceDisconnected(arg0: ComponentName) {
            mService = null
            mBound = false
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)
    }

    override fun onStart() {
        super.onStart()
        // Bind to LocalService.
        Intent(this, LocalService::class.java).also { intent ->
            bindService(intent, connection, Context.BIND_AUTO_CREATE)
        }
    }

    override fun onStop() {
        super.onStop()
        unbindService(connection)
        // Release the reference so the service can be garbage-collected once it's
        // destroyed. onServiceDisconnected() isn't called when the client unbinds.
        mService = null
        mBound = false
    }

    /** Called when a button is clicked (the button in the layout file attaches to
     * this method with the android:onClick attribute).  */
    fun onButtonClick(v: View) {
        if (mBound) {
            // Call a method from the LocalService.
            // However, if this call is something that might hang, then put this request
            // in a separate thread to avoid slowing down the activity performance.
            val num: Int = mService?.randomNumber ?: return
            Toast.makeText(this, "number: $num", Toast.LENGTH_SHORT).show()
        }
    }
}

Java

public class BindingActivity extends Activity {
    LocalService mService;
    boolean mBound = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // Bind to LocalService.
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        unbindService(connection);
        // Release the reference so the service can be garbage-collected once it's
        // destroyed. onServiceDisconnected() isn't called when the client unbinds.
        mService = null;
        mBound = false;
    }

    /** Called when a button is clicked (the button in the layout file attaches to
      * this method with the android:onClick attribute). */
    public void onButtonClick(View v) {
        if (mBound) {
            // Call a method from the LocalService.
            // However, if this call is something that might hang, then put this request
            // in a separate thread to avoid slowing down the activity performance.
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        }
    }

    /** Defines callbacks for service binding, passed to bindService(). */
    private ServiceConnection connection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance.
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mService = null;
            mBound = false;
        }
    };
}

يوضّح النموذج السابق كيفية ربط العميل بالخدمة باستخدام تنفيذ ServiceConnection ودالة الاستدعاء onServiceConnected(). يوفّر القسم التالي مزيدًا من المعلومات حول عملية الربط بالخدمة.

ملاحظة: في المثال السابق، تلغي الطريقة onStop() ربط العميل بالخدمة وتُزيل مرجع العميل إليها. Unbind clients from services at appropriate times, as discussed in the Additional notes section.

لمزيد من الرموز النموذجية، يمكنك الاطّلاع على الفئة LocalService.java والفئة LocalServiceActivities.java في ApiDemos.

استخدام Messenger

إذا كنت بحاجة إلى أن تتواصل خدمتك مع العمليات عن بُعد، يمكنك استخدام Messenger لتوفير واجهة لخدمتك. تتيح لك هذه التقنية إجراء التواصل البيني للعمليات (IPC) بدون الحاجة إلى استخدام AIDL.

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

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

في ما يلي ملخّص لكيفية استخدام Messenger:

  1. تنفّذ الخدمة Handler يتلقّى معاودة اتصال لكل مكالمة من عميل.
  2. تستخدم الخدمة Handler لإنشاء عنصر Messenger (وهو مرجع إلى Handler).
  3. ينشئ Messenger IBinder تعيده الخدمة إلى العملاء من onBind().
  4. يستخدم العملاء IBinder لإنشاء مثيل Messenger (الذي يشير إلى Handler للخدمة)، والذي يستخدمه العميل لإرسال كائنات Message إلى الخدمة.
  5. تتلقّى الخدمة كل Message في Handler، وتحديدًا في طريقة handleMessage().

بهذه الطريقة، لن تتوفّر أي طرق للعميل لاستدعاء الخدمة. بدلاً من ذلك، يرسل العميل رسائل (عناصر Message) تتلقّاها الخدمة في Handler.

في ما يلي مثال على خدمة تستخدم واجهة Messenger:

Kotlin

/** Command to the service to display a message.  */
private const val MSG_SAY_HELLO = 1

class MessengerService : Service() {

    /**
     * Target we publish for clients to send messages to IncomingHandler.
     */
    private lateinit var mMessenger: Messenger

    /**
     * Handler of incoming messages from clients.
     */
    internal class IncomingHandler(
            context: Context,
            private val applicationContext: Context = context.applicationContext
    ) : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MSG_SAY_HELLO ->
                    Toast.makeText(applicationContext, "hello!", Toast.LENGTH_SHORT).show()
                else -> super.handleMessage(msg)
            }
        }
    }

    /**
     * When binding to the service, we return an interface to our messenger
     * for sending messages to the service.
     */
    override fun onBind(intent: Intent): IBinder? {
        Toast.makeText(applicationContext, "binding", Toast.LENGTH_SHORT).show()
        mMessenger = Messenger(IncomingHandler(this))
        return mMessenger.binder
    }
}

Java

public class MessengerService extends Service {
    /**
     * Command to the service to display a message.
     */
    static final int MSG_SAY_HELLO = 1;

    /**
     * Handler of incoming messages from clients.
     */
    static class IncomingHandler extends Handler {
        private Context applicationContext;

        IncomingHandler(Context context) {
            applicationContext = context.getApplicationContext();
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SAY_HELLO:
                    Toast.makeText(applicationContext, "hello!", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    /**
     * Target we publish for clients to send messages to IncomingHandler.
     */
    Messenger mMessenger;

    /**
     * When binding to the service, we return an interface to our messenger
     * for sending messages to the service.
     */
    @Override
    public IBinder onBind(Intent intent) {
        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
        mMessenger = new Messenger(new IncomingHandler(this));
        return mMessenger.getBinder();
    }
}

تتلقّى الخدمة من خلال طريقة handleMessage() في Handler رسالة Message الواردة وتحدّد الإجراء الذي يجب اتّخاذه استنادًا إلى العضو what.

كل ما يحتاج العميل إلى فعله هو إنشاء Messenger استنادًا إلى IBinder الذي تعرضه الخدمة وإرسال رسالة باستخدام send(). على سبيل المثال، إليك نشاط يرتبط بالخدمة ويرسل رسالة MSG_SAY_HELLO إلى الخدمة:

Kotlin

class ActivityMessenger : Activity() {
    /** Messenger for communicating with the service.  */
    private var mService: Messenger? = null

    /** Flag indicating whether we have called bind on the service.  */
    private var bound: Boolean = false

    /**
     * Class for interacting with the main interface of the service.
     */
    private val mConnection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // This is called when the connection with the service has been
            // established, giving us the object we can use to
            // interact with the service.  We are communicating with the
            // service using a Messenger, so here we get a client-side
            // representation of that from the raw IBinder object.
            mService = Messenger(service)
            bound = true
        }

        override fun onServiceDisconnected(className: ComponentName) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected—that is, its process crashed.
            mService = null
            bound = false
        }
    }

    fun sayHello(v: View) {
        if (!bound) return
        // Create and send a message to the service, using a supported 'what' value.
        val msg: Message = Message.obtain(null, MSG_SAY_HELLO, 0, 0)
        try {
            mService?.send(msg)
        } catch (e: RemoteException) {
            e.printStackTrace()
        }

    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)
    }

    override fun onStart() {
        super.onStart()
        // Bind to the service.
        Intent(this, MessengerService::class.java).also { intent ->
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
        }
    }

    override fun onStop() {
        super.onStop()
        // Unbind from the service.
        if (bound) {
            unbindService(mConnection)
            mService = null
            bound = false
        }
    }
}

Java

public class ActivityMessenger extends Activity {
    /** Messenger for communicating with the service. */
    Messenger mService = null;

    /** Flag indicating whether we have called bind on the service. */
    boolean bound;

    /**
     * Class for interacting with the main interface of the service.
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            // This is called when the connection with the service has been
            // established, giving us the object we can use to
            // interact with the service.  We are communicating with the
            // service using a Messenger, so here we get a client-side
            // representation of that from the raw IBinder object.
            mService = new Messenger(service);
            bound = true;
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected—that is, its process crashed.
            mService = null;
            bound = false;
        }
    };

    public void sayHello(View v) {
        if (!bound) return;
        // Create and send a message to the service, using a supported 'what' value.
        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
        try {
            mService.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // Bind to the service.
        bindService(new Intent(this, MessengerService.class), mConnection,
            Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // Unbind from the service.
        if (bound) {
            unbindService(mConnection);
            mService = null;
            bound = false;
        }
    }
}

لا يوضّح هذا المثال كيف يمكن للخدمة الاستجابة للعميل. إذا أردت أن تستجيب الخدمة، عليك أيضًا إنشاء Messenger في العميل. عندما يتلقّى العميل طلب معاودة الاتصال onServiceConnected()، يرسل Message إلى الخدمة التي تتضمّن Messenger العميل في مَعلمة replyTo للطريقة send().

يمكنك الاطّلاع على مثال حول كيفية توفير ميزة المراسلة الثنائية الاتجاه في نموذجَي MessengerService.java (الخدمة) و MessengerServiceActivities.java (العميل).

الربط بخدمة

يمكن لمكوّنات التطبيق (العملاء) الربط بخدمة من خلال استدعاء bindService(). بعد ذلك، يستدعي نظام Android طريقة onBind() للخدمة، والتي تعرض IBinder للتفاعل مع الخدمة.

تكون عملية الربط غير متزامنة، ويتم عرض bindService() على الفور بدون عرض IBinder للعميل. لتلقّي IBinder، على العميل إنشاء مثيل من ServiceConnection وتمريره إلى bindService(). تتضمّن واجهة ServiceConnection طريقة رد اتصال يستدعيها النظام لتسليم IBinder.

ملاحظة: يمكن فقط للأنشطة والخدمات وأدوات توفير المحتوى الربط بخدمة، ولا يمكنك الربط بخدمة من جهاز استقبال بث.

للربط بخدمة من برنامج العميل، اتّبِع الخطوات التالية:

  1. تنفيذ ServiceConnection

    يجب أن يتجاوز التنفيذ طريقتَي رد اتصال:

    onServiceConnected()
    يستدعي النظام هذه الطريقة لتسليم IBinder الذي تم عرضه من خلال طريقة onBind() في الخدمة.
    onServiceDisconnected()
    يستدعي نظام Android هذه الطريقة عند فقدان الاتصال بالخدمة بشكل غير متوقع، مثل تعطُّل الخدمة أو إيقافها. لا يتم استدعاء هذه الطريقة عندما يلغي العميل الربط.
  2. استدعِ bindService()، مع تمرير تنفيذ ServiceConnection.

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

  3. عندما يستدعي النظام طريقة رد الاتصال onServiceConnected()، يمكنك البدء في إجراء مكالمات للخدمة باستخدام الطرق التي تحددها الواجهة.
  4. لإلغاء الربط بالخدمة، يُرجى الاتصال بالرقم ‎unbindService().

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

يربط المثال التالي العميل بالخدمة التي تم إنشاؤها سابقًا من خلال توسيع فئة Binder، لذا كل ما يحتاج إليه هو تحويل IBinder الذي تم عرضه إلى فئة LocalBinder وطلب مثيل LocalService:

Kotlin

var mService: LocalService? = null

val mConnection = object : ServiceConnection {
    // Called when the connection with the service is established.
    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        // Because we have bound to an explicit
        // service that is running in our own process, we can
        // cast its IBinder to a concrete class and directly access it.
        val binder = service as LocalService.LocalBinder
        mService = binder.getService()
        mBound = true
    }

    // Called when the connection with the service disconnects unexpectedly.
    override fun onServiceDisconnected(className: ComponentName) {
        Log.e(TAG, "onServiceDisconnected")
        mService = null
        mBound = false
    }
}

Java

LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established.
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Because we have bound to an explicit
        // service that is running in our own process, we can
        // cast its IBinder to a concrete class and directly access it.
        LocalBinder binder = (LocalBinder) service;
        mService = binder.getService();
        mBound = true;
    }

    // Called when the connection with the service disconnects unexpectedly.
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "onServiceDisconnected");
        mService = null;
        mBound = false;
    }
};

باستخدام ServiceConnection، يمكن للعميل الربط بخدمة من خلال تمريرها إلى bindService()، كما هو موضّح في المثال التالي:

Kotlin

Intent(this, LocalService::class.java).also { intent ->
    bindService(intent, connection, Context.BIND_AUTO_CREATE)
}

Java

Intent intent = new Intent(this, LocalService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
  • المَعلمة الأولى في bindService() هي Intent يحدّد صراحةً الخدمة التي سيتم الربط بها.

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

  • المَعلمة الثانية هي عنصر ServiceConnection.
  • المَعلمة الثالثة هي علامة تشير إلى خيارات الربط، وعادةً ما تكون BIND_AUTO_CREATE، لإنشاء الخدمة إذا لم تكن نشطة. القيم المحتملة الأخرى هي BIND_DEBUG_UNBIND أو BIND_NOT_FOREGROUND أو 0 في حال عدم توفّر أي قيمة.

ملاحظات إضافية

في ما يلي بعض الملاحظات المهمة حول الربط بخدمة:

  • يجب دائمًا اعتراض استثناءات DeadObjectException التي يتم طرحها عند انقطاع الاتصال. هذا هو الاستثناء الوحيد الذي تطرحه الطرق البعيدة.
  • يتم احتساب العناصر المرجعية على مستوى العمليات.
  • عادةً ما يتم إقران الربط وإلغاء الربط أثناء لحظات المطابقة بين بدء التشغيل والإيقاف في مراحل نشاط العميل، كما هو موضّح في الأمثلة التالية:
    • إذا كنت بحاجة إلى التفاعل مع الخدمة فقط عندما يكون نشاطك مرئيًا، يمكنك الربط أثناء onStart() وإلغاء الربط أثناء onStop().
    • إذا أردت أن يتلقّى نشاطك ردودًا حتى عندما يكون متوقفًا في الخلفية، يمكنك الربط أثناء onCreate() وإلغاء الربط أثناء onDestroy(). يُرجى العِلم أنّ هذا يعني أنّ نشاطك يحتاج إلى استخدام الخدمة طوال فترة تشغيلها، حتى في الخلفية، لذا عندما تكون الخدمة في عملية أخرى، فإنّك تزيد من وزن العملية ومن المرجّح أن ينهيها النظام.

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

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

للاطّلاع على المزيد من الرموز النموذجية التي توضّح كيفية الربط بخدمة، يمكنك الاطّلاع على فئة RemoteService.java في ApiDemos.

إدارة مراحل نشاط خدمة ربط

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

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

بالإضافة إلى ذلك، إذا تم بدء الخدمة وقبول الربط، فعندما يستدعي النظام طريقة onUnbind()، يمكنك اختياريًا عرض true إذا كنت تريد تلقّي مكالمة إلى onRebind() في المرة التالية التي يرتبط فيها العميل بالخدمة. onRebind() يعرض قيمة فارغة، ولكن لا يزال العميل يتلقّى IBinder في دالة الاستدعاء onServiceConnected(). يوضّح الشكل التالي منطق هذا النوع من مراحل النشاط.

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

لمزيد من المعلومات حول مراحل نشاط الخدمة التي تم تشغيلها، يُرجى الاطّلاع على نظرة عامة على الخدمات.