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

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

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

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

מידע על שידורים במערכת

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

האובייקט Intent עוטף את ההודעה לשידור. מחרוזת action מזהה את האירוע שהתרחש, למשל android.intent.action.AIRPLANE_MODE. יכול להיות שהכוונה תכלול גם מידע נוסף שמאוגד בשדה extra שלה. לדוגמה, כוונת הפעולה Airplane Mode כוללת תוסף בוליאני שמציין אם מצב טיסה מופעל או לא.

מידע נוסף על קריאת כוונות וקבלת מחרוזת הפעולה מכוונה זמין במאמר Intents and Intent Filters.

פעולות שידור מערכת

רשימה מלאה של פעולות שידור במערכת מופיעה בקובץ 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) או גרסה מתקדמת יותר, המערכת לא כוללת שידורי Wi-Fi של מזהי SSID, מזהי BSSID, פרטי חיבור או תוצאות סריקה. כדי לקבל את המידע הזה, צריך להתקשר למספר 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, תקבלו שידורים כל עוד הפעילות נשארת פעילה. אם נרשמים באמצעות הקשר של האפליקציה, מקבלים שידורים כל עוד האפליקציה פועלת.

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

  1. בקובץ build ברמת המודול של האפליקציה, כוללים את גרסה 1.9.0 ואילך של ספריית AndroidX Core:

    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")
    }
  2. יצירת מופע של BroadcastReceiver:

    Kotlin

    val myBroadcastReceiver = MyBroadcastReceiver()
    

    Java

    MyBroadcastReceiver myBroadcastReceiver = new MyBroadcastReceiver();
    
  3. יצירת מופע של IntentFilter:

    Kotlin

    val filter = IntentFilter("com.example.snippets.ACTION_UPDATE_DATA")
    

    Java

    IntentFilter filter = new IntentFilter("com.example.snippets.ACTION_UPDATE_DATA");
    
  4. בוחרים אם לייצא את מקלט השידור כך שיהיה גלוי לאפליקציות אחרות במכשיר. אם הרכיב הזה של מקלט מאזין לשידורים שנשלחים מהמערכת או מאפליקציות אחרות – אפילו מאפליקציות אחרות שבבעלותכם – צריך להשתמש בדגל 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;
    
  5. מתקשרים למספר registerReceiver() כדי לרשום את המקלט:

    Kotlin

    ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, receiverFlags)
    

    Java

    ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, receiverFlags);
    
  6. כדי להפסיק לקבל שידורים, צריך להתקשר למספר unregisterReceiver(android.content.BroadcastReceiver). חשוב לבטל את הרישום של המקלט כשכבר לא צריך אותו או כשההקשר כבר לא תקף.

ביטול הרישום של מקלט השידורים

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

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 או פעילות onResume/onPause שיטות מחזור חיים: מקלט השידור מקבל עדכונים רק כשהאפליקציה במצב 'המשך'.
  • ‫LifecycleStartEffect או פעילות onStart/onStop שיטות מחזור חיים: מקלט השידור מקבל עדכונים רק כשהאפליקציה במצב 'המשך'.
  • ‫DisposableEffect: מקלט השידור מקבל עדכונים רק כשהרכיב ניתן להרכבה נמצא בעץ ההרכבה. ההיקף הזה לא מצורף להיקף של מחזור החיים של הפעילות. מומלץ לרשום את המקלט בהקשר של האפליקציה. הסיבה לכך היא שהרכיב הקומפוזבילי יכול באופן תיאורטי להיות פעיל אחרי סיום מחזור החיים של הפעילות, ולגרום לדליפת הפעילות.
  • פעילות 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
}

מקבלים שהוגדרו במניפסט

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

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

  1. מציינים את הרכיב <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 מציינים את פעולות השידור שהמקלט שלכם נרשם אליהן.

  2. מחלקת משנה 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 כשהאפליקציה מותקנת. ה-receiver הופך לנקודת כניסה נפרדת לאפליקציה, כלומר המערכת יכולה להפעיל את האפליקציה ולשלוח את השידור אם האפליקציה לא פועלת.

המערכת יוצרת אובייקט רכיב חדש של BroadcastReceiver כדי לטפל בכל שידור שהיא מקבלת. האובייקט הזה תקף רק למשך השיחה עם onReceive(Context, Intent). אחרי שהקוד חוזר מהשיטה הזו, המערכת מחשיבה את הרכיב כלא פעיל.

השפעות על מצב התהליך

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

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

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

לדוגמה, נניח שאפליקציית היעד מכילה רכיב 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=".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) לשליחת הודעות בין אפליקציות שונות או בין המערכת לאפליקציות. שליחת שידור עצמי, שהוא שידור שכל הנמענים שלו פועלים באותו תהליך שבו נשלח השידור, היא לא יעילה, יוצרת עומס מיותר על המערכת ולא מומלצת.

שני תרחישים נפוצים שבהם אפליקציות שולחות שידורים לעצמן:

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

    • ‫Kotlin Flows (SharedFlow ו-StateFlow): פתרון מודרני וייחודי ב-Kotlin לפליטה ולמעקב אחרי זרמי אירועים או עדכוני סטטוס בשגרות משנה וברכיבים באפליקציה.
    • Shared ViewModel: מאפשר שיתוף נתונים ואירועים בין רכיבי ממשק משתמש שונים (כמו fragments או composables) באותה פעילות.
    • התקשרות חוזרת (callback) ומאזינים (listeners): התקשרות חוזרת בממשק רגיל או הפניות לפונקציות שמועברות ישירות בין רכיבים או נרשמות במאגר מרכזי או בבקר.
  • טיפול באירועים, במשימות או בהתראות של המערכת: לדוגמה, קבלת משימה מתוזמנת, התראה או קריאה חוזרת של המערכת, ואז שליחת שידור כדי להפעיל את העבודה בפועל. במקום לשלוח שידור, צריך להשלים את העבודה ישירות בתוך המשימה (כמו JobService או רכיב של WorkManager), בתוך handler של התראות או בתוך רכיב של קריאה חוזרת של המערכת, או להעביר את העבודה ישירות למחלקות של הלוגיקה העסקית של האפליקציה.

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

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

שיקולי אבטחה

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

  • אם הרבה אפליקציות נרשמו לקבל את אותה שידור ב-manifest שלהן, המערכת עלולה להפעיל הרבה אפליקציות, וזה עלול להשפיע באופן משמעותי על הביצועים של המכשיר ועל חוויית המשתמש. כדי להימנע מכך, עדיף להשתמש ברישום הקשר במקום בהצהרה על המניפסט. לפעמים, מערכת Android עצמה אוכפת את השימוש במקלטים שנרשמו בהקשר. לדוגמה, שידור CONNECTIVITY_ACTION מועבר רק למקבלי שידור שרשומים בהקשר.

  • אל תשדרו מידע רגיש באמצעות אובייקט Intent מרומז. כל אפליקציה יכולה לקרוא את המידע אם היא נרשמת לקבלת השידור. יש שלוש דרכים לקבוע מי יוכל לקבל את השידורים שלכם:

    • כששולחים שידור, אפשר לציין הרשאה.
    • ב-Android 4.0 (רמת API‏ 14) ומעלה, אפשר לציין חבילה באמצעות setPackage(String) כששולחים שידור. המערכת מגבילה את השידור לקבוצת האפליקציות שתואמות לחבילה.
  • כשרושמים מקלט, כל אפליקציה יכולה לשלוח שידורים שעלולים להיות זדוניים למקלט של האפליקציה שלכם. יש כמה דרכים להגביל את השידורים שהאפליקציה מקבלת:

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

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

    • התקשרות אל goAsync() בשיטת onReceive() של המקלט והעברת BroadcastReceiver.PendingResult לשרשור ברקע. כך השידור יישאר פעיל גם אחרי שתחזרו מonReceive(). עם זאת, גם בגישה הזו המערכת מצפה שתסיימו את השידור מהר מאוד (תוך פחות מ-10 שניות). היא מאפשרת להעביר עבודה ל-thread אחר כדי למנוע תקלות ב-thread הראשי.
    • תזמון משימה באמצעות JobScheduler. מידע נוסף זמין במאמר תזמון חכם של משימות.
  • אל תתחילו פעילויות מ-broadcast receivers כי חוויית המשתמש תהיה לא נעימה, במיוחד אם יש יותר מ-receiver אחד. במקום זאת, כדאי להציג התראה.