יום חמישי, נובמבר 25

Broadcast Receiver

ה-Broadcast Receiver הוא האמצאי בעזרתו אפשר לממש יכולות Event Driven באנדרואיד. נסביר כאן איך עושים את זה.

המושג Broadcast Receiver הוזכר כאן לפחות פעם אחת במסגרת הדיון על  ה-Intent. ה-BroadcastReceiver הוא class במערכת האנדרואיד שעיקר מהותו היא האזנה ל-intent שנשלחים במערכת. בד"כ ההאזנה מלווה בפיענוח סוג ההודעה והפנייתה לתהליכים המתאימים במערכת בהתאם.

ה- BroadcastReceiver מאזין ל-intent שעונה על הדרישות הבאות:
  1. ה-intent נשלח תוך שימוש במתודה sendBroadcast. 
  2. ה-intent הנ"ל שוייך ל-Broadcast Receiver ע"י פעולת רגיסטרציה\שיוך. נדגים פעולה זו בהמשך.
  3. במידת הצורך, ניתנו ההרשאות עבור ה-Intent בקובץ ה-manifest.xml.
כשמגיע intent שעונה על הקריטריונים הנ"ל, ה-broadcastReceiver נכנס לפעולה. B-R בהאזנה ויכנס  לפעולה גם כאשר האפליקציה אליה הוא שייך אינה בפעולה' והוא יוכל לגרום להפעלת Activities או Services גם אם האפליקציה אינה פעילה. הוא יעשה זאת בד"כ ע"י שליחת intent לאובייקט המתאים. זוהי יכולת ה-Event Driven אותה הזכרנו. אגב, המתודה שנכנסת לפעולה עם הגעת הודעה רלוונטית ל-B-R היא ה-onRecieve.
נציין שפעולת ה-broadcastReceiver צריכה להיות מהירה וקצרה. אחרי 5 שניות מתחילת פעולתו תופיע תצוגת דיאלוג Force Close" על המסך.

עוד קצת על ה-BroadcastReceiver:

  • ה-process שמריץ את ה-BroadcastReceiver הוא BackgroundProcess. 
  • ישנם שני סוגים של הודעות Broadcast:
    • רגיל - Noraml Broadcast: הסדר שבו יופעלו כל המקלטים הקשורים להודעה זו הוא אקראי.
    • מסודר -  Ordered Broadcast: ה-Receivers יטפלו בהודעה לפי הסדר אחד אחרי השני. סדר הפעולה הוא על פי ה-prioriy שנקבע לכל מקלט, ע"ע  android:priority  בקובץ ה-manifest. 



איך מפעילים את מנגנון ה-Broadcast Receiver?

הפעולות אותן צריך לבצע כדי להפעיל את מנגנון ה-Broadcast Receiver הן:
  1. הגדרת ה-receiver ב- manifest.xml.
  2. קביעת פילטרים ל- Intents להם נרצה להאזין. קביעת הפילטרים יכולה להעשות באחת משתי שיטות:
    1.  ב-manifest.xml, בדומה לנעשה עבור implicit intents רגילים.
    2. ב-java ע"י שימוש במתודה המתאימה. 
  3. הכנסת הרשאות לקליטת ה-Intents ב-manifest בהתאם לצורך.
  4.  כתיבת subclass של BroadReceiver.

נפרט ונדגים את הפעולות הנ"ל.
לצורך זה, נציג קטע מקובץ manifest.xml שידגים את פעולות 1-3. הוא מגדיר receiver  ובו 4 פילטרים:
TIME_SET: לקליטת ה-intent שנשלח כתוצאה משינוי השעה בשעון. ראה שורה 3.
BOOT_COMPLETED: לקליטת ה-Intent שנשלח עם סיום ה-BOOT. ראה שורה 8.
TIME_TICK: לקליטת ה-Intent שנשלח כל דקה. ראה שורה 12.
SMS_RECEIVED: לקליטת ה-Intent שנשלח עם הגעת SMS, ומאפשר קריאה וטיפול בו. ראה שורה 15.


  1.             <receiver android:name=".MyBroadcastReceiver">
  2.                 <intent-filter>
  3.                    <action    android:name="android.intent.action.TIME_SET">
  4.                 </action>           
  5.             </intent-filter>
  6.              <intent-filter>
  7.                 <action
  8.                     android:name="android.intent.action.BOOT_COMPLETED">
  9.                 </action>           
  10.             </intent-filter>
  11.             <intent-filter>
  12.                 <action android:name="android.intent.action.TIME_TICK" />
  13.             </intent-filter> 
  14.             <intent-filter>
  15.                 <action android:name="android.provider.Telephony.SMS_RECEIVED" />
  16.             </intent-filter>                   
  17.            </receiver>

  • שורות 1 ו-17 מכילות תגים של receiver ותוחמות את כל המציינים האחרים השייכים ל-receiver. שם ה-receiver הוא שם ה-class קרי: MyBroadcastReceiver.
  • עבור 4 הפילטרים שמוגדרים כאן (שורות 3,8,12,15), נתון ה-action בלבד, ובמקרים שלנו זה מספיק ע"מ לאפיין את ההודעות. ה-Category וה-Data לא משפיעים על פילטרים אלה.
  • כל 4 הפילטרים הנ"ל, מתייחסים להודעות שנשלחות ע"י מערכת האנדרואיד עצמה (ניתן כמובן להגדיר גם פילטרים עבור הודעות שנשלחות מאפליקציות רגילות) . 


 הודעות Broadcast נוספות שנשלחות ע"י מערכת האנדרואיד מובאות ברשימה הבאה:
    • BATERY_LOW - אזהרה על מצב הסוללה.
    • HEADSET_PLUG - חיבור אוזניות.
    • SCREEN_ON- הדלקת הצג.
    • TIMEZON_CHANGED - שינוי איזור שעון.

    לגבי קביעת הפילטרים (נקודה 2 לעי"ל): הזכרנו שניתן לעשות זאת או ע"י הגדרת פילטר ב-manifest או מתוך ה-java. ישנם מקרים בהם ניתן לעשות את זה רק ב-manifest, כמו למשל עבור ה-boot_completed שמתרחש לפני הרצת האפליקציה. ישנם מקרים בהם הגדרה ב-manifest לבדה לא מספקת. למשל - ה TIME_TICK. למה לא ניתן להגדירו ב-manifest? איני יודע, אבל חייבים להפעיל אותו ב-java !(זה עלה לי בלא מעט זיעה להבין את זה). דוגמא לקביעת פילטר ב-java עבור
    Intent עם Action מסוג TIME_TICK.

    1.         registerReceiver(
    2.                    new MyBroadcastReceiver(),
    3.                    new IntentFilter(Intent.ACTION_TIME_TICK)); 
           
     הפרמטרים של המתודה:
    האובייקט של receive Broadcast (שורה 2)
    ה-IntentFilter (שורה 3).

    כעת כשהפילטרים מוגדרים, נעבור לטיפול בהרשאות. ישנם Broadcast Intents שדורשים הרשאה, שמשמעותה הוספת שורה בקובץ ה-manifest, אשר בלעדיה ה-onReceive לא יוכל להאזין להם.
    דוגמא להרשות מתוך ה-manifest.xml שלנו:

    1.      <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    2.       <uses-permission android:name="android.permission.RECEIVE_SMS" />
    שני ה-Broadcast Intents הנ"ל דורשים הרשאה.

    סיימנו עם האדמיניסטרציה הקשורה בקובץ המניפסט, וכעת נותר לגשת ל-java של ה-broadcastFilter.
    הנה דוגמא - מתבססת על הקובץ MyBroadcastReceiver.java:

    1. public class MyBroadcastReceiver extends BroadcastReceiver{
    2.     public void onReceive(Context context, Intent intent) {
    3.         if(intent.getAction().matches("android.intent.action.BOOT_COMPLETED"))
    4.         {
    5.             Log.i(getClass().getSimpleName(),"Action Boot");
    6.         }
    7.         else if (intent.getAction().matches("android.intent.action.TIME_TICK"))
    8.         {
    9.             Log.i(getClass().getSimpleName(),"tick");
    10.         }
    11.         else if (intent.getAction().matches("android.intent.action.TIME_SET"))
    12.             Log.i(getClass().getSimpleName(),"TIME_SET");
    13.         }
    14.         else if (intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) {
    15.             Log.i(getClass().getSimpleName(),"SMS_RECEIVED");
    16.         }
    17.         else
    18.         {
    19.             Log.i(getClass().getSimpleName(), "else ");
    20.         }
    21.     }
    22. }
    הקוד נראה פשוט ואפילו מסביר עצמו. אנסה לעזור לו בכל זאת:
    מוגדר extention class ל- broadcastReceiver - שורה 1.
    בתוך האובייקט  ממומשת מתודה אחת בלבד - בשורה 2: ה-onReceive שמופעל עבור כל intent שמתאים לקריטריונים כפי שפורטו למעלה.
    שאר הקוד בודק את כל אחד מ-4 ה-Intent להם אנו מחכים. במקרה שלנו, כותבים הודעה מתאימה ל-logger.
    נסתכל על אחת מתוך 4 הבדיקות (שורות הקוד 3-6 מלמעלה):
    1. if(intent.getAction().matches("android.intent.action.BOOT_COMPLETED"))
    2.         {
    3.             Log.i(getClass().getSimpleName(),"Action Boot");
    4.         }

       שורה 1 משווה בין ה-action שהתקבל לבין BOOT_COMPLETED. אם יש התאמה - הדפסה ללוגר (שורה 3).


      כמה הערות על פעולת ה-broadcastReceiver:

      • פעולת האובייקט מסוג broadcastReceiver צריכה להיות מהירה וקצרה. אם היא תמשך מעל חמש שניות תתקבל הודעת דיאלוג לסגירת האפליקציה.
      • בגלל פעולתו הקצרה אין להפעיל מתוכו פעילויות שדורשות קבלת משוב למשל: לא startActivityForResult, אלא רק .startActivity
      • עם סיום פעולתו, חוזר אובייקט ה- B.C.R להאזנה. והופך להיות לא פעיל מבחינת המערכת. לכן לא כדאי לעשות מתוכו bind ל-service היות שה- service יפסיק לפעול מיד עם סיום ה-b.c.R. במקום זאת, ה-class של BCR  מכיל את המתודה peekService(Context myContext, Intent)  שתעשה bind רק ל-service שכבר רץ.

       אפליקצית הדוגמא, מאפשרת טיפול ב-4 הודעות שנשלחות ע"י המערכת. אפשר להריץ אותה, לגרום ל-Intents להשלח (מיד נראה איך) ולעקוב בעזרת ה-LogCat אחר הכתיבות ל-logger.
      הוראות להפעלת הארועים שיגרמו לשליחת 4 ה-Intents שלנו:
      BOOT_COMPLETED: זה קל - להפעיל את האמולטור.

      TIME_TICK: גם זה קל. זה קורה כל דקה אוטומטית.

      TIME_SET: להכניס שעה חדשה. 
      מה-command line:
       adb shell
      גם אני לא זכרתי את הפורמט, אז הנה הפקודות שהכנסתי לשינוי השעה:
      # date set ?
      date [-s 20070325.123456] [-u] [date]
      # date -s 20070325.123456
      Sun Mar 25 12:34:56 IST 2007

       

      SMS_RECEIVED: שלח SMS, אפשר מה-eclipse או מה-command line. כדי לשלוח מה-eclipse , הפעל DDMS וגש ל- emulator control - ראה חלון לשליחת SMS בתמונה המצורפת בצד שמאל:


      שליחת sms מה-command line: 
      1. פתח טרמינל\חלון דוס.
      2. telnet localhost 5554. בהנחה שמס הפורט הוא כברירת המחדל - 5554. אחרת - להכניס את מס הפורט המתאים.
      3. הקלד: sms send phone-num text-message

























      3 תגובות:

      1. תודה רבה ההסבר בהיר ועזר לי מאד

        השבמחק
      2. תודה רבה, מאד ברור.
        אתר מקצועי, ובעברית...
        איך אפשר בעזרת Broadcast Receiver לתקשר בין service לאפליקציה?

        השבמחק
      3. תודה!!! אחרי שחרשתי את הרשת וקראתי מלא דוגמאות קוד ואפילו ניסיתי לשאול בSTACKOVERFLOW - אתה הגעת ועשית סדר בכל מה שלא היה ברור ועכשיו הקוד שלי עובד!

        השבמחק