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

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

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

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

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

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

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

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

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

לרשימה מלאה של פעולות השידור של המערכת: קובץ BROADCAST_ACTIONS.TXT ב-Android SDK. לכל פעולת שידור יש שדה קבוע שמשויך אליו. לדוגמה, הערך של הקבוע ACTION_AIRPLANE_MODE_CHANGED הוא android.intent.action.AIRPLANE_MODE. תיעוד לכל פעולת שידור זמין בשדה הקבוע המשויך אליו.

שינויים בשידורי המערכת

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

Android 14

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

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

9 Android

החל מ-Android 9 (רמת API 28), NETWORK_STATE_CHANGED_ACTION השידור אינו מקבל מידע על מיקום המשתמש או באופן אישי נתונים שמאפשרים זיהוי של משתמשים.

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

קבלת שידורים

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

נמענים שהוצהרו כמניפסטים

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

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

  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="APP_SPECIFIC_BROADCAST" />
        </intent-filter>
    </receiver>
    

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

  2. מחלקה משנית BroadcastReceiver והטמעה של onReceive(Context, Intent). של מקלט השידורים ביומנים לדוגמה הבאים, ומציג את התוכן של השידור:

    Kotlin

    private const val TAG = "MyBroadcastReceiver"
    
    class MyBroadcastReceiver : BroadcastReceiver() {
    
        override fun onReceive(context: Context, intent: Intent) {
            StringBuilder().apply {
                append("Action: ${intent.action}\n")
                append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n")
                toString().also { log ->
                    Log.d(TAG, log)
    
                    val binding = ActivityNameBinding.inflate(layoutInflater)
                    val view = binding.root
                    setContentView(view)
    
                    Snackbar.make(view, log, Snackbar.LENGTH_LONG).show()
                }
            }
        }
    }
    

    Java

    public class MyBroadcastReceiver extends BroadcastReceiver {
            private static final String TAG = "MyBroadcastReceiver";
            @Override
            public void onReceive(Context context, Intent intent) {
                StringBuilder sb = new StringBuilder();
                sb.append("Action: " + intent.getAction() + "\n");
                sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
                String log = sb.toString();
                Log.d(TAG, log);
    
                ActivityNameBinding binding =
                        ActivityNameBinding.inflate(layoutInflater);
                val view = binding.root;
                setContentView(view);
    
                Snackbar.make(view, log, Snackbar.LENGTH_LONG).show();
            }
        }
    

    כדי להפעיל קישור תצוגות מפורטות: הגדרת viewBinding ברמת המודול build.gradle.

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

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

נמענים שרשומים לפי הקשר

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

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

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

    מגניב

    dependencies {
        def core_version = "1.13.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.0.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-alpha02"
    }
    

    Kotlin

    dependencies {
        val core_version = "1.13.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.0.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-alpha02")
    }
    
  2. יוצרים מופע של BroadcastReceiver:

    Kotlin

    val br: BroadcastReceiver = MyBroadcastReceiver()
    

    Java

    BroadcastReceiver br = new MyBroadcastReceiver();
    
  3. יוצרים מופע של IntentFilter:

    Kotlin

    val filter = IntentFilter(APP_SPECIFIC_BROADCAST)
    

    Java

    IntentFilter filter = new IntentFilter(APP_SPECIFIC_BROADCAST);
    
  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;
    if (listenToBroadcastsFromOtherApps) {
        receiverFlags = ContextCompat.RECEIVER_EXPORTED;
    } else {
        receiverFlags = ContextCompat.RECEIVER_NOT_EXPORTED;
    }
    
  5. כדי לרשום את המקבל צריך להתקשר registerReceiver():

    Kotlin

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

    Java

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

    שימו לב איפה נרשמים ומבטלים את הרישום של המקבל, לדוגמה, אם תרשום מקבל בonCreate(Bundle) לפי ההקשר של הפעילות, עליך צריך לבטל את הרישום ב-onDestroy() כדי למנוע הדלפה של המקבל מחוץ להקשר של הפעילות. אם תירשמו מקלט בonResume(), צריך לבטל את הרישום ב-onPause() כדי לרשום אותו מספר פעמים (אם אינך רוצה לקבל שידורים כאשר תשהו אותם, וכך תפחיתו תקורה מיותרת של המערכת). לא מומלץ ביטול הרישום ב-onSaveInstanceState(Bundle), כי לא מתבצעת קריאה לכך אם המשתמש חוזר בערימת ההיסטוריה.

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

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

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

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

שליחת שידורים

ב-Android יש שלוש דרכים שבהן אפליקציות יכולות לשלוח שידור:

  • sendOrderedBroadcast(Intent, String) שולחת שידורים למקלט אחד בכל פעם. בזמן שכל מקלט מבצע בתורו, הוא יכול להפיץ את התוצאה למקבל הבא, או לבטל לגמרי את השידור כדי שלא יועבר לערוץ אחר הנמען. אפשר לשלוט במקבלי ההזמנות שפועלים באמצעות המאפיין android:Priorityity של מסנן ה-Intent התואם; של מקלטים אותה עדיפות תפעל בסדר שרירותי.
  • השיטה sendBroadcast(Intent) שולחת שידורים לכל המקלטים לפי סדר לא מוגדר. הפעולה הזו נקראת 'רגילה' שליחת הודעה לכולם. פעולה זו יעילה יותר, אבל המשמעות היא שהנמענים לא יכולים לקרוא תוצאות ממקלטים אחרים, הפצת נתונים שהתקבלו מהשידור, או לבטל את השידור.

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

Kotlin

Intent().also { intent ->
    intent.setAction("com.example.broadcast.MY_NOTIFICATION")
    intent.putExtra("data", "Nothing to see here, move along.")
    sendBroadcast(intent)
}

Java

Intent intent = new Intent();
intent.setAction("com.example.broadcast.MY_NOTIFICATION");
intent.putExtra("data", "Nothing to see here, move along.");
sendBroadcast(intent);

הודעת השידור מוקפת באובייקט Intent. מחרוזת הפעולה של ה-Intent חייבת לספק את התחביר של שם חבילת ה-Java של האפליקציה, וגם לזהות באופן ייחודי את אירוע השידור. אפשר לצרף מידע נוסף ל-Intent עם putExtra(String, Bundle). אפשר גם להגביל את השידור לקבוצה של אפליקציות באותו ארגון. כדי לעשות את זה, קריאה אל setPackage(String) מתוך כוונה.

הגבלת שידורים עם הרשאות

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

שליחה עם הרשאות

כשמתקשרים אל sendBroadcast(Intent, String) או sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle), אפשר לציין פרמטר הרשאה. רק נמענים שביקשו את ההרשאה הזו עם את התג במניפסט שלהם (ולאחר מכן קיבל אם הוא מסוכן) יכול לקבל את השידור. לדוגמה, הקוד הבא שולח שידור:

Kotlin

sendBroadcast(Intent(BluetoothDevice.ACTION_FOUND),
              Manifest.permission.BLUETOOTH_CONNECT)

Java

sendBroadcast(new Intent(BluetoothDevice.ACTION_FOUND),
              Manifest.permission.BLUETOOTH_CONNECT)

כדי לקבל את השידור, האפליקציה המקבלת צריכה לבקש את ההרשאה הזו: מוצגת למטה:

<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>

אפשר לציין הרשאת מערכת קיימת, כמו BLUETOOTH_CONNECT או להגדיר הרשאה מותאמת אישית עם רכיב <permission>. עבור מידע על הרשאות ואבטחה באופן כללי, ראו מערכת הרשאות.

קבלה עם הרשאות

אם מציינים פרמטר הרשאה כשרושמים מקלט שידורים (עם registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) או בתוך התג <receiver> ), רק רשתות שידור שביקשו הרשאה באמצעות תג <uses-permission> במניפסט שלהם (ולאחר מכן קיבל את ההרשאה אם הוא מסוכן) יכול לשלוח Intent למקבל.

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

<receiver android:name=".MyBroadcastReceiver"
          android:permission="android.permission.BLUETOOTH_CONNECT">
    <intent-filter>
        <action android:name="android.intent.action.ACTION_FOUND"/>
    </intent-filter>
</receiver>

לחלופין, לאפליקציה המקבלת יש מקלט רשום לפי הקשר כפי שמוצג בהמשך:

Kotlin

var filter = IntentFilter(Intent.ACTION_FOUND)
registerReceiver(receiver, filter, Manifest.permission.BLUETOOTH_CONNECT, null )

Java

IntentFilter filter = new IntentFilter(Intent.ACTION_FOUND);
registerReceiver(receiver, filter, Manifest.permission.BLUETOOTH_CONNECT, null );

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

<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>

שיקולי אבטחה ושיטות מומלצות

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

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

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

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

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

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

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