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

14
Dịch vụ dạy kèm gia sư lập trình
Bài này thuộc phần 12 của 16 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
Dịch vụ phát triển ứng dụng mobile giá rẻ - chất lượng
Bài trướcHướng dẫn tích hợp Google Drive vào ứng dụng từ A-Z – Phần 3
Bài tiếp theoMiễn phí sách đỉnh về Marketing cho Mobile App
Sơn Dương
Tên đầy đủ là Dương Anh Sơn. Tốt nghiệp ĐH Bách Khoa Hà Nội. Mình bắt đầu nghiệp coder khi mà ra trường chẳng xin được việc đúng chuyên ngành. Mình tin rằng chỉ có chia sẻ kiến thức mới là cách học tập nhanh nhất. Các bạn góp ý bài viết của mình bằng cách comment bên dưới nhé !

14
Bình luận. Cùng nhau thảo luận nhé!

avatar
  Theo dõi bình luận  
Mới nhất Cũ nhất Nhiều voted nhất
Thông báo
Quang Dương
Guest
Quang Dương

Mình nghĩ trang web kiến thức nên có thêm dòng thông tin bài đăng được viết trong thời gian nào, để người đọc dễ theo dõi , vì kiến thức thay đổi khá nhanh, cảm ơn bạn vì bài viết.

Cường
Guest
Cường

Nhờ a hướng dẫn dùm e, e muốn viết 1 app widget, nhiệm vụ của nó chỉ là: kết nối bluetooth vào 1 địa chỉ đã lưu, nếu kết nối được, thì hiện chuổi nhận được từ bluetooth vào textview sẽ được gửi 1s/1 lần. Nếu không kết nối được thì thử kết nối lại mỗi 5ph.
E cảm ơn a rất nhiều

Huyen
Guest
Huyen

Có 2 lớp broadcast receiver chính là gì vậy ạ?