Android BroadcastReceiver(廣播)

醉過才知酒濃發表於2019-02-28

[TOC]

前言

廣播有兩種註冊方式:一種是在清單註冊,這種我們也稱為靜態註冊,一種是在執行時呼叫 registerReceiver() 方法註冊,這種我們也稱為動態註冊。

廣播的傳送根據到達順序有兩種不同的型別:一種是普通廣播,一種是有序廣播

廣播的傳送根據是否指定接收者也可分為兩種不同的型別:一種是顯示廣播,一種是隱式廣播

注意: 如果我們的廣播只是在應用內部使用,那我們可以使用本地廣播。這個實現更加高效(不需要程式間通訊),而且也不需要擔心任何與其他應用程式能夠接受和傳送我們廣播有關的安全問題。

BroadcastReceiver 生命週期

生命週期:我們通過元件傳送一個廣播,系統會自動匹配對應的已經註冊的廣播接收者,呼叫廣播接收者的onReceiver() 方法,廣播接收者 onReceiver() 方法執行完畢後,系統將會認定此廣播接收者物件不在是一個活動的物件,也就會 finished 掉它。

image

注意: 由於BroadcastReceiver 是執行在ui執行緒的所以不能再 onReceiver() 方法中執行耗時操作,否則會彈出 ANR(application No Response 應用無響應)的對話方塊。也不要在onReceiver()方法中啟動執行緒,然後從函式返回,因為一旦它返回,系統就會認為BroadcastReceiver不在活動,因此不再需要它的宿主程式(除非其中的其他應用程式元件是活動的)。因此,系統可能會在任何時候終止程式已回收記憶體,這樣做會終止程式中執行的派生執行緒。

那我們需要在BroadcastReceiver中完成一項耗時操作有沒有什麼辦法呢,這個是有的官方提供了兩種方法:

1. 呼叫 goAsync() 方法,

呼叫 goAsync() 接收者的 onReceiver() 方法並將 BroadcastReceiver.PendingResult 給後臺執行緒。這使得廣播在返回後保持活動狀態 onReceive()。但是,即使使用這種方法,系統也希望您能夠非常快速地完成廣播(10秒以內)。它執行您將工作移動到另一個執行緒,以避免阻塞主執行緒。 示例如下:

    class IBroadcastReceiverTask : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        KLog.i("IBroadcastReceiverTask")
        
        //避免卡死的發生
        val pendingResult = goAsync()
        val task = Task(pendingResult, intent)
        //執行非同步任務
        task.execute()
     
    }



    private class Task(private val pendingResult:PendingResult,
            private val intent: Intent?
    ):AsyncTask<String,String,String>(){
        override fun doInBackground(vararg params: String?): String {
            KLog.i("doInBackground")
            ssss()
            return toString()
        }

        override fun onPostExecute(result: String?) {
            super.onPostExecute(result)
            //必須呼叫 finish() 這樣 BroadcastReceiver 才能被回收
            pendingResult.finish()
            KLog.i("完成耗時操作")
        }

    }
}

 fun ssss(){
    var time = 30
    do {
        Thread.sleep(1000)
        KLog.i(time)
        time--
    }while (time!=0)
}
複製程式碼

上面是第一種方法的示例

2. 使用JobService

用JobScheduler JobService,這樣系統就知道在這個過程中還有活動的工作要做。 示例如下:

class IBroadcastReceiverJobService: BroadcastReceiver() {

    var JOB_TEST =10001
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    override fun onReceive(context: Context?, intent: Intent?) {
        KLog.i("IBroadcastReceiverJobService")
        val jobScheduler:JobScheduler = context?.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler

        val jobInfo = JobInfo.Builder(JOB_TEST, ComponentName(context.packageName, MyJobService::class.java.getName()))
                .setPeriodic(AlarmManager.INTERVAL_FIFTEEN_MINUTES)
                .setPersisted(false)
                .build()
        jobScheduler.schedule(jobInfo)
        KLog.i("IBroadcastReceiverJobService   完成")
    }
}

@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class MyJobService : JobService(){
    override fun onStopJob(params: JobParameters?): Boolean {
        KLog.i("onStopJob ===> 結束")
        return false
    }

    override fun onStartJob(params: JobParameters?): Boolean {
        KLog.i("onStartJob ===> 開始")

       object : AsyncTask<String, String, String>() {
            override fun doInBackground(vararg params: String?): String {
                ssss()
                return toString()
            }

           override fun onPostExecute(result: String?) {
               super.onPostExecute(result)
               KLog.i("onPostExecute ===> 開始")
               this@MyJobService.jobFinished(params,false)
           }

        }.execute()


        KLog.i("onStartJob ===> 結束")
        return false
    }

}
複製程式碼

這裡需要注意一下在 AndroidManifest.xml 中宣告JobService 的時候一定要加上 "android.permission.BIND_JOB_SERVICE" 這個許可權,不然執行時會報錯。 宣告如下:

<service
        android:permission="android.permission.BIND_JOB_SERVICE"
        android:name=".MyJobService"/>
複製程式碼

還有就是JobService 是在Android 5.0(API級別21)才加入的 注意適配低版本。

想要知道更詳細的JobScheduler 用法 請檢視panzeyong.com/2017/05/21/…

在清單註冊(靜態註冊)

清單註冊:也可稱為靜態註冊,就是在 AndroidManifest.xml 中宣告廣播接收器。此類廣播在應用尚未啟動的時候就可以接受到相應的廣播。
那如何註冊呢,如下:

//建立一個類 繼承BroadcastReceiver
class IBroadcastReceiver1: BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        KLog.i("onReceive ===>  ${javaClass.simpleName}")
        StringBuilder().apply {
            append("Action: ${intent?.action}\n")
            append("URI: ${intent?.toUri(Intent.URI_INTENT_SCHEME)}\n")
            toString().also { log ->
                KLog.d(TAG, log)
                Toast.makeText(context, log, Toast.LENGTH_LONG).show()
            }
        }
    }

    companion object {
        private const val TAG = "IBroadcastReceiver1"
    }
}
複製程式碼

AndroidManifest.xml中用宣告建立的類

     <receiver android:name=".IBroadcastReceiver1" >
            <intent-filter android:priority="666">
                <action android:name="com.hugo.IBroadcastReceiver1"/>
                
            </intent-filter>
        </receiver>
        
複製程式碼

這樣我們就通過清單註冊了一個叫 IBroadcastReceiver1 的廣播。

動態註冊

動態註冊:也就是在Service 或者Activity等元件中,通過Context.registerReceiver()註冊廣播接收器。此類廣播接收器是在應用已經啟動後,通過程式碼進行註冊的。
示例如下:

//建立一個 動作 過濾器
    val filter = IntentFilter()
    //設定監聽 動作  可以設定多個
    filter.addAction(Constants.FILTER_NAME2)
    // 設定優先順序  取值範圍 -1000 到 1000 數值越大 優先順序越高
    filter.priority = 1000
    //註冊廣播監聽器
    registerReceiver(IBroadcastReceiver2(),filter)
複製程式碼

這樣我們就動態的註冊了一個廣播接收者。

注意: 在退出或關閉廣播註冊的元件是,務必呼叫 unregisterReceiver() 方法解除註冊,不是容易造成廣播接收器的洩漏

普通廣播

普通廣播是完全非同步的,可以在同一時刻(邏輯上)被所有的接收者接受到,訊息傳遞的效率比較高,但是缺點是:接收者不能處理結果傳遞給下一個接收者,並且無法終止廣播Intent的傳遞。
那我們怎麼傳送廣播呢 如下例:

        val intent = Intent()
        //設定 廣播動作
        intent.action = Constants.FILTER_NAME2
        //傳送廣播
        sendBroadcast(intent)
        
        日誌
        IBroadcastReceiver2.kt: [ (IBroadcastReceiver2.kt:16)#OnReceive ] onReceive ===>  IBroadcastReceiver2
        IBroadcastReceiver2.kt: [ (IBroadcastReceiver2.kt:21)#OnReceive ] Action: IBroadcastReceiver2
        IBroadcastReceiver3.kt: [ (IBroadcastReceiver3.kt:16)#OnReceive ] onReceive ===>  IBroadcastReceiver3
        IBroadcastReceiver3.kt: [ (IBroadcastReceiver3.kt:24)#OnReceive ] Action: IBroadcastReceiver2
複製程式碼

如上例:呼叫Context.sendBroadcast() 方法,傳送普通廣播,只要是符合Intent中設定的 action 的廣播接收者都可以進行對應的處理。

總結:

  1. 無視優先順序,動態廣播接收器優先於靜態廣播接收器;
  2. 清單註冊:先掃描的優先於後掃描的;
  3. 動態註冊:先註冊優先於後註冊的。

有序廣播

有序廣播:是按照廣播接收者在註冊時設定的優先順序別,依次接收廣播。優先順序高的接收者可以根據不同的需求修改資料或者中斷廣播。

那麼怎麼設定優先順序呢,就在在註冊廣播接收者是 設定 priority的值取值範圍是 -1000到1000,預設值為0。

傳送廣播如下例子:

     val intent = Intent()
    intent.action = Constants.FILTER_NAME2
    sendOrderedBroadcast(intent,null)
    
    日誌如下:
    IBroadcastReceiver2.kt: [ (IBroadcastReceiver2.kt:17)#OnReceive ] onReceive ===>  IBroadcastReceiver2
IBroadcastReceiver2.kt: [ (IBroadcastReceiver2.kt:25)#OnReceive ] Action: IBroadcastReceiver2
IBroadcastReceiver3.kt: [ (IBroadcastReceiver3.kt:16)#OnReceive ] onReceive ===>  IBroadcastReceiver3
IBroadcastReceiver3.kt: [ (IBroadcastReceiver3.kt:18)#OnReceive ] msg ===> IBroadcastReceiver2  這是上一個接收器傳過來的資訊
IBroadcastReceiver3.kt: [ (IBroadcastReceiver3.kt:25)#OnReceive ] Action: IBroadcastReceiver2
複製程式碼

如示例所示 呼叫Context.sendOrderedBroadcast() 方法傳送廣播,系統就會根據廣播接收者的優先順序別依次執行,前面的廣播有權終止廣播,這樣後面的接收者就收不到該廣播了,前面的廣播也可以向後面的廣播接收者傳送資料。

廣播接收者呼叫abortBroadcast() 該方法就可以終止該廣播。

廣播接收者向後傳送資料的方法有 setResultExtras()setResult() 兩個方法。

廣播接收者獲取前面傳送的資料是使用 getResultExtras(true) 該方法。

注意: 以上方法都是在onReceive() 該方法裡呼叫的。

總結:

  1. 優先順序高的先接收;
  2. 同優先順序動態註冊優先於靜態註冊;
  3. 同優先順序同類廣播接收者,靜態註冊:先掃描優先於後掃描的,動態註冊 :先註冊優先於後註冊的。

本地廣播

本地廣播:顧名思義就是該廣播只在應用內部有用。

本地廣播主要使用LocalBroadcastManager 類中的方法:

  1. registerReceiver():這個是用與註冊廣播接收器的。
  2. sendBroadcast():這個是用於傳送廣播的。
  3. sendBroadcastSync():這個和 sendBroadcast()方法類似,但是如果有用於Intent的接收器,該函式將阻塞並在返回之前立即分派它們。
  4. unregisterReceiver():這個方法是用於解除註冊用的。

以上就是本地廣播所有方法了。使用方法和前面介紹的基本一樣。

顯示廣播

顯示廣播:傳送的Intent是顯示Intent的廣播,同過指定Intent元件名稱來實現,它一般用在知道目標元件名稱的前提下,意圖明確,指定了啟用的廣播接收者,所以一般是在應用內部使用。

示例如下:


val intent = Intent()    
intent.setClass(this,IBroadcastReceiver1::class.java)
sendBroadcast(intent)


val intent = Intent(this,IBroadcastReceiver1::class.java)
sendBroadcast(intent)


val intent = Intent() 
intent.setClassName(this,IBroadcastReceiver1::class.java.name)
sendBroadcast(intent)


//上面三種方法的內部實現都是 建立了ComponentName物件 並賦值給intent.component
val intent = Intent() 
intent.component = ComponentName(this,IBroadcastReceiver1::class.java)
sendBroadcast(intent)
    
複製程式碼

以上就是顯示廣播的使用方法。

隱式廣播

隱式廣播 :就是同過過濾器(Intent Filter)來實現,它一般是沒有指出廣播接收者的名稱,Android系統會根據隱式意圖中設定的動作(action)、類別(category)、資料(URL和資料型別)進行匹配找到最適合的接收者來處理這個意圖。這個一般用於不同應用之間。

示例如下:

//註冊接收者
        val filter = IntentFilter()
  //設定優先順序
    filter.priority = 666
    //設定資料型別
    filter.addDataType("text/plain")
    //設定URL
    filter.addDataScheme("https")
    // 設定類別
    filter.addCategory("android.intent.category.DEFAULT")
    registerReceiver(IBroadcastReceiver4(),filter)

//發出隱式廣播  
     val intent = Intent()
    intent.action = Constants.FILTER_NAME2
    //設定類別
    intent.addCategory("android.intent.category.DEFAULT")
    //設定資料型別   
    //intent.type = "text/plain"
    //設定URL
    //intent.data = Uri.parse("https://www.baidu.com")
    // 設定 資料型別和URL
     intent.setDataAndType(Uri.parse("https://www.baidu.com"),"text/plain")
            sendBroadcast(intent)
複製程式碼

上面就是隱式廣播了。

注意:

  1. 在Android 7.0(API級別24)開始系統對廣播做了一些限制:
    1. 不能傳送 ACTION_NEW_PICTUREACTION_NEW_VIDEO廣播。
    2. 必須使用registerReceiver(BroadcastReceiver,IntentFilter)註冊CONNECTIVITY_ACTION廣播。使用靜態宣告接收者無效。
  2. 在Android 8.0(API級別26)開始系統對清單宣告的接收器的限制進一步的增強了,除了少部分隱式廣播不限制外,其他的所有隱式廣播均無法通過 AndroidManifest.xml中宣告,不過可以通過顯示呼叫。官方推薦使用動態註冊對廣播進行註冊。
  3. 在Android 9.0(API級別28)開始, NETWORK_STATE_CHANGED_ACTION 廣播不會收到有關使用者位置或個人身份資料的資訊。此外應用安裝在執行Android9.0或更高版本的裝置上,來著WI-Fi的系統廣播不再包含 SSID、BSSID、連線資訊或掃描結果。需要獲取這些資訊,可以呼叫 getConnectionInfo()獲取。

隱式廣播例外情況

關於清單註冊隱式廣播的例外情況: 官網

    // Android 8.0 上不限制的隱式廣播
/**
開機廣播
 Intent.ACTION_LOCKED_BOOT_COMPLETED
 Intent.ACTION_BOOT_COMPLETED
*/
"保留原因:這些廣播只在首次啟動時傳送一次,並且許多應用都需要接收此廣播以便進行作業、鬧鈴等事項的安排。"

/**
增刪使用者
Intent.ACTION_USER_INITIALIZE
"android.intent.action.USER_ADDED"
"android.intent.action.USER_REMOVED"
*/
"保留原因:這些廣播只有擁有特定系統許可權的app才能監聽,因此大多數正常應用都無法接收它們。"
    
/**
時區、ALARM變化
"android.intent.action.TIME_SET"
Intent.ACTION_TIMEZONE_CHANGED
AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED
*/
"保留原因:時鐘應用可能需要接收這些廣播,以便在時間或時區變化時更新鬧鈴"

/**
語言區域變化
Intent.ACTION_LOCALE_CHANGED
*/
"保留原因:只在語言區域發生變化時傳送,並不頻繁。 應用可能需要在語言區域發生變化時更新其資料。"

/**
Usb相關
UsbManager.ACTION_USB_ACCESSORY_ATTACHED
UsbManager.ACTION_USB_ACCESSORY_DETACHED
UsbManager.ACTION_USB_DEVICE_ATTACHED
UsbManager.ACTION_USB_DEVICE_DETACHED
*/
"保留原因:如果應用需要了解這些 USB 相關事件的資訊,目前尚未找到能夠替代註冊廣播的可行方案"

/**
藍芽狀態相關
BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED
BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED
BluetoothDevice.ACTION_ACL_CONNECTED
BluetoothDevice.ACTION_ACL_DISCONNECTED
*/
"保留原因:應用接收這些藍芽事件的廣播時不太可能會影響使用者體驗"

/**
Telephony相關
CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED
TelephonyIntents.ACTION_*_SUBSCRIPTION_CHANGED
TelephonyIntents.SECRET_CODE_ACTION
TelephonyManager.ACTION_PHONE_STATE_CHANGED
TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED
TelecomManager.ACTION_PHONE_ACCOUNT_UNREGISTERED
*/
"保留原因:裝置製造商 (OEM) 電話應用可能需要接收這些廣播"

/**
賬號相關
AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION
*/
"保留原因:一些應用需要了解登入帳號的變化,以便為新帳號和變化的帳號設定計劃操作"

/**
應用資料清除
Intent.ACTION_PACKAGE_DATA_CLEARED
*/
"保留原因:只在使用者顯式地從 Settings 清除其資料時傳送,因此廣播接收器不太可能嚴重影響使用者體驗"
    
/**
軟體包被移除
Intent.ACTION_PACKAGE_FULLY_REMOVED
*/
"保留原因:一些應用可能需要在另一軟體包被移除時更新其儲存的資料;對於這些應用,尚未找到能夠替代註冊此廣播的可行方案"

/**
外撥電話
Intent.ACTION_NEW_OUTGOING_CALL
*/
"保留原因:執行操作來響應使用者打電話行為的應用需要接收此廣播"
    
/**
當裝置所有者被設定、改變或清除時發出
DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED
*/
"保留原因:此廣播傳送得不是很頻繁;一些應用需要接收它,以便知曉裝置的安全狀態發生了變化"
    
/**
日曆相關
CalendarContract.ACTION_EVENT_REMINDER
*/
"保留原因:由日曆provider傳送,用於向日歷應用釋出事件提醒。因為日曆provider不清楚日曆應用是什麼,所以此廣播必須是隱式廣播。"
    
/**
安裝或移除儲存相關廣播
Intent.ACTION_MEDIA_MOUNTED
Intent.ACTION_MEDIA_CHECKING
Intent.ACTION_MEDIA_EJECT
Intent.ACTION_MEDIA_UNMOUNTED
Intent.ACTION_MEDIA_UNMOUNTABLE
Intent.ACTION_MEDIA_REMOVED
Intent.ACTION_MEDIA_BAD_REMOVAL
*/
"保留原因:這些廣播是作為使用者與裝置進行物理互動的結果:安裝或移除儲存卷或當啟動初始化時(當可用卷被裝載)的一部分傳送的,因此它們不是很常見,並且通常是在使用者的掌控下"

/**
簡訊、WAP PUSH相關
Telephony.Sms.Intents.SMS_RECEIVED_ACTION
Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION

注意:需要申請以下許可權才可以接收
"android.permission.RECEIVE_SMS"
"android.permission.RECEIVE_WAP_PUSH"
*/
"保留原因:SMS簡訊應用需要接收這些廣播"

複製程式碼

參考連結

panzeyong.com/2017/05/21/…

juejin.im/post/5aefd2…

developer.android.com/guide/compo…

本文demo 連結 github.com/tao11122233…

相關文章