สมมติคุณมีแอปพลิเคชันที่ต้องการการเชื่อมต่ออินเทอร์เน็ตที่เสถียร คุณต้องการให้แอปได้รับการแจ้งเตือนเมื่อการเชื่อมต่ออินเทอร์เน็ตเปลี่ยนแปลง ทำอย่างไร? วิธีหนึ่งคือ service ที่ตรวจสอบ internet connection ตลอดเวลา การ implement แบบนี้แย่มากหลายเหตุผล จึงไม่นำมาพิจารณา วิธีแก้คือ Broadcast Receiver จะฟังการเปลี่ยนแปลงที่คุณบอก Broadcast receiver จะได้รับการแจ้งเตือนของ broadcast เสมอ ไม่ว่าสถานะแอปจะเป็นอย่างไร ไม่ว่าจะกำลังรัน อยู่ใน background หรือไม่ได้รันเลย
พื้นหลัง
Broadcast receivers เป็น components ใน Android app ที่ฟัง broadcast messages (หรือ events) จากแหล่งต่างๆ:
- จากแอปพลิเคชันอื่นๆ
- จากระบบเอง
- จากแอปของคุณเอง
หมายความว่า ถูก invoke เมื่อ action บางอย่างเกิดขึ้นที่พวกเขาเขียนโปรแกรมให้ฟัง (เช่น broadcast)
Broadcast คือ message ห่อหุ้มใน Intent object Broadcast อาจเป็น implicit หรือ explicit
- Implicit broadcast คือ broadcast ที่ไม่ target แอปของคุณโดยเฉพาะ จึงไม่ exclusive กับแอปคุณ เพื่อ register ต้องใช้ IntentFilter และประกาศใน manifest Android OS ไปตรวจสอบ intent filters ที่ประกาศใน manifest ว่ามี match หรือไม่ เนื่องจากพฤติกรรมนี้ implicit broadcasts ไม่มี target attribute ตัวอย่าง implicit broadcast คือ action ของข้อความ SMS เข้า
- Explicit broadcast คือ broadcast ที่ target แอปของคุณโดยเฉพาะบน component ที่รู้ล่วงมานี้ เกิดจาก target attribute ที่มี package name หรือ component class name ของแอป
มี 2 วิธีประกาศ receiver:
- ประกาศใน AndroidManifest.xml ด้วย tag <receiver> (เรียกว่า static)
<receiver android:name=".YourBrodcastReceiverClass" android:exported="true"> <intent-filter> <!-- Actions ที่อยากฟัง ตัวอย่างด้านล่าง --> <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter> </receiver>สังเกต broadcast receiver ข้างต้นมี property exported=”true” attribute นี้บอก receiver ว่าสามารถรับ broadcasts จากนอก scope ของแอปได้
2. หรือ dynamically โดย register instance ด้วย registerReceiver (เรียกว่า context registered)
public abstract Intent registerReceiver (BroadcastReceiver receiver, IntentFilter filter);การ Implement
สร้าง broadcast receiver ของตัวเอง ต้อง extend BroadcastReceiver parent class และ override method บังคับ onReceive:
public void onReceive(Context context, Intent intent) { // Implement logic ของคุณที่นี่ }รวมทั้งหมดเข้าด้วยกัน:
public class MyBroadcastReceiver extends BroadcastReceiver { @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(); Toast.makeText(context, log, Toast.LENGTH_LONG).show(); } }⚠️ Method onReceive รันบน main thread ดังนั้น execution ควรสั้น
ถ้ารัน long process ระบบอาจ kill process หลัง method return เพื่อหลีกเลี่ยง ใช้ goAsync หรือ schedule job อ่านเพิ่มเกี่ยว scheduling job ท้ายบทความ
ตัวอย่าง Dynamic Registration
เพื่อ register receiver กับ context ต้อง instantiate broadcast receiver ก่อน:
BroadcastReceiver myBroadcastReceiver = new MyBroadcastReceiver();จากนั้น register ขึ้นกับ context ที่ต้องการ:
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); this.registerReceiver(myBroadcastReceiver, filter);อย่าลืม unregister broadcast receiver เมื่อไม่ต้องการแล้ว
@Override protected void onStop() { super.onStop(); unregisterReceiver(myBroadcastReceiver); }การ Broadcast Event
จุดประสงค์ broadcasting messages จากแอปคือให้แอปตอบสนองต่อ events ขณะเกิดขึ้นภายในแอป คิดสถานการณ์ ที่ส่วนหนึ่งของโค้ด user ทำ action และเพราะนั้น อยาก execute logic อื่นในตำแหน่งต่าง
มี 3 วิธีส่ง broadcasts:
- Method sendOrderedBroadcast ส่ง broadcasts ให้ receiver ทีละตัว แต่ละ broadcast สามารถส่ง data ให้ตัวถัดไป หรือหยุด propagation ไป receivers ต่อไป
- Method sendBroadcast คล้ายด้านบน แตต่างตรงที่ receivers ทุกตัวรับ message และไม่ depend กัน
- Method LocalBroadcastManager.sendBroadcast ส่ง broadcasts ให้ receivers ที่ define ภายในแอปเท่านั้น ไม่เกิน scope ของแอป ตัวอย่างส่ง custom broadcast
https://giphy.com/gifs/23gUJhHyWkXEwl7UYV/html5

Gotchas และสิ่งที่ต้องระวัง
- อย่าส่ง sensitive data ผ่าน implicit broadcast เพราะแอปไหนฟังก็จะได้รับ ป้องกันได้โดยระบุ package หรือ attach permission กับ broadcast
- อย่า start activities จาก broadcast ที่รับมา เพราะ UX แย่ ควรแสดง notification แทน
bullet points ต่อไปอ้างอิงการเปลี่ยนแปลง broadcast receivers สำหรับแต่ละ Android OS version (ตั้งแต่ 7.0) สำหรับแต่ละ version มี limitations และพฤติกรรมเปลี่ยน จำ limitations เหล่านี้ตอนคิดจะใช้ broadcast receiver
- 7.0 ขึ้นไป (API level 24) - system broadcasts 2 ตัวถูก disable: Action_New_Picture และ Action_New_Video (แต่นำกลับมาใน Android O สำหรับ registered receivers)
- 8.0 ขึ้นไป (API level 26) - implicit broadcasts ส่วนใหญ่ต้อง register dynamically ไม่ใช่ statically (ใน manifest) หา broadcasts ที่ whitelisted ได้ที่ลิงก์นี้
- 9.0 ขึ้นไป (API level 28) - รับข้อมูลน้อยลงบน Wi-Fi system broadcast และ Network_State_Changed_Action
การเปลี่ยนแปลงใน Android O เป็นสิ่งที่ต้องระวังมากที่สุด เหตุผลการเปลี่ยนแปลงเพราะนำไปสู่ performance issues, battery drain และทำลาย UX เกิดจากหลายแอป (แม้ไม่ได้รัน) ฟัง system-wide change และเมื่อ change เกิดขึ้น chaos ensued ทุกแอปที่ register กับ action ก็ wake up เช็คว่าต้องทำอะไรจาก broadcast นำ Wi-Fi state ที่เปลี่ยนบ่อย มาคิด จะเข้าใจว่าทำไมมีการเปลี่ยนแปลงนี้
ทางเลือกแทน Broadcast Receivers
เพื่อ navigate restrictions เหล่านี้ง่ายขึ้น นี่คือ breakdown ของ components อื่นๆ ใช้แทน broadcast receiver แต่ละตัวมี responsibility และ use case แตกต่าง ลอง map ว่าตัวไหนตอบสนองความต้องการคุณ
- LocalBroadcastManager - ตามที่กล่าว ใช้ได้เฉพาะ broadcasts ภายในแอป
- Scheduling A Job - Job รันขึ้นกับ signal หรือ trigger ที่รับมา อาจพบว่า broadcast ที่ฟังอยู่ แทนด้วย job ได้ ยิ่งไปกว่า JobScheduler จะ guarantee job จะเสร็จ แต่คำนึงถึง system factors (เวลา และ conditions) ว่าจะรันเมื่อไหร่ ตอนสร้าง job override method onStartJob method นี้รันบน main thread ให้จบงานในเวลาจำกัด หากต้อง complex logic เริ่ม background task return value เป็น boolean true = actions ยังดำเนินอยู่ false = job เสร็จ
อยากลอง broadcast receivers จริง ตามลิงก์ repositories ที่ผมเตรียมไว้:
- Custom Broadcast (with manifest declaration)
- Registering Broadcast (without declaring one in the manifest)
- LocalBroadcastManager
Broadcast over.