סקירה כללית של שירותים קשורים

שרת מאגד (bound service) הוא השרת בממשק לקוח-שרת. הוא מאפשר לרכיבים כמו פעילויות להתחבר לשירות, לשלוח בקשות, לקבל תשובות ולבצע תקשורת בין תהליכים (IPC). בדרך כלל, שירות מאגד פועל רק כשהוא משרת רכיב באפליקציה, והוא לא פועל ברקע ללא הגבלת זמן.

במאמר הזה מוסבר איך ליצור שירות מאוגד, כולל איך לאגד את השירות מרכיבים אחרים באפליקציה. ב סקירה הכללית על שירותים מוסבר איך להעביר התראות משירות ולהגדיר את השירות כך שיפעל בחזית.

העקרונות הבסיסיים

שירות מאוגד הוא הטמעה של המחלקה Service שמאפשרת לאפליקציות אחרות להתאגד איתו ולתקשר איתו. כדי לספק קישור לשירות, צריך להטמיע את שיטת הקריאה החוזרת onBind(). השיטה הזו מחזירה אובייקט IBinder שמגדיר את ממשק התכנות שבו הלקוחות יכולים להשתמש כדי ליצור אינטראקציה עם השירות.

קישור לשירות שהופעל

כמו שמוסבר בסקירה הכללית של השירותים, אתם יכולים ליצור שירות שהוא גם מופעל וגם מקושר. כלומר, אפשר להפעיל שירות על ידי קריאה ל-startService(), וכך השירות יכול לפעול ללא הגבלת זמן. אפשר גם לאפשר ללקוח להתחבר לשירות באמצעות קריאה ל-bindService().

אם מאפשרים לשירות להתחיל ולהיות מקושר, כשהשירות מתחיל, המערכת לא משמידה את השירות כשכל הלקוחות מבטלים את הקישור. במקום זאת, צריך לעצור את השירות באופן מפורש על ידי קריאה ל-stopSelf() או ל-stopService().

בדרך כלל מטמיעים את onBind() או את onStartCommand(), אבל לפעמים צריך להטמיע את שניהם. לדוגמה, בנגן מוזיקה יכול להיות שימושי לאפשר לשירות לפעול ללא הגבלת זמן וגם לספק קישור. כך, פעילות יכולה להפעיל את השירות כדי להשמיע מוזיקה, והמוזיקה ממשיכה לפעול גם אם המשתמש יוצא מהאפליקציה. לאחר מכן, כשהמשתמש חוזר לאפליקציה, הפעילות יכולה להתחבר לשירות כדי לקבל שוב שליטה על ההפעלה.

מידע נוסף על מחזור החיים של השירות כשמוסיפים קישור לשירות שהופעל זמין בקטע ניהול מחזור החיים של שירות מאוגד.

לקוח מתחבר לשירות באמצעות קריאה ל-bindService(). כשהיא עושה זאת, היא צריכה לספק הטמעה של ServiceConnection, שעוקב אחרי החיבור לשירות. ערך ההחזרה של bindService() מציין אם השירות המבוקש קיים ואם ללקוח יש הרשאת גישה אליו.

כשהמערכת של Android יוצרת את החיבור בין הלקוח לשירות, היא קוראת ל-onServiceConnected() ב-ServiceConnection. ה-method‏ onServiceConnected() כוללת ארגומנט IBinder, שבו הלקוח משתמש כדי לתקשר עם השרת המאגד.

אפשר לחבר כמה לקוחות לשירות בו-זמנית. עם זאת, המערכת שומרת במטמון את ערוץ התקשורת של השירות IBinder. במילים אחרות, המערכת קוראת ל-method‏ onBind() של השירות כדי ליצור את ה-IBinder רק כשהלקוח הראשון מבצע קישור. לאחר מכן המערכת מעבירה את אותו IBinder לכל הלקוחות הנוספים שמתחברים לאותו שירות, בלי להפעיל שוב את onBind().

כשהלקוח האחרון מבטל את הקישור לשירות, המערכת משמידה את השירות, אלא אם השירות הופעל באמצעות startService().

החלק הכי חשוב בהטמעה של שירות מאגד הוא להגדיר את הממשק ששיטת הקריאה החוזרת (callback) onBind() מחזירה. בקטע הבא מתוארות כמה דרכים להגדיר את הממשק של IBinder בשירות.

יצירת שירות מאוגד (bound service)

כשיוצרים שירות שמספק קישור, צריך לספק IBinder שמספק את ממשק התכנות שבו הלקוחות יכולים להשתמש כדי לקיים אינטראקציה עם השירות. יש שלוש דרכים להגדיר את הממשק:

הרחבת המחלקה Binder
אם השירות פרטי לאפליקציה שלכם ופועל באותו תהליך כמו הלקוח, וזה מצב נפוץ, אתם יכולים ליצור את הממשק על ידי הרחבה של המחלקה Binder והחזרת מופע שלה מ-onBind(). הלקוח מקבל את Binder ויכול להשתמש בו כדי לגשת ישירות לשיטות ציבוריות שזמינות בהטמעה של Binder או ב-Service.

זו השיטה המועדפת כשהשירות הוא רק קובץ שירות (service worker) ברקע של האפליקציה שלכם. המקרה היחיד שבו זו לא הדרך המועדפת ליצור את הממשק הוא אם אפליקציות אחרות או תהליכים נפרדים משתמשים בשירות.

שימוש באפליקציית מסנג'ר
אם אתם צריכים שהממשק יעבוד בתהליכים שונים, אתם יכולים ליצור ממשק לשירות באמצעות Messenger. כך השירות מגדיר Handler שמגיב לסוגים שונים של אובייקטים מסוג Message.

ה-Handler הזה הוא הבסיס ל-Messenger שיכול לשתף IBinder עם הלקוח, וכך לאפשר ללקוח לשלוח פקודות לשירות באמצעות אובייקטים של Message. בנוסף, הלקוח יכול להגדיר Messenger משלו, כדי שהשירות יוכל לשלוח הודעות בחזרה.

זו הדרך הפשוטה ביותר לבצע תקשורת בין תהליכים (IPC), כי Messenger מוסיף את כל הבקשות לתור ב-thread אחד, כך שלא צריך לתכנן את השירות כך שיהיה thread-safe.

שימוש ב-AIDL
שפת ההגדרה לבניית ממשק Android‏ (AIDL) מפרקת אובייקטים לטיפוסים בסיסיים שמערכת ההפעלה יכולה להבין, ומעבירה אותם בין תהליכים כדי לבצע IPC. השיטה הקודמת, שמשתמשת ב-Messenger, מבוססת למעשה על AIDL כמבנה הבסיסי שלה.

כמו שצוין בקטע הקודם, Messenger יוצר תור של כל בקשות הלקוח בשרשור יחיד, כך שהשירות מקבל בקשות אחת בכל פעם. אבל אם אתם רוצים שהשירות יטפל בכמה בקשות בו-זמנית, אתם יכולים להשתמש ב-AIDL ישירות. במקרה כזה, השירות צריך להיות thread-safe ובעל יכולת ריבוי הליכי משנה (multithreading).

כדי להשתמש ב-AIDL ישירות, צריך ליצור קובץ .aidl שמגדיר את ממשק התכנות. כלי ה-SDK של Android משתמשים בקובץ הזה כדי ליצור מחלקה מופשטת שמטמיעה את הממשק ומטפלת ב-IPC, ואז אתם יכולים להרחיב אותה בתוך השירות שלכם.

הערה: ברוב האפליקציות, AIDL היא לא האפשרות הכי טובה ליצירת שירות מאוגד, כי יכול להיות שהיא תדרוש יכולות של ריבוי תהליכים (multithreading) ותוביל להטמעה מסובכת יותר. לכן, במאמר הזה לא מוסבר איך להשתמש בו בשירות שלכם. אם אתם בטוחים שאתם צריכים להשתמש ב-AIDL ישירות, עיינו במסמך AIDL.

הרחבת המחלקה Binder

אם רק האפליקציה המקומית משתמשת בשירות שלכם והיא לא צריכה לפעול בין תהליכים, אתם יכולים להטמיע מחלקת Binder משלכם שמספקת ללקוח גישה ישירה לשיטות ציבוריות בשירות.

הערה: זה עובד רק אם הלקוח והשירות נמצאים באותה אפליקציה ובאותו תהליך, וזה המצב הנפוץ ביותר. לדוגמה, זה פועל היטב באפליקציית מוזיקה שצריכה לקשר פעילות לשירות שלה שמפעיל מוזיקה ברקע.

כך מגדירים את זה:

  1. בשירות, יוצרים מופע של Binder שמבצע אחת מהפעולות הבאות:
    • מכיל שיטות ציבוריות שהלקוח יכול להפעיל.
    • הפונקציה מחזירה את המופע הנוכחי של Service, שכולל שיטות ציבוריות שהלקוח יכול להפעיל.
    • מחזירה מופע של מחלקה אחרת שמתארחת בשירות, עם שיטות ציבוריות שהלקוח יכול לקרוא להן.
  2. מחזירים את המופע הזה של Binder משיטת הקריאה החוזרת onBind().
  3. בצד הלקוח, מקבלים את Binder משיטת הקריאה החוזרת (callback) של onServiceConnected() ומבצעים קריאות לשרת המאגד באמצעות השיטות שסופקו.

הערה: השירות והלקוח חייבים להיות באותה אפליקציה כדי שהלקוח יוכל להמיר את האובייקט שהוחזר ולקרוא לממשקי ה-API שלו בצורה תקינה. השירות והלקוח צריכים להיות באותו תהליך, כי השיטה הזו לא מבצעת מרשלינג בין תהליכים.

לדוגמה, הנה שירות שמספק ללקוחות גישה לשיטות בשירות באמצעות הטמעה של 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. כך הלקוחות יכולים לקרוא ל-methods ציבוריות בשירות. לדוגמה, לקוחות יכולים להתקשר אל 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 והקריאה החוזרת (callback) של onServiceConnected(). בקטע הבא מוסבר בהרחבה על תהליך הקישור לשירות.

הערה: בדוגמה הקודמת, ה-method‏ onStop() מבטלת את הקישור של הלקוח לשירות ומשחררת את ההפניה של הלקוח אליו. לבטל את הקישור של לקוחות לשירותים בזמנים המתאימים, כפי שמתואר בקטע הערות נוספות.

כדי לראות עוד קוד לדוגמה, עיינו במחלקה LocalService.java ובמחלקה LocalServiceActivities.java ב-ApiDemos.

שימוש בשליח

אם אתם צריכים שהשירות שלכם יתקשר עם תהליכים מרוחקים, אתם יכולים להשתמש ב-Messenger כדי לספק את הממשק לשירות. השיטה הזו מאפשרת לבצע תקשורת בין תהליכים (IPC) בלי להשתמש ב-AIDL.

שימוש ב-Messenger לממשק פשוט יותר משימוש ב-AIDL, כי Messenger מוסיף את כל הקריאות לשירות לתור. ממשק AIDL טהור שולח בקשות בו-זמנית לשירות, שצריך לטפל בריבוי הליכי משנה.

ברוב האפליקציות, השירות לא צריך לבצע ריבוי תהליכים, כך שהשימוש ב-Messenger מאפשר לשירות לטפל בקריאה אחת בכל פעם. אם חשוב שהשירות יהיה מרובה-תהליכים, צריך להשתמש ב-AIDL כדי להגדיר את הממשק.

כך משתמשים בMessenger:

  1. השירות מטמיע Handler שמקבל קריאה חוזרת (callback) לכל קריאה מלקוח.
  2. השירות משתמש ב-Handler כדי ליצור אובייקט Messenger (שהוא הפניה ל-Handler).
  3. ה-Messenger יוצר IBinder שהשירות מחזיר ללקוחות מ-onBind().
  4. הלקוחות משתמשים ב-IBinder כדי ליצור מופע של Messenger (שמפנה ל-Handler של השירות), שבו הלקוח משתמש כדי לשלוח אובייקטים של Message לשירות.
  5. השירות מקבל כל Message ב-Handler שלו – באופן ספציפי, ב-method‏ 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();
    }
}

ה-method‏ 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 בלקוח. כשהלקוח מקבל את הקריאה החוזרת (callback) של onServiceConnected(), הוא שולח Message לשירות שכולל את ה-Messenger של הלקוח בפרמטר replyTo של השיטה send().

אפשר לראות דוגמה להעברת הודעות דו-כיוונית בדוגמאות של MessengerService.java (שירות) ו- MessengerServiceActivities.java (לקוח).

קישור לשירות

רכיבים באפליקציה (לקוחות) יכולים להתחבר לשירות באמצעות קריאה ל-bindService(). לאחר מכן, מערכת Android מפעילה את ה-method‏ onBind() של השירות, שמחזירה IBinder לאינטראקציה עם השירות.

הקישור הוא אסינכרוני, ו-bindService() מחזיר מיד את ל לקוח בלי להחזיר את IBinder. כדי לקבל את ה-IBinder, הלקוח צריך ליצור מופע של ServiceConnection ולהעביר אותו אל bindService(). ה-ServiceConnection כולל שיטת קריאה חוזרת שהמערכת קוראת לה כדי להעביר את ה-IBinder.

הערה: רק פעילויות, שירותים וספקים לניהול גישה יכולים להתחבר לשירות. אי אפשר להתחבר לשירות ממקלט שידור.

כדי לקשר לשירות מהלקוח, פועלים לפי השלבים הבאים:

  1. מטמיעים את ServiceConnection.

    ההטמעה צריכה לשנות שתי שיטות קריאה חוזרת:

    onServiceConnected()
    המערכת קוראת ל-method הזה כדי להעביר את ה-IBinder שמוחזר על ידי ה-method‏ 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 שמציין במפורש את השירות שצריך לקשר.

    זהירות: אם משתמשים ב-Intent כדי לקשר ל-Service, צריך לוודא שהאפליקציה מאובטחת באמצעות Intent מפורש. שימוש ב-intent מרומז כדי להפעיל שירות הוא סיכון אבטחה, כי אי אפשר לדעת איזה שירות יגיב ל-intent, והמשתמש לא יכול לראות איזה שירות מופעל. החל מ-Android 5.0 (רמת API‏ 21), המערכת מקפיצה חריגה אם קוראים ל-bindService() עם Intent משתמע.

  • הפרמטר השני הוא האובייקט ServiceConnection.
  • הפרמטר השלישי הוא דגל שמציין אפשרויות לקישור – בדרך כלל BIND_AUTO_CREATE, כדי ליצור את השירות אם הוא עדיין לא פעיל. ערכים אפשריים אחרים הם BIND_DEBUG_UNBIND,‏ BIND_NOT_FOREGROUND או 0 אם אין ערך.

הערות נוספות

הנה כמה הערות חשובות לגבי קישור לשירות:

  • תמיד צריך ללכוד חריגות מסוג DeadObjectException, שנזרקות כשהחיבור נכשל. זהו החריג היחיד שנזרק על ידי שיטות מרוחקות.
  • האובייקטים נספרים כהפניות בתהליכים שונים.
  • בדרך כלל, אתם משייכים את הקישור והביטול של הקישור במהלך רגעי ההתאמה של ההפעלה וההשבתה של מחזור החיים של הלקוח, כפי שמתואר בדוגמאות הבאות:
    • אם אתם צריכים ליצור אינטראקציה עם השירות רק כשהפעילות שלכם גלויה, צריך לבצע קישור במהלך onStart() וביטול קישור במהלך onStop().
    • אם רוצים שהפעילות תקבל תגובות גם כשהיא נעצרת ברקע, צריך לבצע קישור במהלך onCreate() וביטול קישור במהלך onDestroy(). חשוב לזכור שהמשמעות היא שהפעילות צריכה להשתמש בשירות כל הזמן שהיא פועלת, גם ברקע. לכן, כשהשירות נמצא בתהליך אחר, אתם מגדילים את משקל התהליך ויש סיכוי גבוה יותר שהמערכת תפסיק אותו.

    הערה: בדרך כלל לא מבצעים קישור וביטול קישור במהלך הקריאות החוזרות (callbacks) של onResume() ו-onPause() של הפעילות, כי הקריאות החוזרות האלה מתרחשות בכל מעבר במחזור החיים. חשוב לצמצם ככל האפשר את העיבוד שמתרחש במעברים האלה.

    בנוסף, אם כמה פעילויות באפליקציה מקושרות לאותו שירות ויש מעבר בין שתיים מהפעילויות האלה, יכול להיות שהשירות יושמד וייווצר מחדש כשהפעילות הנוכחית תנותק (במהלך ההשהיה) לפני שהפעילות הבאה תקושר (במהלך ההמשך). המעבר הזה בין פעילויות, שמתאר איך הפעילויות מתאמות את מחזורי החיים שלהן, מתואר במאמר מחזור החיים של פעילות.

לדוגמאות נוספות של קוד לדוגמה שמראות איך לבצע קישור לשירות, אפשר לעיין במחלקה RemoteService.java ב-ApiDemos.

ניהול מחזור החיים של שירות מאוגד (bound service)

כשהשירות לא מאוגד לאף לקוח, מערכת Android משמידה אותו (אלא אם הוא הופעל באמצעות startService()). לכן, אם השירות הוא שירות מאוגד בלבד, אתם לא צריכים לנהל את מחזור החיים שלו. מערכת Android מנהלת אותו בשבילכם על סמך השאלה אם הוא קשור ללקוחות כלשהם.

עם זאת, אם אתם בוחרים להטמיע את שיטת הקריאה החוזרת (callback) onStartCommand(), אתם צריכים לעצור את השירות באופן יזום, כי עכשיו השירות נחשב מופעל. במקרה כזה, השירות פועל עד שהוא מפסיק את עצמו באמצעות stopSelf() או עד שרכיב אחר קורא ל-stopService(), גם אם הוא לא מקושר ללקוחות כלשהם.

בנוסף, אם השירות שלכם מופעל ומקבל קישור, כשהמערכת מפעילה את השיטה onUnbind(), אתם יכולים להחזיר את הערך true אם אתם רוצים לקבל קריאה ל-onRebind() בפעם הבאה שלקוח מקשר לשירות. ‫onRebind() מחזירה void, אבל הלקוח עדיין מקבל את IBinder בקריאה החוזרת (callback) של onServiceConnected(). התרשים הבא ממחיש את הלוגיקה של מחזור חיים מהסוג הזה.

מחזור החיים של שירות מופעל ושרת מאגד (bound service): מתבצעת קריאה ל-onStartCommand()‎ ול-onBind()‎. כשכל הלקוחות מבטלים את האיגוד, מתבצעת קריאה ל-onUnbind()‎. באיגוד הבא, מתבצעת קריאה ל-onRebind()‎ אם onUnbind()‎ החזיר ערך true, אחרת מתבצעת קריאה ל-onBind()‎. אחרי שהשירות נעצר, מתבצעת קריאה ל-onDestroy()‎.
איור 1. מחזור החיים של שירות שמופעל ומאפשר גם קישור.

מידע נוסף על מחזור החיים של שירות שהופעל זמין בסקירה הכללית על שירותים.