אפליקציות ל-Android שולחות ומקבלות הודעות לכולם ממערכת Android ומאפליקציות אחרות ל-Android, בדומה לתבנית העיצוב פרסום-מינוי. המערכת והאפליקציות בדרך כלל שולחות שידורים כשאירועים מסוימים מתרחשים. לדוגמה, מערכת Android שולחת שידורים כשמתרחשים אירועי מערכת שונים, כמו אתחול המערכת או טעינת המכשיר. אפליקציות גם שולחות שידורים בהתאמה אישית, למשל כדי להודיע לאפליקציות אחרות על משהו שעשוי לעניין אותן (לדוגמה, הורדת נתונים חדשים).
אפליקציות יכולות להירשם כדי לקבל שידורים ספציפיים. כששידור נשלח, המערכת מנתבת אותו באופן אוטומטי לאפליקציות שנרשמו לקבלת שידורים מהסוג הספציפי הזה.
באופן כללי, אפשר להשתמש בשידורים כמערכת להעברת הודעות בין אפליקציות ומחוץ למסלול המשתמש הרגיל. עם זאת, חשוב להיזהר מניצול לרעה של האפשרות להגיב לשידורים ולהפעיל ברקע משימות שיכולות להאט את ביצועי המערכת.
מידע על שידורי מערכת
המערכת שולחת שידורים באופן אוטומטי כשמתרחשים אירועים שונים במערכת, למשל כשהמערכת עוברת למצב טיסה או יוצאת ממנו. כל האפליקציות שהמשתמשים רשומים אליהן מקבלות את השידורים האלה.
האובייקט Intent כולל את ההודעה לכולם. המחרוזת action מזהה את האירוע שהתרחש, למשל android.intent.action.AIRPLANE_MODE. יכול להיות שה-intent יכלול גם מידע נוסף בשדה הנוסף שלו.
לדוגמה, ה-Intent של מצב טיסה כולל תוספת בוליאנית שמציינת אם מצב הטיסה מופעל או לא.
מידע נוסף על קריאת Intent וקבלת מחרוזת הפעולה מ-Intent זמין במאמר Intent ומסנני Intent.
פעולות של שידור מערכת
רשימה מלאה של פעולות שידור מערכת מופיעה בקובץ BROADCAST_ACTIONS.TXT ב-Android SDK. לכל פעולת שידור משויך שדה קבוע. לדוגמה, הערך של הקבוע ACTION_AIRPLANE_MODE_CHANGED הוא android.intent.action.AIRPLANE_MODE.
התיעוד של כל פעולת שידור זמין בשדה הקבוע שמשויך אליה.
שינויים בשידורי המערכת
פלטפורמת Android מתפתחת כל הזמן, ולכן מדי פעם משתנה האופן שבו שידורי המערכת פועלים. כדי לתמוך בכל הגרסאות של Android, חשוב לזכור את השינויים הבאים.
Android 16
ב-Android 16, לא מובטח סדר העברת השידורים באמצעות המאפיין android:priority או IntentFilter.setPriority() בתהליכים שונים. העדיפויות של השידור נשמרות רק בתהליך של אותה אפליקציה ולא בכל התהליכים.
בנוסף, סדרי העדיפויות של השידורים מוגבלים באופן אוטומטי לטווח (SYSTEM_LOW_PRIORITY + 1, SYSTEM_HIGH_PRIORITY - 1).
רק לרכיבי מערכת מותר להגדיר את SYSTEM_LOW_PRIORITY,
SYSTEM_HIGH_PRIORITY כעדיפות שידור.
Android 14
כשהאפליקציות נמצאות במצב שמור במטמון, המערכת מבצעת אופטימיזציה של שידור ההודעות כדי לשמור על תקינות המערכת. לדוגמה, המערכת דוחה שידורי מערכת פחות חשובים כמו ACTION_SCREEN_ON כשהאפליקציה במצב שמור במטמון.
אחרי שהאפליקציה עוברת ממצב שמור במטמון למחזור חיים של תהליך פעיל, המערכת מעבירה את כל השידורים שנדחו.
שידורים חשובים שמוצהרים במניפסט מסירים באופן זמני אפליקציות מהמצב שנשמר במטמון לצורך מסירה.
Android 9
החל מ-Android 9 (רמת API 28), שידור NETWORK_STATE_CHANGED_ACTION לא מקבל מידע על מיקום המשתמש או נתונים אישיים מזהים.
אם האפליקציה מותקנת במכשיר עם Android 9.0 (רמת API 28) ומעלה, המערכת לא כוללת מזהי SSID, מזהי BSSID, פרטי חיבור או תוצאות סריקה בשידורי Wi-Fi. כדי לקבל את המידע הזה, צריך להתקשר למספר getConnectionInfo().
Android 8.0
החל מ-Android 8.0 (רמת API 26), המערכת מטילה הגבלות נוספות על מקלטים שהוצהרו במניפסט.
אם האפליקציה מטרגטת את Android מגרסה 8.0 ואילך, אי אפשר להשתמש במניפסט כדי להצהיר על מקלט לרוב ההודעות המרומזות (הודעות שלא מטרגטות את האפליקציה באופן ספציפי). אתם עדיין יכולים להשתמש במקלט שרשום בהקשר כשהמשתמש משתמש באפליקציה באופן פעיל.
Android 7.0
ב-Android מגרסה 7.0 (רמת API 24) ומעלה, המערכת לא שולחת את שידורי המערכת הבאים:
בנוסף, באפליקציות שמטרגטות ל-Android 7.0 ואילך צריך לרשום את השידור של CONNECTIVITY_ACTION באמצעות registerReceiver(BroadcastReceiver, IntentFilter). הצהרה על מקלט במניפסט לא עובדת.
קבלת שידורים
אפליקציות יכולות לקבל שידורים בשתי דרכים: באמצעות מקלטים שרשומים בהקשר ומקלטים שמוצהרים במניפסט.
מקבלים שרשומים בהקשר
מקלטים שרשומים בהקשר מקבלים שידורים כל עוד ההקשר שבו הם רשומים תקף. בדרך כלל זה קורה בין הקריאות ל-registerReceiver ול-unregisterReceiver. ההקשר של הרישום הופך ללא חוקי גם כשהמערכת משמידה את ההקשר המתאים. לדוגמה, אם נרשמים בהקשר של Activity, מקבלים שידורים כל עוד הפעילות נשארת פעילה. אם נרשמים עם ההקשר של האפליקציה, מקבלים שידורים כל עוד האפליקציה פועלת.
כדי לרשום מקלט עם הקשר, מבצעים את השלבים הבאים:
בקובץ ה-build ברמת המודול של האפליקציה, כוללים את גרסה 1.9.0 ומעלה של ספריית הליבה של AndroidX:
Groovy
dependencies { def core_version = "1.19.1" // Java language implementation implementation "androidx.core:core:$core_version" // Kotlin implementation "androidx.core:core-ktx:$core_version" // To use RoleManagerCompat implementation "androidx.core:core-role:1.1.0" // To use the Animator APIs implementation "androidx.core:core-animation:1.0.0" // To test the Animator APIs androidTestImplementation "androidx.core:core-animation-testing:1.0.0" // Optional - To enable APIs that query the performance characteristics of GMS devices. implementation "androidx.core:core-performance:1.0.0" // Optional - to use ShortcutManagerCompat to donate shortcuts to be used by Google implementation "androidx.core:core-google-shortcuts:1.1.0" // Optional - to support backwards compatibility of RemoteViews implementation "androidx.core:core-remoteviews:1.1.0" // Optional - APIs for SplashScreen, including compatibility helpers on devices prior Android 12 implementation "androidx.core:core-splashscreen:1.2.0" }
Kotlin
dependencies { val core_version = "1.19.1" // Java language implementation implementation("androidx.core:core:$core_version") // Kotlin implementation("androidx.core:core-ktx:$core_version") // To use RoleManagerCompat implementation("androidx.core:core-role:1.1.0") // To use the Animator APIs implementation("androidx.core:core-animation:1.0.0") // To test the Animator APIs androidTestImplementation("androidx.core:core-animation-testing:1.0.0") // Optional - To enable APIs that query the performance characteristics of GMS devices. implementation("androidx.core:core-performance:1.0.0") // Optional - to use ShortcutManagerCompat to donate shortcuts to be used by Google implementation("androidx.core:core-google-shortcuts:1.1.0") // Optional - to support backwards compatibility of RemoteViews implementation("androidx.core:core-remoteviews:1.1.0") // Optional - APIs for SplashScreen, including compatibility helpers on devices prior Android 12 implementation("androidx.core:core-splashscreen:1.2.0") }
יוצרים מכונה של
BroadcastReceiver:Kotlin
val myBroadcastReceiver = MyBroadcastReceiver()Java
MyBroadcastReceiver myBroadcastReceiver = new MyBroadcastReceiver();יוצרים מכונה של
IntentFilter:Kotlin
val filter = IntentFilter("com.example.snippets.ACTION_UPDATE_DATA")Java
IntentFilter filter = new IntentFilter("com.example.snippets.ACTION_UPDATE_DATA");בוחרים אם לייצא את מקלט השידור ולהציג אותו לאפליקציות אחרות במכשיר. אם המקלט הזה מאזין לשידורים שנשלחים מהמערכת או מאפליקציות אחרות – אפילו אפליקציות אחרות שבבעלותכם – צריך להשתמש בדגל
RECEIVER_EXPORTED. אם המקלט הזה מאזין רק לשידורים שנשלחים על ידי האפליקציה, צריך להשתמש בדגלRECEIVER_NOT_EXPORTED.Kotlin
val listenToBroadcastsFromOtherApps = false val receiverFlags = if (listenToBroadcastsFromOtherApps) { ContextCompat.RECEIVER_EXPORTED } else { ContextCompat.RECEIVER_NOT_EXPORTED }Java
boolean listenToBroadcastsFromOtherApps = false; int receiverFlags = listenToBroadcastsFromOtherApps ? ContextCompat.RECEIVER_EXPORTED : ContextCompat.RECEIVER_NOT_EXPORTED;רושמים את המקלט באמצעות קריאה ל-
registerReceiver():Kotlin
ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, receiverFlags)Java
ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, receiverFlags);כדי להפסיק לקבל שידורים, צריך להתקשר למספר
unregisterReceiver(android.content.BroadcastReceiver). חשוב לבטל את הרישום של המקלט כשכבר לא צריך אותו או כשההקשר כבר לא רלוונטי.
ביטול הרישום של מקלט השידורים
בזמן שמקלט השידור רשום, הוא מכיל הפניה להקשר שבו רשמתם אותו. זה עלול לגרום לדליפות אם ההיקף הרשום של המקלט חורג מהיקף מחזור החיים של ההקשר. לדוגמה, זה יכול לקרות כשרושמים מקלט בהיקף של Activity, אבל שוכחים לבטל את הרישום שלו כשהמערכת מבצעת כיבוי סופי ל-Activity. לכן, תמיד צריך לבטל את הרישום של מקלט השידורים.
Kotlin
class MyActivity : ComponentActivity() {
private val myBroadcastReceiver = MyBroadcastReceiver()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
ContextCompat.registerReceiver(this, myBroadcastReceiver, filter, receiverFlags)
setContent { MyApp() }
}
override fun onDestroy() {
super.onDestroy()
// When you forget to unregister your receiver here, you're causing a leak!
this.unregisterReceiver(myBroadcastReceiver)
}
}
Java
class MyActivity extends ComponentActivity {
MyBroadcastReceiver myBroadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
ContextCompat.registerReceiver(this, myBroadcastReceiver, filter, receiverFlags);
// Set content
}
}
רישום של נמענים בהיקף הקטן ביותר
מקלט השידור צריך להירשם רק כשאתם באמת מעוניינים בתוצאה. בוחרים את היקף הנמענים הקטן ביותר האפשרי:
-
LifecycleResumeEffectאו methods של מחזור החיים של הפעילותonResume/onPause: מקלט השידור מקבל עדכונים רק כשהאפליקציה במצב המשך. -
LifecycleStartEffectאו פעילותonStart/onStopשיטות מחזור חיים: מקלט השידור מקבל עדכונים רק כשהאפליקציה במצב 'המשך'. -
DisposableEffect: מקלט השידור מקבל עדכונים רק כשהרכיב הניתן לTex נמצא בעץ הקומפוזיציה. ההיקף הזה לא מצורף להיקף של מחזור החיים של הפעילות. מומלץ לרשום את המקלט בהקשר של האפליקציה. הסיבה לכך היא שהרכיב הקומפוזבילי יכול באופן תיאורטי להיות פעיל אחרי סיום מחזור החיים של הפעילות, ולגרום לדליפת הפעילות. - פעילות
onCreate/onDestroy: מקלט השידור מקבל עדכונים כשהפעילות במצב שבו היא נוצרה. חשוב לבטל את הרישום ב-onDestroy()ולא ב-onSaveInstanceState(Bundle), כי יכול להיות שהפונקציה הזו לא תיקרא. - היקף מותאם אישית: לדוגמה, אתם יכולים לרשום מקלט בהיקף
ViewModel, כדי שהוא ימשיך לפעול גם אחרי יצירה מחדש של הפעילות. חשוב להשתמש בהקשר של האפליקציה כדי לרשום את המקלט, כי המקלט יכול להיות פעיל גם אחרי סיום מחזור החיים של הפעילות ולגרום לדליפת הפעילות.
יצירת קומפוזיציה עם שמירת מצב וקומפוזיציה ללא שמירת מצב
ב-Compose יש פונקציות קומפוזביליות עם שמירת מצב וללא שמירת מצב. רישום או ביטול רישום של מקלט שידורים בתוך פונקציה קומפוזבילית הופך אותה לפונקציה עם מצב. התוכן הקומפוזבילי הוא לא פונקציה דטרמיניסטית שמעבדת את אותו תוכן כשמועברים אליה אותם פרמטרים. המצב הפנימי יכול להשתנות בהתאם לקריאות ל-broadcastReceiver הרשום.
כשיטה מומלצת ב-Compose, מומלץ לפצל את הפונקציות הקומפוזביליות לגרסאות עם שמירת מצב ולגרסאות בלי שמירת מצב. לכן, מומלץ להוציא את יצירת מקלט השידורים מ-Composable כדי להפוך אותו לבלי שמירת מצב:
@Composable
fun MyStatefulScreen() {
val myBroadcastReceiver = remember { MyBroadcastReceiver() }
val context = LocalContext.current
LifecycleStartEffect(true) {
// ...
ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, flags)
onStopOrDispose { context.unregisterReceiver(myBroadcastReceiver) }
}
MyStatelessScreen()
}
@Composable
fun MyStatelessScreen() {
// Implement your screen
}
מקבלים שהוגדרו במניפסט
אם אתם מצהירים על מקלט שידור במניפסט, המערכת מפעילה את האפליקציה כשהשידור נשלח. אם האפליקציה לא פועלת, המערכת מפעילה אותה.
כדי להצהיר על מקלט שידור במניפסט, מבצעים את השלבים הבאים:
מציינים את הרכיב
<receiver>במניפסט של האפליקציה.<!-- If this receiver listens for broadcasts sent from the system or from other apps, even other apps that you own, set android:exported to "true". --> <receiver android:name=".MyBroadcastReceiver" android:exported="false"> <intent-filter> <action android:name="com.example.snippets.ACTION_UPDATE_DATA" /> </intent-filter> </receiver>מסנני ה-Intent מציינים את פעולות השידור שהמקלט נרשם אליהן.
יוצרים מחלקת משנה של
BroadcastReceiverומטמיעים אתonReceive(Context, Intent). בדוגמה הבאה, מקלט השידור רושם ביומן ומציג את תוכן השידור:Kotlin
class MyBroadcastReceiver : BroadcastReceiver() { @Inject lateinit var dataRepository: DataRepository override fun onReceive(context: Context, intent: Intent) { if (intent.action == "com.example.snippets.ACTION_UPDATE_DATA") { val data = intent.getStringExtra("com.example.snippets.DATA") ?: "No data" // Do something with the data, for example send it to a data repository: dataRepository.updateData(data) } } }Java
public static class MyBroadcastReceiver extends BroadcastReceiver { @Inject DataRepository dataRepository; @Override public void onReceive(Context context, Intent intent) { if (Objects.equals(intent.getAction(), "com.example.snippets.ACTION_UPDATE_DATA")) { String data = intent.getStringExtra("com.example.snippets.DATA"); // Do something with the data, for example send it to a data repository: if (data != null) { dataRepository.updateData(data); } } } }
מנהל החבילות של המערכת רושם את המקלט כשהאפליקציה מותקנת. ה-receiver הופך לנקודת כניסה נפרדת לאפליקציה, כלומר המערכת יכולה להפעיל את האפליקציה ולשלוח את השידור אם האפליקציה לא פועלת.
המערכת יוצרת אובייקט רכיב BroadcastReceiver חדש כדי לטפל בכל שידור שהיא מקבלת. האובייקט הזה תקף רק למשך הקריאה לפונקציה onReceive(Context, Intent). אחרי שהקוד חוזר מהשיטה הזו, המערכת מחשיבה את הרכיב כלא פעיל יותר.
השפעות על מצב התהליך
השאלה אם BroadcastReceiver פועל או לא משפיעה על התהליך שהוא מכיל, מה שיכול לשנות את הסבירות שהוא יגרום לקריסת המערכת. תהליך שפועל בחזית מפעיל את ה-method onReceive() של המקבל. המערכת מריצה את התהליך, אלא אם יש מחסור קיצוני בנפח הזיכרון.
המערכת משביתה את BroadcastReceiver אחרי onReceive().
המשמעות של תהליך המארח של המקבל תלויה ברכיבי האפליקציה שלו. אם התהליך הזה מארח רק מקלט שהוצהר במניפסט, המערכת עשויה להרוג אותו אחרי onReceive() כדי לפנות משאבים לתהליכים קריטיים אחרים. זה נפוץ באפליקציות שהמשתמש לא ביצע בהן פעולות אף פעם או לאחרונה.
לכן, מקלטי שידורים לא צריכים להפעיל שרשורים ארוכים ברקע.
המערכת יכולה לעצור את התהליך בכל שלב אחרי onReceive() כדי לפנות זיכרון, ולסיים את ה-thread שנוצר. כדי שהתהליך ימשיך לפעול, מתזמנים JobService מהמקלט באמצעות JobScheduler, כדי שהמערכת תדע שהתהליך עדיין פועל. סקירה כללית של עבודה ברקע מספקת פרטים נוספים.
שליחת שידורים
ב-Android יש שתי דרכים לאפליקציות לשלוח שידורים:
- השיטה
sendOrderedBroadcast(Intent, String)שולחת שידורים למקלט אחד בכל פעם. כל מקלט שמופעל בתורו יכול להעביר תוצאה למקלט הבא. הוא גם יכול לבטל לחלוטין את השידור כדי שלא יגיע למקלטים אחרים. אתם יכולים לקבוע את הסדר שבו רכיבי ה-Receiver פועלים באותו תהליך של אפליקציה. כדי לעשות זאת, משתמשים במאפייןandroid:priorityשל מסנן ה-Intent התואם. ההרצה של מקלטים עם אותה עדיפות מתבצעת בסדר שרירותי. - ה-method
sendBroadcast(Intent)שולחת שידורים לכל המקבלים בסדר לא מוגדר. הפעולה הזו נקראת "שידור רגיל". השיטה הזו יעילה יותר, אבל המשמעות היא שהמקבלים לא יכולים לקרוא תוצאות ממקבלים אחרים, להפיץ נתונים שהתקבלו מהשידור או לבטל את השידור.
בקטע הקוד הבא אפשר לראות איך לשלוח שידור על ידי יצירת Intent וקריאה ל-sendBroadcast(Intent).
Kotlin
val intent = Intent("com.example.snippets.ACTION_UPDATE_DATA").apply {
putExtra("com.example.snippets.DATA", newData)
setPackage("com.example.snippets")
}
context.sendBroadcast(intent)
Java
Intent intent = new Intent("com.example.snippets.ACTION_UPDATE_DATA");
intent.putExtra("com.example.snippets.DATA", newData);
intent.setPackage("com.example.snippets");
context.sendBroadcast(intent);
ההודעה לשידור עטופה באובייקט Intent. המחרוזת action של ה-intent צריכה לספק את תחביר שם החבילה של האפליקציה ב-Java ולזהות באופן ייחודי את אירוע השידור. אפשר לצרף מידע נוסף ל-intent באמצעות putExtra(String, Bundle). אפשר גם להגביל שידור לקבוצת אפליקציות באותו ארגון על ידי קריאה ל-setPackage(String) ב-intent.
הגבלת השידורים באמצעות הרשאות
הרשאות מאפשרות להגביל את השידורים לקבוצת האפליקציות שיש להן הרשאות מסוימות. אתם יכולים לאכוף הגבלות על השולח או על המקבל של השידור.
שליחת הודעות לכולם עם הרשאות
כשמפעילים את השיטה sendBroadcast(Intent, String) או sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String,
Bundle), אפשר לציין פרמטר הרשאה. רק נמענים שביקשו את ההרשאה הזו באמצעות התג <uses-permission> במניפסט שלהם יכולים לקבל את השידור. אם ההרשאה מסוכנת, צריך להעניק אותה לפני שהמקלט יוכל לקבל את השידור. לדוגמה, הקוד הבא שולח שידור עם הרשאה:
Kotlin
context.sendBroadcast(intent, android.Manifest.permission.ACCESS_COARSE_LOCATION)
Java
context.sendBroadcast(intent, android.Manifest.permission.ACCESS_COARSE_LOCATION);
כדי לקבל את השידור, האפליקציה המקבלת צריכה לבקש את ההרשאה באופן הבא:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
אפשר לציין הרשאת מערכת קיימת כמו BLUETOOTH_CONNECT או להגדיר הרשאה מותאמת אישית באמצעות הרכיב <permission>. למידע על הרשאות ואבטחה באופן כללי, אפשר לעיין במאמר בנושא הרשאות מערכת.
קבלת שידורים עם הרשאות
אם מציינים פרמטר של הרשאה כשרושמים מקלט שידור (באמצעות registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) או בתג <receiver> במניפסט), רק משדרי תוכן שביקשו את ההרשאה באמצעות התג <uses-permission> במניפסט שלהם יכולים לשלוח Intent למקלט. אם ההרשאה מסוכנת, צריך להעניק אותה גם למשדר.
לדוגמה, נניח שהאפליקציה המקבלת כוללת מקלט שהוצהר במניפסט באופן הבא:
<!-- If this receiver listens for broadcasts sent from the system or from
other apps, even other apps that you own, set android:exported to "true". -->
<receiver
android:name=".MyBroadcastReceiverWithPermission"
android:permission="android.permission.ACCESS_COARSE_LOCATION"
android:exported="true">
<intent-filter>
<action android:name="com.example.snippets.ACTION_UPDATE_DATA" />
</intent-filter>
</receiver>
או שהאפליקציה המקבלת כוללת מקלט עם רישום לפי ההקשר, באופן הבא:
Kotlin
ContextCompat.registerReceiver(
context, myBroadcastReceiver, filter,
android.Manifest.permission.ACCESS_COARSE_LOCATION,
null, // scheduler that defines thread, null means run on main thread
receiverFlags
)
Java
ContextCompat.registerReceiver(
context, myBroadcastReceiver, filter,
android.Manifest.permission.ACCESS_COARSE_LOCATION,
null, // scheduler that defines thread, null means run on main thread
receiverFlags
);
כדי לשלוח שידורים למקלטים האלה, האפליקציה השולחת צריכה לבקש את ההרשאה באופן הבא:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
מומלץ להימנע משידורים באותו תהליך
שידורים נועדו להיות מנגנון תקשורת בין תהליכים (IPC) לשליחת הודעות בין אפליקציות שונות או בין המערכת לאפליקציות. שליחת שידור עצמי, כלומר שידור שבו כל המקלטים פועלים באותו תהליך ששלח את השידור, היא פעולה מאוד לא יעילה, יוצרת תקורה מיותרת במערכת ומומלץ מאוד להימנע ממנה.
שני תרחישים נפוצים שבהם אפליקציות שולחות שידורים לעצמן:
תקשורת בין רכיבים באותו תהליך: לדוגמה, העברת אירועים או נתונים בין פעילויות, קטעים, שירותים או שרשורים ברקע. במקום לשלוח שידורים, השתמשו במנגנוני תקשורת סטנדרטיים בתהליך, כמו תבנית הצופה או זרמים תגובתיים:
- Kotlin Flows (
SharedFlowו-StateFlow): פתרון מודרני ואידיומטי ב-Kotlin להנפקה ולצפייה בסטרימינג של אירועים או עדכוני סטטוס בשגרות המשך (coroutines) וברכיבים באפליקציה. ViewModelמשותף: מאפשר לשתף נתונים ואירועים בין רכיבים שונים של ממשק המשתמש (כמו קטעים או רכיבים שאפשר לשלב) באותה פעילות.- התקשרות חוזרת (callback) ומאזינים (listeners): התקשרות חוזרת בממשק רגיל או הפניות לפונקציות שמועברות ישירות בין רכיבים או נרשמות במאגר מרכזי או בבקר.
- Kotlin Flows (
טיפול באירועי מערכת, במשימות או בהתראות: לדוגמה, קבלת משימה מתוזמנת, התראה או קריאה חוזרת (callback) של המערכת, ואז שליחת שידור כדי להפעיל את העבודה בפועל. במקום לשלוח שידור, צריך להשלים את העבודה ישירות בתוך המשימה (כמו
JobServiceאו רכיב של WorkManager), בתוך handler של התראות או בתוך רכיב של קריאה חוזרת של המערכת, או להעביר את העבודה ישירות למחלקות של הלוגיקה העסקית של האפליקציה.
במכשירים עם Android 17 QPR2 ואילך, המערכת מעבירה שידורים עצמיים בצורה יעילה יותר על ידי החזרה שלהם לתהליך השליחה, שמעביר אותם למקלטים שלו ב-thread הראשי. שליחת שידור עצמי לא מעלה את רמת החשיבות של התהליך ולא מונעת הקפאה של תהליך שנשמר במטמון, לכן אל תסתמכו על שידורים עצמיים כדי לשמור על פעילות האפליקציה או כדי לבצע עבודה ברקע. במקום זאת, בוחרים את ה-API המתאים למשימה, כפי שמתואר בסקירה הכללית של משימות ברקע. גם אחרי האופטימיזציה הזו, שידורים עצמיים פחות יעילים ממנגנוני תקשורת בתוך התהליך, ולכן מומלץ להשתמש בחלופות האלה במקום זאת.
מידע נוסף על תכנון התקשורת בין רכיבי האפליקציה זמין במדריך לארכיטקטורת אפליקציות.
שיקולי אבטחה
הנה כמה שיקולי אבטחה שקשורים לשליחה ולקבלה של שידורים:
אם הרבה אפליקציות נרשמו לקבל את אותה שידור ב-manifest שלהן, המערכת עלולה להפעיל הרבה אפליקציות, וזה עלול להשפיע באופן משמעותי על הביצועים של המכשיר ועל חוויית המשתמש. כדי להימנע מכך, עדיף להשתמש ברישום הקשר במקום בהצהרה על המניפסט. לפעמים מערכת Android עצמה אוכפת את השימוש במקלטים שרשומים בהקשר. לדוגמה, השידור
CONNECTIVITY_ACTIONנשלח רק למקלטים שרשומים בהקשר.אל תשדרו מידע רגיש באמצעות אובייקט Intent מרומז. כל אפליקציה יכולה לקרוא את המידע אם היא נרשמת לקבלת השידור. יש שלוש דרכים לשלוט במי שיכול לקבל את השידורים שלכם:
- כששולחים שידור, אפשר לציין הרשאה.
- ב-Android מגרסה 4.0 (רמת API 14) ואילך, אפשר לציין חבילה עם
setPackage(String)כששולחים שידור. המערכת מגבילה את השידור לקבוצת האפליקציות שתואמות לחבילה.
כשרושמים מקלט, כל אפליקציה יכולה לשלוח שידורים שעלולים להיות זדוניים למקלט של האפליקציה שלכם. יש כמה דרכים להגביל את השידורים שהאפליקציה מקבלת:
- אפשר לציין הרשאה כשרושמים מקלט שידור.
- למקבלים שהוגדרו במניפסט, אפשר להגדיר את המאפיין android:exported במניפסט לערך false. המקלט לא מקבל שידורים ממקורות מחוץ לאפליקציה.
מרחב השמות של פעולות השידור הוא גלובלי. חשוב לוודא ששמות הפעולות ומחרוזות אחרות כתובים במרחב שמות שבבעלותכם. אחרת, יכול להיות שתהיה התנגשות לא מכוונת עם אפליקציות אחרות.
השיטה
onReceive(Context, Intent)של מקלט פועלת בשרשור הראשי, ולכן היא צריכה לפעול ולהחזיר ערך במהירות. אם אתם צריכים לבצע עבודה ממושכת, היזהרו ביצירת שרשורים או בהפעלת שירותים שפועלים ברקע, כי המערכת יכולה להרוג את כל התהליך אחרי ש-onReceive()חוזר. למידע נוסף, קראו את המאמר ההשפעה על מצב התהליך. כדי לבצע עבודה ממושכת, מומלץ:- קריאה ל-
goAsync()ב-methodonReceive()של המקלט והעברת ה-BroadcastReceiver.PendingResultל-thread ברקע. כך השידור יישאר פעיל אחרי החזרה מ-onReceive(). עם זאת, גם בגישה הזו המערכת מצפה שתסיימו את השידור מהר מאוד (תוך פחות מ-10 שניות). היא מאפשרת להעביר עבודה ל-thread אחר כדי למנוע תקלות ב-thread הראשי. - תזמון משימה באמצעות
JobScheduler. מידע נוסף זמין במאמר תזמון משימות חכם.
- קריאה ל-
אל תתחילו פעילויות ממקלטי שידור כי חוויית המשתמש לא נעימה, במיוחד אם יש יותר ממקלט אחד. במקום זאת, כדאי להציג התראה.