Android BroadcastReceiver(廣播)

[TOC]html

前言

廣播有兩種註冊方式:一種是在清單註冊,這種咱們也稱爲靜態註冊,一種是在運行時調用 registerReceiver() 方法註冊,這種咱們也稱爲動態註冊。java

廣播的發送根據到達順序有兩種不一樣的類型:一種是普通廣播,一種是有序廣播android

廣播的發送根據是否指定接收者也可分爲兩種不一樣的類型:一種是顯示廣播,一種是隱式廣播git

注意: 若是咱們的廣播只是在應用內部使用,那咱們可使用本地廣播。這個實現更加高效(不須要進程間通訊),並且也不須要擔憂任何與其餘應用程序可以接受和發送咱們廣播有關的安全問題。github

BroadcastReceiver 生命週期

生命週期:咱們經過組件發送一個廣播,系統會自動匹配對應的已經註冊的廣播接收者,調用廣播接收者的onReceiver() 方法,廣播接收者 onReceiver() 方法運行完畢後,系統將會認定此廣播接收者對象不在是一個活動的對象,也就會 finished 掉它。 安全

image

注意: 因爲BroadcastReceiver 是運行在ui線程的因此不能再 onReceiver() 方法中執行耗時操做,不然會彈出 ANR(application No Response 應用無響應)的對話框。也不要在onReceiver()方法中啓動線程,而後從函數返回,由於一旦它返回,系統就會認爲BroadcastReceiver不在活動,所以再也不須要它的宿主進程(除非其中的其餘應用程序組件是活動的)。所以,系統可能會在任什麼時候候終止進程已回收內存,這樣作會終止進程中運行的派生線程。app

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

1. 調用 goAsync() 方法,

調用 goAsync() 接收者的 onReceiver() 方法並將 BroadcastReceiver.PendingResult 給後臺線程。這使得廣播在返回後保持活動狀態 onReceive()。可是,即便使用這種方法,系統也但願您可以很是快速地完成廣播(10秒之內)。它運行您將工做移動到另外一個線程,以免阻塞主線程。 示例以下:ide

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…

相關文章
相關標籤/搜索