Service
הוא רכיב של אפליקציה שיכול לבצע פעולות ממושכות ברקע. אין לו ממשק משתמש. אחרי שמפעילים שירות, הוא עשוי להמשיך לפעול במשך זמן מה, גם אחרי שהמשתמש עובר לאפליקציה אחרת. בנוסף, רכיב יכול להתחבר לשירות כדי ליצור איתו אינטראקציה ואפילו לבצע תקשורת בין תהליכים (IPC). לדוגמה, שירות יכול לטפל בעסקאות ברשת, להשמיע מוזיקה, לבצע קלט/פלט של קבצים או לקיים אינטראקציה עם ספק תוכן, והכול ברקע.
זהירות: שירות פועל ב-thread הראשי של תהליך האירוח שלו. השירות לא יוצר thread משלו ולא פועל בתהליך נפרד, אלא אם ציינתם אחרת. כדי למנוע שגיאות מסוג Application Not Responding (ANR), צריך להריץ פעולות חסימה בשרשור נפרד בתוך השירות.
סוגי השירותים
אלה שלושת הסוגים השונים של שירותים:
- חזית
-
שירות שפועל בחזית מבצע פעולה כלשהי שגלומה למשתמש. לדוגמה, אפליקציית אודיו תשתמש בשירות שפועל בחזית כדי להפעיל טראק אודיו. שירותים שפועלים בחזית חייבים להציג התראה. שירותים שפועלים בחזית ממשיכים לפעול גם כשהמשתמש לא מבצע אינטראקציה עם האפליקציה.
כשמשתמשים בשירות שפועל בחזית, צריך להציג התראה כדי שהמשתמשים ידעו שהשירות פועל. אי אפשר לסגור את ההתראה הזו אלא אם השירות מושבת או מוסר מהחזית.
מידע נוסף על הגדרת שירותי חזית באפליקציה
הערה: ממשק ה-API של WorkManager מספק דרך גמישה לתזמון משימות, ויכול להריץ את המשימות האלה כשירותים שפועלים בחזית במקרה הצורך. במקרים רבים, עדיף להשתמש ב-WorkManager מאשר להשתמש ישירות בשירותים שפועלים בחזית.
- רקע
- שירות ברקע מבצע פעולה שהמשתמש לא מבחין בה ישירות. לדוגמה, אם אפליקציה משתמשת בשירות כדי לדחוס את האחסון שלה, בדרך כלל מדובר בשירות רקע.
הערה: אם האפליקציה שלכם מטרגטת לרמת API 26 ומעלה, המערכת מטילה הגבלות על הפעלת שירותי רקע כשהאפליקציה עצמה לא בחזית. לדוגמה, ברוב המקרים אסור לגשת למידע על המיקום ברקע. במקום זאת, תזמנו משימות באמצעות WorkManager.
- כבול
- שירות מקושר כשרכיב אפליקציה מתחבר אליו באמצעות קריאה ל-
bindService()
. שירות מקושר מציע ממשק לקוח-שרת שמאפשר לרכיבים לקיים אינטראקציה עם השירות, לשלוח בקשות, לקבל תוצאות ואפילו לעשות זאת בין תהליכים באמצעות תקשורת בין תהליכים (IPC). שירות מקושר פועל רק כל עוד רכיב אחר של האפליקציה מקושר אליו. כמה רכיבים יכולים לקשר לשירות בו-זמנית, אבל כשכל הקישור שלהם יבוטל, השירות ייהרס.
במסמכים האלה אנחנו מדברים בדרך כלל על שירותים מופעלים ומקושרים בנפרד, אבל השירות יכול לפעול בשתי הדרכים – הוא יכול להיות מופעל (כדי לפעול ללא הגבלת זמן) וגם לאפשר קישור. העניין הוא רק אם מטמיעים כמה שיטות קריאה חוזרת (callback): onStartCommand()
כדי לאפשר לרכיבים להפעיל אותה ו-onBind()
כדי לאפשר קישור.
לא משנה אם השירות מופעל, מקושר או שניהם, כל רכיב באפליקציה יכול להשתמש בשירות (גם מאפליקציה נפרדת) באותו אופן שבו כל רכיב יכול להשתמש בפעילות – על ידי הפעלתו באמצעות Intent
. עם זאת, אפשר להצהיר על השירות כפרטי בקובץ המניפסט ולחסום את הגישה מאפליקציות אחרות.
מידע נוסף מופיע בקטע הצהרה על השירות במניפסט.
בחירה בין שירות לשרשור
שירות הוא פשוט רכיב שיכול לפעול ברקע, גם כשהמשתמש לא מבצע פעולות באפליקציה. לכן, כדאי ליצור שירות רק אם אתם זקוקים לכך.
אם אתם צריכים לבצע עבודה מחוץ לשרשור הראשי, אבל רק בזמן שהמשתמש מבצע פעולות באפליקציה, עדיף ליצור שרשור חדש בהקשר של רכיב אחר באפליקציה. לדוגמה, אם אתם רוצים להשמיע מוזיקה, אבל רק בזמן שהפעילות שלכם פועלת, תוכלו ליצור שרשור ב-onCreate()
, להתחיל להריץ אותו ב-onStart()
ולהפסיק אותו ב-onStop()
.
מומלץ גם להשתמש במאגרי חוטים ובמפעילים מחבילת java.util.concurrent
או בקורוטינים של Kotlin במקום בכיתה Thread
המסורתית. למידע נוסף על העברת הביצועים לשרשראות רקע, אפשר לעיין במסמך שרשראות ב-Android.
חשוב לזכור שאם אתם משתמשים בשירות, הוא עדיין פועל בשרשור הראשי של האפליקציה כברירת מחדל, ולכן עדיין צריך ליצור שרשור חדש בתוך השירות אם הוא מבצע פעולות אינטנסיביות או חסימות.
העקרונות הבסיסיים
כדי ליצור שירות, צריך ליצור קבוצת משנה של Service
או להשתמש באחת מקבוצות המשנה הקיימות שלו. בהטמעה, צריך לשנות את הגדרת ברירת המחדל של כמה שיטות קריאה חוזרת (callback) שמטפלות בהיבטים מרכזיים של מחזור החיים של השירות, ולספק מנגנון שמאפשר לרכיבים להתחבר לשירות, אם הדבר רלוונטי. אלה שיטות ה-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
הוא המאפיין היחיד הנדרש – הוא מציין את שם הכיתה של השירות. אחרי שפרסמת את האפליקציה, אל תשנו את השם הזה כדי למנוע סיכון לשבירת הקוד בגלל התלות בכוונות מפורשות להפעלה או לקישור של השירות (מומלץ לקרוא את הפוסט בבלוג דברים שלא ניתן לשנות).
זהירות: כדי להבטיח שהאפליקציה מאובטחת, תמיד צריך להשתמש בכוונה מפורשת כשמפעילים Service
ולא להצהיר על מסנני כוונה לשירותים. שימוש בכוונה משתמעת כדי להפעיל שירות הוא סיכון אבטחה, כי אי אפשר להיות בטוחים לגבי השירות שתגיב לכוונה, והמשתמש לא יכול לראות איזה שירות מופעל. החל מגרסה Android 5.0 (רמת API 21), המערכת תיצור חריגה אם תפעילו את bindService()
עם כוונה משתמעת.
כדי לוודא שהשירות יהיה זמין רק לאפליקציה שלכם, תוכלו לכלול את המאפיין android:exported
ולהגדיר אותו לערך false
. כך אפשר למנוע מאפליקציות אחרות להפעיל את השירות, גם אם הן משתמשות ב-Intent מפורש.
הערה:
המשתמשים יכולים לראות אילו שירותים פועלים במכשיר שלהם. אם הם רואים שירות שהם לא מזהים או לא סומכים עליו, הם יכולים להפסיק את השירות. כדי למנוע מהמשתמשים להפסיק בטעות את השירות, צריך להוסיף את המאפיין android:description
לאלמנט <service>
בקובץ המניפסט של האפליקציה. בתיאור, כדאי להוסיף משפט קצר שמסביר מה השירות עושה ואילו יתרונות הוא מספק.
יצירת שירות מופעל
שירות שהופעל הוא שירות שרכיב אחר מפעיל באמצעות קריאה ל-startService()
, וכתוצאה מכך מתבצעת קריאה לשיטה onStartCommand()
של השירות.
כשמפעילים שירות, יש לו מחזור חיים שהוא בלתי תלוי ברכיב שהפעיל אותו. השירות יכול לפעול ברקע ללא הגבלת זמן, גם אם הרכיב שהפעיל אותו נהרס. לכן, השירות אמור להפסיק את עצמו כשהמשימה שלו תושלם באמצעות קריאה ל-stopSelf()
, או שרכיב אחר יכול להפסיק אותו באמצעות קריאה ל-stopService()
.
רכיב של אפליקציה, כמו פעילות, יכול להפעיל את השירות על ידי קריאה ל-startService()
והעברת Intent
שמציין את השירות וכולל את כל הנתונים שהשירות צריך להשתמש בהם. השירות מקבל את Intent
הזה ב-method onStartCommand()
.
לדוגמה, נניח שפעילות מסוימת צריכה לשמור נתונים מסוימים במסד נתונים אונליין. הפעילות יכולה להעביר את הנתונים לשמירה לשירות נלווה על ידי העברת כוונה (intent) אל 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
- אם המערכת תפסיק את השירות אחרי שה-method
onStartCommand()
יחזיר נתונים, אל תיצרו מחדש את השירות אלא אם יש כוונות בהמתנה למשלוח. זו האפשרות הבטוחה ביותר כדי להימנע מהפעלת השירות כשאין צורך, וכשאפשר פשוט להפעיל מחדש את המשימות שלא הסתיימו באפליקציה. START_STICKY
- אם המערכת תפסיק את השירות אחרי שהחזרה של
onStartCommand()
תתבצע, צריך ליצור מחדש את השירות ולקרוא ל-onStartCommand()
, אבל לא לשלוח מחדש את הכוונה האחרונה. במקום זאת, המערכת קוראת ל-onStartCommand()
עם כוונה (intent) null, אלא אם יש כוונות בהמתנה להפעלת השירות. במקרה כזה, המערכת תעביר את הכוונות האלה. האפשרות הזו מתאימה לנגני מדיה (או לשירותים דומים) שלא מבצעים פקודות, אלא פועלים ללא הגבלת זמן וממתינים למשימה. START_REDELIVER_INTENT
- אם המערכת תפסיק את השירות אחרי שהפונקציה
onStartCommand()
תחזיר נתונים, צריך ליצור מחדש את השירות ולקרוא לפונקציהonStartCommand()
עם הכוונה האחרונה שנשלחה לשירות. כל הכוונות בהמתנה נשלחות בתורו. האפשרות הזו מתאימה לשירותים שמבצעים משימות באופן פעיל, וחשוב להמשיך אותן מיד, כמו הורדת קובץ.
פרטים נוספים על ערכי ההחזרה האלה זמינים במסמכי העזרה המקושרים של כל קבוע.
הפעלת שירות
אפשר להפעיל שירות מפעילות או מרכיב אחר באפליקציה על ידי העברת Intent
אל startService()
או אל startForegroundService()
. מערכת Android מפעילה את השיטה onStartCommand()
של השירות ומעבירה לה את הערך של Intent
, שמציין איזה שירות להפעיל.
הערה: אם האפליקציה שלכם מטרגטת את רמת ה-API 26 ואילך, המערכת מטילה הגבלות על השימוש בשירותי רקע או על היצירה שלהם, אלא אם האפליקציה עצמה נמצאת בחזית. אם אפליקציה צריכה ליצור שירות שפועל בחזית, היא צריכה לבצע קריאה ל-startForegroundService()
. השיטה הזו יוצרת שירות רקע, אבל השיטה מאותתת למערכת שהשירות יעלה לחזית. אחרי שיוצרים את השירות, הוא צריך להפעיל את השיטה startForeground()
תוך חמש שניות.
לדוגמה, פעילות יכולה להפעיל את שירות הדוגמה בקטע הקודם (HelloService
) באמצעות כוונה מפורשת עם startService()
, כפי שמוצג כאן:
Kotlin
startService(Intent(this, HelloService::class.java))
Java
startService(new Intent(this, HelloService.class));
השיטה startService()
מחזירה תשובה באופן מיידי, ומערכת Android קוראת ל-method 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).
כדי ליצור שירות מקושר, מטמיעים את שיטת ה-callback onBind()
כדי להחזיר IBinder
שמגדיר את הממשק לתקשורת עם השירות. לאחר מכן, רכיבים אחרים באפליקציה יכולים להפעיל את bindService()
כדי לאחזר את הממשק ולהתחיל לבצע קריאות לשיטות בשירות. השירות קיים רק כדי לשרת את רכיב האפליקציה שמקושר אליו, כך שכאשר אין רכיבים שמקושרים לשירות, המערכת משמידה אותו.
לא צריך להפסיק שירות מקושר באותו אופן שבו צריך להפסיק אותו כשהשירות מופעל דרך onStartCommand()
.
כדי ליצור שירות מקושר, צריך להגדיר את הממשק שמציין איך לקוח יכול לתקשר עם השירות. הממשק הזה בין השירות ללקוח חייב להיות הטמעה של IBinder
, והוא מה שהשירות צריך להחזיר מ-method onBind()
. אחרי שהלקוח מקבל את IBinder
, הוא יכול להתחיל ליצור אינטראקציה עם השירות דרך הממשק הזה.
מספר לקוחות יכולים להתחבר לשירות בו-זמנית. כשהלקוח מסיים את האינטראקציה עם השירות, הוא קורא ל-unbindService()
כדי לבטל את הקישור.
כשאין לקוחות שמקושרים לשירות, המערכת משמידה אותו.
יש כמה דרכים להטמיע שירות מקושר, וההטמעה מורכבת יותר מזו של שירות שהופעל. לכן, הדיון בנושא שירותים מקושרים מופיע במסמך נפרד בנושא שירותים מקושרים.
שליחת התראות למשתמש
כששירות פועל, הוא יכול להודיע למשתמש על אירועים באמצעות התראות בסרגל ההתראות או התראות בסרגל הסטטוס.
התראה בסרגל ההתראות היא הודעה שמופיעה על פני השטח של החלון הנוכחי רק לרגע לפני שהיא נעלמת. התראה בשורת הסטטוס כוללת סמל עם הודעה בשורת הסטטוס, והמשתמש יכול ללחוץ עליה כדי לבצע פעולה (למשל, להתחיל פעילות).
בדרך כלל, התראה בסרגל הסטטוס היא השיטה הטובה ביותר להשתמש בה כשהעבודה ברקע, כמו הורדת קובץ, הסתיימה והמשתמש יכול לבצע פעולה לגביה. כשהמשתמש בוחר בהתראה בתצוגה המורחבת, ההתראה יכולה להפעיל פעילות (למשל, להציג את הקובץ שהורדתם).
ניהול מחזור החיים של שירות
מחזור החיים של שירות פשוט בהרבה מזה של פעילות. עם זאת, חשוב עוד יותר לשים לב לאופן שבו השירות נוצר ונמחק, כי שירות יכול לפעול ברקע בלי שהמשתמש יהיה מודע לכך.
מחזור החיים של השירות – מהרגע שהוא נוצר ועד שהוא מושמד – יכול להתנהל בשני אופנים:
- שירות שהופעל
השירות נוצר כשרכיב אחר קורא ל-
startService()
. לאחר מכן השירות פועל ללא הגבלת זמן, וצריך להפסיק אותו בעצמו באמצעות קריאה ל-stopSelf()
. רכיב אחר יכול גם להפסיק את השירות על ידי קריאה ל-stopService()
. כשהשירות מושבת, המערכת משמידה אותו. - שירות מקושר
השירות נוצר כשרכיב אחר (לקוח) קורא ל-
bindService()
. לאחר מכן הלקוח מתקשר עם השירות דרך ממשקIBinder
. הלקוח יכול לסגור את החיבור באמצעות קריאה ל-unbindService()
. מספר לקוחות יכולים לקשר לאותו שירות, וכשהם מבטלים את הקישור, המערכת משמידה את השירות. השירות לא צריך להפסיק את עצמו.
שני הנתיבים האלה לא נפרדים לגמרי. אפשר לקשר לשירות שכבר הופעל באמצעות startService()
. לדוגמה, אפשר להפעיל שירות של מוזיקה ברקע על ידי קריאה ל-startService()
עם Intent
שמזהה את המוזיקה שרוצים להשמיע. מאוחר יותר, כשהמשתמש רוצה לשלוט במידה מסוימת בנגן או לקבל מידע על השיר הנוכחי, הפעילות יכולה לקשר את עצמה לשירות באמצעות קריאה ל-bindService()
. במקרים כאלה, stopService()
או stopSelf()
לא מפסיקים את השירות בפועל עד שכל הלקוחות מבטלים את הקישור.
הטמעת הקריאות החוזרות במחזור החיים
בדומה לפעילות, לשירות יש שיטות קריאה חוזרת (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 funonCreate
() { // The service is being created } override funonStartCommand
(intent: Intent?, flags: Int, startId: Int): Int { // The service is starting, due to a call to startService() return startMode } override funonBind
(intent: Intent): IBinder? { // A client is binding to the service with bindService() return binder } override funonUnbind
(intent: Intent): Boolean { // All clients have unbound with unbindService() return allowRebind } override funonRebind
(intent: Intent) { // A client is binding to the service with bindService(), // after onUnbind() has already been called } override funonDestroy
() { // 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 voidonCreate
() { // The service is being created } @Override public intonStartCommand
(Intent intent, int flags, int startId) { // The service is starting, due to a call tostartService()
return startMode; } @Override public IBinderonBind
(Intent intent) { // A client is binding to the service withbindService()
return binder; } @Override public booleanonUnbind
(Intent intent) { // All clients have unbound withunbindService()
return allowRebind; } @Override public voidonRebind
(Intent intent) { // A client is binding to the service withbindService()
, // after onUnbind() has already been called } @Override public voidonDestroy
() { // The service is no longer used and is being destroyed } }
הערה: בניגוד לשיטות ה-call back של מחזור החיים של הפעילות, לא צריך לבצע קריאה להטמעה של שיטות ה-call back האלה בסופר-קלאס.
באיור 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()
, אבל אין קריאה חוזרת (callback) מתאימה לשירות (אין קריאה חוזרת מסוג onStop()
). אלא אם השירות קשור ללקוח, המערכת תהרוס אותו כשהשירות יופסק – onDestroy()
הוא הקריאה היחידה חזרה (callback) שתתקבל.
למידע נוסף על יצירת שירות שמספק קישור, תוכלו לעיין במסמך Bound Services, שכולל מידע נוסף על שיטת ה-callback onRebind()
בקטע ניהול מחזור החיים של שירות מקושר.