Tất tần tật về Broadcast Receiver trong Android

0
447
Bài này thuộc phần 12 của 13 phần trong series Tự học lập trình Android trong 24 giờ

Bạn đã bao giờ nghe đến “đài phát thanh truyền hình” trong Android chưa?  Nếu chưa thì mình bật mí nhé! Đó chính là Broadcast Receiver. Vậy broadcast receiver android là gì? Đúng như tên gọi của nó, nhiệm vụ chính của Broadcast receiver trong android là lan truyền thông tin trong hệ thống và chỉ những ứng dụng đăng kí mới nhận được.

Bài viết này mình sẽ chia sẻ tất cả những kiến thức quan trọng nhất về Broadcast Receiver Android.

Từ đó bạn có thể dễ dàng ứng dụng và sử dụng hiệu quả cho ứng dụng của mình

broadcast receiver trong android

1. Broadcast Receiver Android là gì?

Android Broadcast Receiver là một thành phần nơi bạn có thể đăng ký sự kiện của hệ thống hay ứng dụng. Bạn sẽ nhận được thông báo về các sự kiện đã đăng ký trước đó. Việc phát tin broadcast có thể bắt nguồn từ hệ thống hoặc từ các ứng dụng

Ví dụ: một số broadcast từ hệ thống như thông báo pin yếu, bật tắt màn hình, kết nối hay ngắt kết thiết bị ngoại vi…

Với cấp độ ứng dụng có thể kể ra như khi bạn download một file nhạc. Ứng dụng nhạc này sẽ nhận được thông báo về việc download, sau khi kết thúc sẽ đưa bài hát này vào danh sách để bật. Để thực hiện được việc này, ứng dụng nhạc cần thiết phải đăng ký cho sự kiện.

Không giống như Activity, Broadcast receiver trong android không hề có giao diện người dùng. Mặc dù nó có thể tạo thông báo trên thanh status bar.

Để truyền thông tin, Broadcast receiver sử dụng Intent để đóng gói dữ liệu. Nếu bạn đã đọc bài viết của mình về Intent trong Android thì sẽ dễ dàng hiểu về Broadcast receiver trong Android.

Bài viết này mình sẽ tập trung 3 vấn đề chính:

  • Cách tạo Broadcast Receiver
  • Hướng dẫn đăng kí để nhận thông báo từ Broadcast Receiver
  • Cách gửi Broadcast Event/Intent
  • Vấn đề bảo mật khi sử dụng Broadcast Receiver

2. Tạo một class Broadcast Receiver trong Android

Hãy theo dõi đoạn code bên dưới để xem cách tạo một broadcast receiver trong Android như nào:

class MyReceiver:BroadcastReceiver() {
  fun onReceive(context:Context, intent:Intent) {
    // This method is called when this BroadcastReceiver receives an Intent broadcast.
    Toast.makeText(context, "Action: " + intent.getAction(), Toast.LENGTH_SHORT).show()
  }
}

Chúng ta sẽ tạo một class là MyReciever và được kế thừa từ BroadcastReceiver. Do BroadcastReceiver là một abstract class nên chúng ta bắt buộc phải override hàm onReceiver(). Bất cứ khi nào có một sự kiện xảy ra, Android sẽ gọi hàm onReceiver().

Bạn để ý rằng, hàm onReceiver() có 2 tham số được truyền vào là: IntentContent. Với Intent, bạn sẽ nhận được những thông tin cần thiết từ hệ thống.

Còn Context sẽ giúp bạn có thể làm được một việc như start một Activity hay Service kiểu như context.startService(new Intent(this, TestService.class));

3. Đăng kí nhận Broadcast Receiver trong Android

Chúng ta đã hoàn thành việc tạo một Broadcast Receiver trong Android, nhưng để nó có thể nhận được thông báo từ hệ thống hay ứng dụng khác thì nó cần phải đăng kí. Có 2 cách đăng kí:

  • Đăng kí bằng cách khai báo trong Manifest file
  • Hoặc đăng kí bằng Kotlin code

#Cách 1: Đăng kí Broadcast Receiver trong Manifest File

Đây là cách có lẽ là dễ nhất

<receiver
    android:name="com.pycitup.pyc.MyReceiver"
    android:enabled="true"
    android:exported="true" >
    <intent-filter>
        <action android:name="com.pycitup.BroadcastReceiver" />
    </intent-filter>
</receiver>

Chúng ta sử dụng thẻ <receiver>để đăng kí với một intent filter.

Nếu bạn quên chưa biết Intent filter là gì thì mời bạn đọc lại bài viết về intent này nhé: Intent trong Android: Vai trò và cách sử dụng

Về cơ bản có thể hiểu nôm na: Ứng dụng sử dụng intent filter để thông báo với hệ thống là ứng dụng của tôi đăng kí nhận thông báo.

Nhưng không phải nhận tất cả mà chỉ những thông báo nào phù hợp thì mới nhận. Ở đây thì chỉ những thông báo nào có action là: com.pycitup.BroadcastReceiverthì mới nhận được

#Cách 2: Sử dụng code để đăng kí Broadcast Receiver

Chúng ta có thể thực hiện việc đăng kí bằng code Kotlin như sau:

var filter = IntentFilter("com.pycitup.BroadcastReceiver")
var myReceiver = MyReceiver()
fun registerReceiver(myReceiver, filter)

Tương tự như cách đăng kí bằng cách khai báo trong Manifest. Chúng ta cũng chỉ đăng kí nhận những thông báo có action là: com.pycitup.BroadcastReceiver.

Cơ bản action này cũng chỉ là một String mà bạn muốn đặt là gì cũng được. Thông thường thì người ta sẽ đặt tên action giống với tên package ứng dụng.

Có một sự khác biết lớn giữa 2 cách đăng kí là:Nếu đăng kí bằng cách sử dụng code thì khi activity bị stop thì việc lắng nghe broadcast cũng bị tạm dừng theo. Trong khi đăng kí qua manifest thì sẽ lắng nghe vĩnh viễn.

Do vậy, nếu bạn đăng kí receiver thông qua code thì nên hủy đăng kí khi activity bị stop. Kiểu như sau

protected fun onPause() {
  unregisterReceiver(mReceiver)
  super.onPause()
}

Vậy cách đăng kí receiver nào là tốt nhất?

Lời khuyên của mình là tùy vào mục đích ứng dụng của bạn mà lựa chọn cách đăng kí cho phù hợp.

Nếu ứng dụng muốn cập nhập ngay trên màn hình điện thoại( như home screen, launcher, status bar, widget…) khi nhận thông báo từ hệ thống hay từ ứng dụng khác thì cách đăng kí qua manifest là thích hợp.

Trong khi cũng nhận những thông báo mà bạn chỉ muốn cập nhập khi người dùng đang ở ứng dụng thì cách đăng kí bằng code là hợp lý hơn. Vì bạn sẽ chủ động đăng kí/ hủy đăng kí để tối ưu hiệu năng hệ thống.

Ngoài ra, có một số trường hợp ngoại lệ như action Intent.ACTION_TIME_TICK là bạn không thể đăng kí trong manifest mà phải đăng kí bằng code. Android làm như vậy là để tiết kiệm pin cho hệ thống.

4. Gửi Broadcast Event/Intent

Chúng ta đã hoàn thành 2/3 công đoạn quan trọng của Broadcast Receiver trong Android. Phần cuối cùng chính là gửi thông báo(Broadcast).

val intent = Intent()
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
intent.setAction("com.pycitup.BroadcastReceiver")
intent.putExtra("Foo", "Bar")
sendBroadcast(intent)

Chúng ta tạo một intent, sau đó thiết lập action cho intent này. Ở đây là com.pycitup.BroadcastReceiver. Chính là action mà chúng ta đã sử dụng để đăng kí ở phần phía trên bài viết. Dữ liệu sẽ được đưa vào intent thông qua hàm putExtra()

Cuối cùng gửi Broadcast bằng hàm sendBroadcast()

Mình giải thích thêm một chút nếu bạn nào để ý trong đoạn code trên có một flag: FLAG_INCLUDE_STOPPED_PACKAGES. Cơ bản thì flag này cho phép ứng dụng vẫn nhận thông báo trong trường hợp ứng dụng được cài đặt nhưng chưa chạy lần nào hoặc bị tắt bởi công cụ quản lý hệ thống như task manager.

Các kiểu gửi Broadcasts

Nhìn chung, có 2 hình thức để gửi broadcasts:

  • Gửi Broadcasts theo cách thông thường: Tức là chúng ta sử dụng hàm Context.sendBradcast() như bạn vừa thấy ở trên. Đây là cách gửi broadcast không đồng bộ.Và phía receiver cũng vậy. Các receiver được chạy theo một thứ tự không xác định, thường là cùng một lúc. Nhược điểm của cách gửi này là các reciever không thể sử dụng kết quả của nhau
  • Gửi Broadcasts theo thứ tự:  Để làm được điều này, chúng ta sử dụng hàm Context.sendOrderedBroadcast() và nó chỉ định rõ receiver được nhận tại một thời điểm. Thứ tự Receiver nhận kiểm soát bởi thuộc tính android:priority. Reciever có cùng mức độ ưu tiên sẽ được thực hiện theo thứ tự ngẫu nhiên. Khi mỗi reciever thực hiện, nó có thể chuyển kết quả đến receiver kế tiếp hoặc hủy bỏ toàn bộ chuỗi broadcasts để không cho reciever khác nhận được

Dưới đây là ví dụ cho cách gửi Broadcast theo thứ tự

// MySecondReceiver.kt
class MySecondReceiver:BroadcastReceiver() {
  private val TAG = MySecondReceiver::class.java!!.getSimpleName()
  fun onReceive(context:Context, intent:Intent) {
    val results = getResultExtras(true)
    results.putString("hierarchy", TAG)
    Log.d(TAG, "MySecondReceiver")
  }
}
// MyReceiver.kt
class MyReceiver:BroadcastReceiver() {
  private val TAG = MyReceiver::class.java!!.getSimpleName()
  fun onReceive(context:Context, intent:Intent) {
    val results = getResultExtras(true)
    val hierarchy = results.getString("hierarchy")
    results.putString("hierarchy", hierarchy + "->" + TAG)
    Log.d(TAG, "MyReceiver")
  }
}

Cuối cùng là khai báo đăng kí trong Manifest

<receiver
    android:name="com.pycitup.pyc.MyReceiver"
    >
    <intent-filter android:priority="1">
        <action android:name="com.pycitup.BroadcastReceiver" />
    </intent-filter>
</receiver>
<receiver
    android:name="com.pycitup.pyc.MySecondReceiver"
    >
    <intent-filter android:priority="2">
        <action android:name="com.pycitup.BroadcastReceiver" />
    </intent-filter>
</receiver>
Lưu ý:
Các bạn để ý đến mức độ ưu tiên của receiver thông qua thẻ android:priority. Mức ưu tiên càng cao thì sẽ được nhận trước tiên. Mặc định, mức độ ưu tiên là 0

Tiếp theo là phần gửi Broadcast trong MainActivity.onCreate ():

val filter = IntentFilter("com.pycitup.BroadcastReceiver")
registerReceiver(object:BroadcastReceiver() {
  fun onReceive(context:Context, intent:Intent) {
    val results = getResultExtras(true)
    val hierarchy = results.getString("hierarchy")
    results.putString("hierarchy", hierarchy + "->" + TAG)
    Log.d(TAG, "Anonymous class broadcast receiver")
  }
}, filter)
val intent = Intent("com.pycitup.BroadcastReceiver")
sendOrderedBroadcast(intent, null, object:BroadcastReceiver() {
  fun onReceive(context:Context, intent:Intent) {
    val results = getResultExtras(true)
    val hierarchy = results.getString("hierarchy")
    println(hierarchy)
    Log.d(TAG, "Final Receiver")
  }
}, null, Activity.RESULT_OK, null, null)

Và đây là kết quả khi bạn chạy ứng dụng

﹕ MySecondReceiver
﹕ MyReceiver
﹕ Anonymous class broadcast receiver
﹕ MySecondReceiver->MyReceiver->MainActivity
﹕ Final Receiver

Như mình nói ở trên, bất kì receiver nào cũng có thể hủy toàn bộ chuỗi Broadcast để không cho các receiver khác nhận được. Đơn giản là gọi hàm abortBroadcast()

5. Local BroadcastManager – Gửi Broadcast trong nội bộ ứng dụng

Như các bạn đã biết thì khi gửi Broadcast thì bất kì ứng dụng nào đăng kí đều nhận được. Đôi khi vì lý do bảo mật cho ứng dụng mà chúng ta không muốn bất kì ứng dụng nào khác nhận được. Những thông tin này chỉ được gửi bên trong ứng dụng.

LocalBroadcast sẽ giúp bạn làm được điều đó.

LocalBroadcastManager.getInstance(this).registerReceiver(object:BroadcastReceiver() {
  fun onReceive(context:Context, intent:Intent) {
    val message = intent.getStringExtra("foo")
    Log.d("LocalBroadcastManager", "foo : " + message)
  }
}, IntentFilter("my-custom-event"))

Còn đây là cách gửi local broadcast

val intent = Intent("my-custom-event")
intent.putExtra("foo", "bar")
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)

Và đừng quên hủy đăng kí trong hàm onPause()của Activity

protected fun onPause() {
  LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver)
  super.onPause()
}

6. Permissions

Có một số Broadcast cần phải yêu cầu permission. Tất nhiên là những permission này phải được sự chấp thuận của người dùng.

Ví dụ:  Intent.ACTION_BOOT_COMPLETEDyêu cầu permission  RECEIVE_BOOT_COMPLETED

Các bạn khai báo permission trong Manifest như sau

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

Tất nhiên, đây chỉ là khai báo thôi, từ Android M trở lên, bạn cần phải viết thêm code để yêu cầu người dùng chấp thuận permission này cho ứng dụng

Mặc dù hầu hết Broadcast không yêu cầu permission, tuy nhiên cẩn tắc vô áy náy, bạn có thể kiểm tra tại đây

7. Tổng kết

Như vậy là chúng ta đã đi qua tất cả các vấn đề của Broadcast Receiver trong Android. Mặc dù BroadcastReceiver không phải là vấn đề khó nhưng cũng có nhiều điểm thú vị phải không?

Bài viết sau chúng ta sẽ cùng nhau tìm hiểu về xử lý đa luồng thông qua thread, Asynctask trong Android

Các bạn đừng quên để lại comment nếu có bất kì thắc mắc nào nhé

Xem tiếp các bài trong Series
Phần trước: Service trong Android là gì? Các loại service trong androidPhần kế tiếp: AsyncTask trong Android – công cụ xử lý đa luồng hữu hiệu

Bình luận. Đặt câu hỏi cũng là một cách học

avatar
  Theo dõi bình luận  
Thông báo