שירותים שפועלים בחזית מבצעים פעולות שהמשתמשים רואים.
שירותים שפועלים בחזית מוצגים בתזכורת בסרגל הסטטוס, כדי להודיע למשתמשים שהאפליקציה מבצעת משימה בחזית ומנצלת משאבי מערכת.
דוגמאות לאפליקציות שמשתמשות בשירותים שפועלים בחזית:
- אפליקציית נגן מוזיקה שמפעילה מוזיקה בשירות שפועל בחזית. ההתראה עשויה להציג את השיר הנוכחי שמושמע.
- אפליקציית כושר שמתעדת ריצה של משתמש בשירות שפועל בחזית, אחרי שקיבלה הרשאה מהמשתמש. יכול להיות שההתראה תציג את המרחק שהמשתמש עבר במהלך סשן הכושר הנוכחי.
כדאי להשתמש בשירות שפועל בחזית רק כשהאפליקציה צריכה לבצע משימה שהמשתמשים יכולים להבחין בה, גם אם אין להם אינטראקציה ישירה איתה. אם לפעולה חשובה מספיק ואתם רוצים להשתמש בהתראה עם עדיפות מינימלית, אפשר ליצור משימה ברקע.
במסמך הזה מוסבר מהי ההרשאה הנדרשת לשימוש בשירותים שפועלים בחזית, ואיך מפעילים שירות שפועל בחזית ומסירים אותו מהרקע. בנוסף, מוסבר איך לשייך תרחישים לדוגמה מסוימים לסוגים של שירותים שפועלים בחזית, ואילו הגבלות גישה חלות כשמפעילים שירות שפועל בחזית מאפליקציה שפועלת ברקע.
המשתמש יכול לסגור את ההתראה כברירת מחדל
החל מ-Android 13 (רמת API 33), המשתמשים יכולים לסגור את ההתראה שמשויכת לשירות שפועל בחזית כברירת מחדל. כדי לעשות זאת, המשתמשים מבצעים מחווה של החלקה על ההתראה. בדרך כלל, ההתראה לא תיסגר אלא אם השירות שפועל בחזית יופסק או יוסר ממנה.
אם אתם לא רוצים שהמשתמש יוכל לסגור את ההתראה, עליכם להעביר את הערך true
ל-method setOngoing()
כשיוצרים את ההתראה באמצעות Notification.Builder
.
שירותים שמציגים התראה באופן מיידי
אם לשירות שפועל בחזית יש לפחות אחד מהמאפיינים הבאים, המערכת תציג את ההתראה המשויכת מיד אחרי הפעלת השירות, גם במכשירים עם Android 12 ואילך:
- השירות משויך להתראה שכוללת לחצני פעולה.
- לשירות יש
foregroundServiceType
שלmediaPlayback
,mediaProjection
אוphoneCall
. - השירות מספק תרחיש לדוגמה שקשור לשיחות טלפון, לניווט או להפעלת מדיה, כפי שמוגדר במאפיין הקטגוריה של ההתראה.
- השירות ביקש שלא לשנות את ההתנהגות על ידי העברת הערך
FOREGROUND_SERVICE_IMMEDIATE
אלsetForegroundServiceBehavior()
בזמן הגדרת ההתראה.
ב-Android 13 ואילך (רמת API 33 ואילך), אם המשתמש דוחה את הרשאת ההתראות, הוא עדיין רואה התראות שקשורות לשירותים שפועלים בחזית במנהל המשימות, אבל לא רואה אותן בחלונית ההתראות.
הצהרה על שירותים שפועלים בחזית במניפסט
במניפסט של האפליקציה, צריך להצהיר על כל אחד מהשירותים שפועלים בחזית באפליקציה עם רכיב <service>
. לכל שירות, משתמשים במאפיין android:foregroundServiceType
כדי להצהיר על סוג העבודה שהשירות מבצע.
לדוגמה, אם האפליקציה יוצרת שירות שפועל בחזית ומשמיע מוזיקה, אפשר להצהיר על השירות כך:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>
<application ...>
<service
android:name=".MyMediaPlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="false">
</service>
</application>
</manifest>
אם יש כמה סוגים שחלים על השירות, מפרידים ביניהם באמצעות האופרטור |
. לדוגמה, בשירות שמשתמש במצלמה ובמיקרופון
השירות יכריז שהוא כך:
android:foregroundServiceType="camera|microphone"
בקשת הרשאות לשירות שפועל בחזית
אפליקציות שמטרגטות ל-Android 9 (רמת API 28) ואילך ומשתמשות בשירותים שפועלים בחזית צריכות לבקש את ההרשאה FOREGROUND_SERVICE
במניפסט של האפליקציה, כפי שמתואר בקטע הקוד הבא. מדובר בהרשאה רגילה, ולכן המערכת מעניקה אותה באופן אוטומטי לאפליקציה שמבקשת אותה.
בנוסף, אם האפליקציה מטרגטת לרמת API 34 ומעלה, היא צריכה לבקש את סוג ההרשאה המתאים לסוג העבודה שהשירות שפועל בחזית יבצע. לכל סוג שירות שפועל בחזית יש סוג הרשאה מתאים. לדוגמה, אם אפליקציה מפעילה שירות שפועל בחזית שמשתמש במצלמה, אתם צריכים לבקש גם את ההרשאות ל-FOREGROUND_SERVICE
וגם ל-FOREGROUND_SERVICE_CAMERA
. אלה כולן הרשאות רגילות, ולכן המערכת מעניקה אותן באופן אוטומטי אם הן מפורטות במניפסט.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA"/>
<application ...>
...
</application>
</manifest>
דרישות מוקדמות לשימוש בשירות שפועל בחזית
החל מ-Android 14 (רמת API 34), כשמפעילים שירות שפועל בחזית, המערכת בודקת אם מתקיימים תנאים מוקדמים ספציפיים בהתאם לסוג השירות. לדוגמה, אם תנסו להפעיל שירות שפועל בחזית מסוג location
, המערכת תבדוק כדי לוודא שלאפליקציה כבר יש את ההרשאה ACCESS_COARSE_LOCATION
או ACCESS_FINE_LOCATION
. אם לא, המערכת תשליך את השגיאה SecurityException
.
לכן, עליכם לוודא שאתם עומדים בדרישות המוקדמות הנדרשות לפני שאתם מפעילים שירות בחזית. במסמכי העזרה בנושא סוג השירות שפועל בחזית מפורטים התנאים המוקדמים הנדרשים לכל סוג של שירות שפועל בחזית.
הפעלת שירות שפועל בחזית
לפני שמבקשים מהמערכת להפעיל שירות כשירות שפועל בחזית, צריך להפעיל את השירות עצמו:
Kotlin
val intent = Intent(...) // Build the intent for the service context.startForegroundService(intent)
Java
Context context = getApplicationContext(); Intent intent = new Intent(...); // Build the intent for the service context.startForegroundService(intent);
בתוך השירות, בדרך כלל ב-onStartCommand()
, אפשר לבקש שהשירות יפעל בחזית. כדי לעשות זאת, צריך להפעיל את ServiceCompat.startForeground()
(האפשרות הזו זמינה ב-androidx-core מגרסה 1.12 ואילך). השיטה מקבלת את הפרמטרים הבאים:
- השירות
- מספר שלם חיובי שמזהה באופן ייחודי את ההתראה בשורת הסטטוס
- האובייקט
Notification
עצמו - סוגי השירותים שפועלים בחזית שמזהים את העבודה שהשירות מבצע
הסוגים האלה עשויים להיות קבוצת משנה של הסוגים שמוצהרים במניפסט, בהתאם לתרחיש לדוגמה הספציפי. לאחר מכן, אם צריך להוסיף עוד סוגי שירותים, אפשר לבצע שוב קריאה ל-startForeground()
.
לדוגמה, נניח שאפליקציית כושר מפעילה שירות למעקב אחר ריצה שתמיד צריך להשתמש בו כדי לקבל מידע על location
, אבל יכול להיות שלא תצטרכו להפעיל מדיה. חשוב להצהיר גם על location
וגם על mediaPlayback
במניפסט. אם משתמש מתחיל ריצה ורוצה לעקוב רק אחרי המיקום שלו, האפליקציה צריכה לקרוא ל-startForeground()
ולהעביר רק את ההרשאה ACCESS_FINE_LOCATION
. לאחר מכן, אם המשתמש רוצה להתחיל להפעיל אודיו, צריך לקרוא שוב ל-startForeground()
ולהעביר את השילוב הבינארי של כל סוגי השירותים שפועלים בחזית (במקרה הזה, ACCESS_FINE_LOCATION|FOREGROUND_SERVICE_MEDIA_PLAYBACK
).
דוגמה להפעלה של שירות שפועל בחזית של המצלמה:
Kotlin
class MyCameraService: Service() { private fun startForeground() { // Before starting the service as foreground check that the app has the // appropriate runtime permissions. In this case, verify that the user has // granted the CAMERA permission. val cameraPermission = PermissionChecker.checkSelfPermission(this, Manifest.permission.CAMERA) if (cameraPermission != PermissionChecker.PERMISSION_GRANTED) { // Without camera permissions the service cannot run in the foreground // Consider informing user or updating your app UI if visible. stopSelf() return } try { val notification = NotificationCompat.Builder(this, "CHANNEL_ID") // Create the notification to display while the service is running .build() ServiceCompat.startForeground( /* service = */ this, /* id = */ 100, // Cannot be 0 /* notification = */ notification, /* foregroundServiceType = */ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA } else { 0 }, ) } catch (e: Exception) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && e is ForegroundServiceStartNotAllowedException) { // App not in a valid state to start foreground service // (e.g. started from bg) } // ... } } }
Java
public class MyCameraService extends Service { private void startForeground() { // Before starting the service as foreground check that the app has the // appropriate runtime permissions. In this case, verify that the user // has granted the CAMERA permission. int cameraPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA); if (cameraPermission == PackageManager.PERMISSION_DENIED) { // Without camera permissions the service cannot run in the // foreground. Consider informing user or updating your app UI if // visible. stopSelf(); return; } try { Notification notification = new NotificationCompat.Builder(this, "CHANNEL_ID") // Create the notification to display while the service // is running .build(); int type = 0; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { type = ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA; } ServiceCompat.startForeground( /* service = */ this, /* id = */ 100, // Cannot be 0 /* notification = */ notification, /* foregroundServiceType = */ type ); } catch (Exception e) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && e instanceof ForegroundServiceStartNotAllowedException ) { // App not in a valid state to start foreground service // (e.g started from bg) } // ... } } //... }
הסרת שירות מחזית המסך
כדי להסיר את השירות מחזית השירות, קוראים ל-stopForeground()
.
השיטה מקבלת ערך בוליאני שמציין אם להסיר גם את ההתראה בסרגל הסטטוס. חשוב לציין שהשירות ממשיך לפעול.
אם תפסיקו את השירות בזמן שהוא פועל בחזית, ההתראה שלו תוסר.
טיפול בהפסקה של אפליקציות שפועלות בשירותים שפועלים בחזית ביוזמת המשתמש
החל מ-Android 13 (רמת API 33), משתמשים יכולים להשלים תהליך עבודה ממסגרת ההתראות כדי להפסיק אפליקציה עם שירותים שפועלים בחזית, ללא קשר לגרסה של SDK לטירגוט של האפליקציה. האפשרות הזו, שנקראת מנהל המשימות, מציגה רשימה של אפליקציות שפועל בהן כרגע שירות בחזית.
הרשימה הזו מסומנת בתווית אפליקציות פעילות. לצד כל אפליקציה מופיע לחצן עצירה. באיור 1 מוצג תהליך העבודה של מנהל המשימות במכשיר עם Android 13.
כשהמשתמש לוחץ על הלחצן Stop לצד האפליקציה ב-Task Manager, מתרחשות הפעולות הבאות:
- המערכת מסירה את האפליקציה מהזיכרון. לכן האפליקציה כולה נעצרת, ולא רק השירות שפועל בחזית.
- המערכת מסירה את מקבץ הגיבוי של נתוני הפעילות של האפליקציה.
- כל הפעלת מדיה תיפסק.
- ההתראה שמשויכת לשירות שפועל בחזית תוסר.
- האפליקציה שלך נשארת בהיסטוריה.
- משימות מתוזמנות פועלות במועד שנקבע להן.
- ההתראות מופעלות במועד או בחלון הזמן שנקבעו.
כדי לבדוק שהאפליקציה מתנהגת כצפוי בזמן שהמשתמש מפסיק אותה ואחרי שהוא מפסיק אותה, מריצים את פקודת ה-ADB הבאה בחלון מסוף:
adb shell cmd activity stop-app PACKAGE_NAME
פטורים
המערכת כוללת כמה רמות של החרגות לאפליקציות מסוגים מסוימים, כפי שמתואר בקטעים הבאים.
ההחרגות הן לכל אפליקציה, ולא לכל תהליך. אם המערכת פטרה תהליך אחד באפליקציה, כל התהליכים האחרים באפליקציה הזו פטורים גם הם.
הצגת פטורים במנהל המשימות בכלל
האפליקציות הבאות יכולות להריץ שירות שפועל בחזית ולא להופיע במנהל המשימות בכלל:
- אפליקציות ברמת המערכת
- אפליקציות בטיחות, כלומר אפליקציות עם התפקיד
ROLE_EMERGENCY
- מכשירים שנמצאים במצב הדגמה
החרגות מאפשרות של משתמשים לעצור את ההפעלה
כשסוגים מסוימים של אפליקציות מפעילים שירות בחזית, הן מופיעות במנהל המשימות, אבל אין לחצן עצירה לצד שם האפליקציה שהמשתמש יכול להקיש עליו:
- אפליקציות של בעלי המכשיר
- אפליקציות של בעלי הפרופיל
- אפליקציות קבועות
- אפליקציות עם התפקיד
ROLE_DIALER
שימוש בממשקי API ייעודיים במקום בשירותים שפועלים בחזית
בהרבה תרחישי שימוש יש ממשקי API של פלטפורמות או Jetpack שאפשר להשתמש בהם לביצוע משימות, אחרת אולי תשתמשו בשירות שפועל בחזית. אם יש ממשק API מתאים שנועד למטרה מסוימת, כמעט תמיד כדאי להשתמש בו במקום בשירות שפועל בחזית. הרבה פעמים ממשקי API שמיועדים למטרות עסקיות מספקים יכולות נוספות שספציפיות לתרחישים לדוגמה, שהייתם צריכים לפתח בעצמכם. לדוגמה, Bubbles API מטפל בלוגיקה המורכבת של ממשק המשתמש באפליקציות שליחת הודעות שצריכות להטמיע תכונות של בועות צ'אט.
במסמכי התיעוד של סוגי השירותים שפועלים בחזית מפורטות חלופות טובות לשימוש במקום שירותים שפועלים בחזית.
הגבלות על הפעלת שירות שפועל בחזית מהרקע
אפליקציות שמטרגטות ל-Android מגרסה 12 ואילך לא יכולות להפעיל שירותים שפועלים בחזית בזמן שהאפליקציה פועלת ברקע, מלבד כמה מקרים מיוחדים. אם האפליקציה מנסה להפעיל
שירות שפועל בחזית בזמן שהיא פועלת ברקע, והשירות שפועל בחזית לא עונה על אחד המקרים החריגים, המערכת גורמת ל-ForegroundServiceStartNotAllowedException
.
בנוסף, אם אפליקציה רוצה להפעיל שירות שפועל בחזית שנדרשת לו הרשאות בזמן השימוש (לדוגמה: הרשאות לחיישן גוף, למצלמה, למיקרופון או למיקום), היא לא יכולה ליצור את השירות בזמן שהאפליקציה פועלת ברקע, גם אם האפליקציה נכללת באחת מהפטורים מהגבלות ההפעלה ברקע. הסיבה לכך מוסברת בקטע הגבלות על הפעלת שירותים בחזית שצריכים הרשאות לשימוש.
פטורים מהגבלות של התחלת פעילות ברקע
במצבים הבאים, האפליקציה יכולה להפעיל שירותים שפועלים בחזית גם כשהיא פועלת ברקע:
- האפליקציה עוברת ממצב גלוי למשתמש, כמו פעילות.
- האפליקציה יכולה להפעיל פעילות מהרקע, מלבד במקרה שיש לאפליקציה פעילות בערימה האחורית של משימה קיימת.
האפליקציה מקבלת הודעה בעדיפות גבוהה באמצעות Firebase Cloud Messaging.
המשתמש מבצע פעולה ברכיב בממשק המשתמש שקשור לאפליקציה. לדוגמה, יכול להיות שיש לו אינטראקציה עם בועה, התראה, ווידג'ט או פעילות.
האפליקציה מפעילה התראה מדויקת כדי להשלים פעולה שהמשתמש מבקש.
האפליקציה היא שיטת הקלט הנוכחית של המכשיר.
האפליקציה שלכם מקבלת אירוע שקשור להגדרת גבולות וירטואליים או למעבר לזיהוי פעילות.
אחרי שהמכשיר יופעל מחדש ויקבל את פעולת הכוונה
ACTION_BOOT_COMPLETED
,ACTION_LOCKED_BOOT_COMPLETED
אוACTION_MY_PACKAGE_REPLACED
במקלט שידור.האפליקציה מקבלת את פעולת הכוונה
ACTION_TIMEZONE_CHANGED
,ACTION_TIME_CHANGED
אוACTION_LOCALE_CHANGED
במקלט שידורים.האפליקציה מקבלת את האירוע
ACTION_TRANSACTION_DETECTED
מ-NfcService
.אפליקציות עם הרשאות או תפקידי מערכת מסוימים, כמו בעלי המכשיר ובעלי הפרופיל.
האפליקציה משתמשת בניהול מכשיר Companion ומצהירה על ההרשאה
REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND
או על ההרשאהREQUEST_COMPANION_RUN_IN_BACKGROUND
. כשאפשר, כדאי להשתמש ב-REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND
.המשתמש משבית את האופטימיזציות של הסוללה באפליקציה.
לאפליקציה שלכם יש את ההרשאה
SYSTEM_ALERT_WINDOW
. הערה: אם האפליקציה שלכם מטרגטת את Android 15 ואילך, היא חייבת לקבל את ההרשאהSYSTEM_ALERT_WINDOW
וגם חייבת להיות לה כרגע חלון שכבת-על גלוי.
הגבלות על הפעלת שירותים שפועלים בחזית שצריכים הרשאות לשימוש בזמן אמת
ב-Android 14 (רמת API 34) ואילך, יש מצבים מיוחדים שצריך להיות מודעים אליהם כשמפעילים שירות שפועל בחזית שדורש הרשאות לשימוש פעיל.
אם האפליקציה מטרגטת ל-Android מגרסה 14 ואילך, מערכת ההפעלה
בודקת כשיוצרים שירות שפועל בחזית כדי לוודא שלאפליקציה יש את כל ההרשאות המתאימות לסוג השירות הזה. לדוגמה, כשיוצרים שירות שפועל בחזית מסוג microphone, מערכת ההפעלה מוודאת שלאפליקציה יש כרגע את ההרשאה RECORD_AUDIO
. אם אין לכם את ההרשאה הזו, המערכת תשליך את השגיאה SecurityException
.
כשמדובר בהרשאות לשימוש פעיל, המצב הזה עלול לגרום לבעיה. אם לאפליקציה יש הרשאה לשימוש בזמן השימוש, היא תהיה בתוקף רק כשהיא בחזית. כלומר, אם האפליקציה נמצאת ברקע ומנסה ליצור שירות שפועל בחזית מסוג מצלמה, מיקום או מיקרופון, המערכת מזהה שלאפליקציה אין כרגע את ההרשאות הנדרשות, ומציגה את השגיאה SecurityException
.
באופן דומה, אם האפליקציה פועלת ברקע ויוצרת שירות בריאות שדורש את ההרשאה BODY_SENSORS
, לאפליקציה אין כרגע את ההרשאה הזו והמערכת תיצור חריגה.
(הכלל הזה לא חל אם מדובר בשירות בריאות שדורש הרשאות שונות, כמו ACTIVITY_RECOGNITION
). קריאה ל-PermissionChecker.checkSelfPermission()
לא מונעת את הבעיה הזו. אם לאפליקציה יש הרשאה בזמן שימוש, והיא מבצעת קריאה ל-checkSelfPermission()
כדי לבדוק אם יש לה את ההרשאה, השיטה תחזיר PERMISSION_GRANTED
גם אם האפליקציה פועלת ברקע. כשהשיטה מחזירה את הערך PERMISSION_GRANTED
, המשמעות היא "האפליקציה שלך קיבלה את ההרשאה הזו בזמן שהאפליקציה בשימוש".
לכן, אם לשירות בחזית נדרשת הרשאה לשימוש פעיל, צריך להפעיל את Context.startForegroundService()
או Context.bindService()
בזמן שהאפליקציה מציגה פעילות גלויה, אלא אם השירות נכלל באחד מהחריגים שהוגדרו.
החרגות מההגבלות על הרשאות בזמן השימוש
במצבים מסוימים, גם אם שירות שפועל בחזית מופעל בזמן שהאפליקציה פועלת ברקע, הוא עדיין יכול לגשת לנתוני המיקום, המצלמה והמיקרופון בזמן שהאפליקציה פועלת בחזית ("בזמן השימוש").
באותן סיטואציות, אם השירות מצהיר על סוג שירות שפועל בחזית של location
והוא מופעל על ידי אפליקציה שיש לה את ההרשאה ACCESS_BACKGROUND_LOCATION
, השירות הזה יכול לגשת לפרטי המיקום כל הזמן, גם כשהאפליקציה פועלת ברקע.
המצבים האלה מפורטים ברשימה הבאה:
- רכיב מערכת מפעיל את השירות.
- השירות מתחיל לפעול באמצעות אינטראקציה עם ווידג'טים של אפליקציות.
- השירות מתחיל באינטראקציה עם ההתראה.
- השירות מתחיל בתור
PendingIntent
שנשלח מאפליקציה אחרת גלויה. - השירות מתחיל על ידי אפליקציה שהיא בקר מדיניות מכשירים שפועלת במצב 'בעלי המכשיר'.
- השירות מתחיל על ידי אפליקציה שמספקת את
VoiceInteractionService
. - השירות מופעל על ידי אפליקציה עם ההרשאה המיוחדת
START_ACTIVITIES_FROM_BACKGROUND
.
איך לקבוע אילו שירותים מושפעים באפליקציה שלכם
כשבודקים את האפליקציה, מפעילים את השירותים שלה שפועלים בחזית. אם לשירות שהופעל יש גישה מוגבלת למיקום, למיקרופון ולמצלמה, ההודעה הבאה תופיע ב-Logcat:
Foreground service started from background can not have \ location/camera/microphone access: service SERVICE_NAME