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

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

זהירות: שירות פועל ב-thread הראשי של האירוח שלו תהליך; השירות לא יוצר שרשור משלו ולא פועל בתהליך נפרד, אלא אם תציינו אחרת. עליך להריץ כל פעולות חסימה על ליצור שרשור נפרד בתוך השירות כדי להימנע מהתכונה 'אפליקציה' שגיאות מסוג 'לא מגיב' (ANR).

סוגי השירותים

אלה הם שלושת סוגי השירותים השונים:

חזית

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

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

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

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

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

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

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

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

לא משנה אם השירות מופעל, קשור או שניהם, רכיב כלשהו באפליקציה יכול להשתמש בשירות (גם מאפליקציה נפרדת) באותו אופן שבו כל רכיב יכול להשתמש פעילות – על ידי התחלתה ב-Intent. עם זאת, אפשר להצהיר את השירות בסטטוס פרטי בקובץ המניפסט וחוסמים את הגישה מאפליקציות אחרות. מידע נוסף מופיע בקטע הזה בקטע הצהרה על השירות מניפסט.

בחירה בין שירות לשרשור

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

אם אתם צריכים לבצע עבודה מחוץ ל-thread הראשי, אבל רק בזמן שהמשתמש יוצר אינטראקציה באפליקציה שלכם, עליכם ליצור במקום זאת שרשור חדש בהקשר של אפליקציה אחרת לרכיב הזה. לדוגמה, אם אתם רוצים להשמיע מוזיקה אבל רק כשהפעילות מתבצעת, כדאי ליצור שרשור ב-onCreate(), מתחילים להריץ אותו ב-onStart(), ולהפסיק אותה ב-onStop(). כדאי גם להשתמש במאגרי שרשורים ובאדמינים מהחבילה java.util.concurrent או קורוטינים של קוטלין במקום כיתה אחת (Thread). לצפייה במסמך שרשור ב-Android אפשר לקרוא מידע נוסף על העברת הביצוע לשרשורים ברקע.

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

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

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

onStartCommand()
המערכת מפעילה את השיטה הזו על ידי קריאה ל-startService() כשרכיב אחר (כמו פעילות) מבקש להפעיל את השירות. כשהשיטה הזו מופעלת, השירות מתחיל ויכול לפעול ללא הגבלת זמן. אם תטמיעו את המזהה הזה, באחריותכם להפסיק את השירות כאשר פעולתו תושלם באמצעות קריאה אל stopSelf() או stopService(). אם אתם רוצים רק לספק קישור, אין צורך צריך ליישם את השיטה הזו.
onBind()
המערכת מפעילה את השיטה הזו על ידי קריאה ל-bindService() כשרכיב אחר רוצה להתחבר לשירות (למשל לבצע RPC). בהטמעה של השיטה הזו, עליכם לספק ממשק שלקוחות משמש לתקשורת עם השירות באמצעות החזרת IBinder. חובה תמיד להטמיע את השיטה הזו. עם זאת, אם לא רוצים לאפשר את הקישור, צריך להחזיר null.
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 שאי אפשר לשנות זאת).

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

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

הערה: המשתמשים יכולים לראות אילו שירותים פועלים במכשיר שלהם. אם הם רואים שירות שהם לא מזהים או לא סומכים עליו, הם יכולים להפסיק את השירות. לחשבון כדי למנוע ממשתמשים להפסיק את השירות בטעות, כדי להוסיף את android:description ל <service> בקובץ המניפסט של האפליקציה. בתיאור, לספק משפט קצר שמסביר מה השירות עושה ואילו יתרונות שהוא מספק.

יצירת שירות שכבר התחיל

שירות שהופעל הוא שירות שרכיב אחר מתחיל בקריאה ל-startService(), וכתוצאה מכך מתבצעת קריאה של השירות onStartCommand().

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

רכיב אפליקציה כמו פעילות יכול להפעיל את השירות על ידי קריאה ל-startService() והעברת Intent שמציין את השירות וכולל את כל הנתונים שבהם השירות ישתמש. השירות מקבל Intent הזה בשיטה onStartCommand().

למשל, נניח שפעילות מסוימת צריכה לשמור נתונים במסד נתונים באינטרנט. הפעילות יכול להפעיל שירות נלווה ולספק לו את הנתונים כדי לחסוך באמצעות העברת כוונה אל startService(). השירות מקבל את ה-Intent ב-onStartCommand(), מתחבר לאינטרנט ומבצע את עסקה במסד הנתונים. כשהעסקה מסתיימת, השירות עוצר את עצמו הושמד.

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

המחלקה Service היא הבסיס לכל השירותים. כשמרחיבים את הכיתה, חשוב ליצור שרשור חדש שבו השירות יכול להשלים את כל העבודה שלו; השירות משתמש ב-thread הראשי של האפליקציה על ידי ברירת המחדל, שעלולה להאט את הביצועים של כל פעילות שהאפליקציה פועלת.

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

לפרטים נוספים על ערכי ההחזרה האלה, עיינו בחומר העזר המקושר של כל ערך קבוע.

הפעלת שירות

אפשר להפעיל שירות מפעילות או מרכיב אחר של אפליקציה על ידי העברת Intent אל startService() או startForegroundService(). מערכת Android מפעילה את ה-method onStartCommand() של השירות ומעבירה את Intent, שמציין איזה שירות להפעיל.

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

לדוגמה, פעילות יכולה להפעיל את השירות לדוגמה בקטע הקודם (HelloService) באמצעות Intent מפורש עם startService(), כמו שמוצג כאן:

Kotlin

startService(Intent(this, HelloService::class.java))

Java

startService(new Intent(this, HelloService.class));

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

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

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

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

שליחת התראות למשתמש

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

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

בדרך כלל, התראה משורת הסטטוס היא השיטה הטובה ביותר לשימוש בעבודה ברקע, כמו הורדה של קובץ הסתיימה והמשתמש יכול לבצע פעולה לגבי הקובץ. כשהמשתמש בוחר את ההתראה מהתצוגה המורחבת, ההתראה יכולה להתחיל פעילות (למשל, כדי להציג את הקובץ שהורד).

ניהול מחזור החיים של שירות

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

מחזור החיים של השירות – מרגע יצירתו ועד למועד הריסו אותו – יכול לעקוב אחר בכל אחד משני הנתיבים האלה:

  • שירות שהתחיל

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

  • שירות קשור

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

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

הטמעת הקריאות החוזרות (callback) במחזור החיים

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

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
    }
}

הערה: בשונה משיטות הקריאה החוזרת (callback) של מחזור החיים של הפעילות, לא נדרש לבצע קריאה להטמעה של מחלקה-על של שיטות הקריאה החוזרת האלה.

איור 2. מחזור החיים של השירות. התרשים משמאל מציג את מחזור החיים כשהשירות נוצר באמצעות startService(), ודיאגרמה בצד ימין מציגה את מחזור החיים לאחר יצירת השירות עם bindService().

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

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

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

    הערה: onCreate() וה-methods onDestroy() נקראות לכל השירותים, בין אם הם נוצרו על ידי startService() או bindService().

  • משך החיים הפעיל של שירות מתחיל בקריאה ל-onStartCommand() או ל-onBind(). לכל שיטה מועברת השיטה Intent שהועברה אל startService() או אל bindService().

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

הערה: למרות ששירות שהופעל הופסק על ידי שיחה אל או stopSelf() או stopService(), אין קריאה חוזרת בהתאמה עבור השירות (אין התקשרות חזרה של onStop()). אלא אם השירות קשור ללקוח, המערכת משמידת אותו כשהשירות מופסק – onDestroy() הוא הקריאה החוזרת היחידה שמתקבלת.

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