零、前言
1.本文的知識點
1).BroadcastReceiver`靜態`使用
2).BroadcastReceiver`動態`使用
3).BroadcastReceiver`有序`廣播
4).BroadcastReceiver和`系統`行為的結合
5).小例子:使用BroadcastReceiver更新音樂播放器進度條
複製程式碼
2.BroadcastReceiver總覽
現在才發現BroadcastReceiver原來這麼精簡,純原始碼才260
直接繼承Object,沒有實現介面,沒有家庭背景,可以說是個很簡單的類
類名:BroadcastReceiver 父類:Object 修飾:public abstract
實現的介面:[]
包名:android.content 依賴類個數:9
內部類/介面個數:1
原始碼行數:653 原始碼行數(除註釋):260
屬性個數:2 方法個數:36 public方法個數:36
複製程式碼
一、BroadcastReceiver靜態使用
靜態使用也就是配置在
AndroidManifest.xml
中配置意圖過濾器來匹配
關於intent的相關知識,見前一篇,這裡不做解釋
1.寫一個類繼承自BroadcastReceiver
/**
* 作者:張風捷特烈<br></br>
* 時間:2019/1/21/021:16:53<br></br>
* 郵箱:1981462002@qq.com<br></br>
* 說明:談一個吐司的BroadcastReceiver
*/
class ToastBroadcastReceiver : BroadcastReceiver() {
/**
* 接收時呼叫的方法
*/
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "Toly", Toast.LENGTH_SHORT).show()
}
}
---->[app/src/main/AndroidManifest.xml]------------------
<receiver android:name=".receiver.receiver.ToastBroadcastReceiver">
<intent-filter>
<action android:name="www.toly1994.com.br.toast"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
複製程式碼
2.測試的Activity
---->[BrActivity#onCreate]------------------
id_btn_send.setOnClickListener {
val intent = Intent("www.toly1994.com.br.toast")
sendBroadcast(intent)
}
複製程式碼
3.靜態廣播在Android8.0+
intent必須指定廣播的component,才有效
---->[BrActivity#onCreate]------------------
id_btn_send.setOnClickListener {
val intent = Intent("www.toly1994.com.br.toast")
intent.component = ComponentName(
"com.toly1994.tolyservice",//專案包名
"com.toly1994.tolyservice.receiver.receiver.ToastBroadcastReceiver"//廣播接收者全類名
)
sendBroadcast(intent)
}
複製程式碼
4.靜態廣播中的資料獲取
廣播接收者的onReceive回撥中有intent: Intent,你應該明白怎麼傳資料了吧
---->[BrActivity#onCreate]------------------
id_btn_send.setOnClickListener {
val intent = Intent("www.toly1994.com.br.toast")
id_et_txt.text
//為intent新增資料
intent.putExtra("toast_data", id_et_txt.text.toString())
intent.component = ComponentName(
"com.toly1994.tolyservice",//專案包名
"com.toly1994.tolyservice.receiver.receiver.ToastBroadcastReceiver"//廣播接收者全類名
)
sendBroadcast(intent)
}
---->[ToastBroadcastReceiver]------------------
/**
* 接收時呼叫的方法
*/
override fun onReceive(context: Context, intent: Intent) {
val data = intent.getStringExtra("toast_data")
//data?:"NO MSG"表示如果data是空,就取"NO MSG"
Toast.makeText(context, data?:"NO MSG", Toast.LENGTH_SHORT).show()
}
複製程式碼
5.BroadcastReceiver有什麼用?
感覺從上面來看,BroadcastReceiver的onReceive確實耦合性非常低
外部只需要用intent和context.sendBroadcast就能觸發它
但似乎BroadcastReceiver也沒有太大的亮點,作用平平
為了說明他的亮點,現在我們新建一個app:Anotherapp
可以發現在另一個app裡也能正常使用這個廣播
這就有點意思了,我在A專案中寫了一個類,它的方法可以在B專案中觸發
這就是靜態廣播厲害的地方,也是我第一次接觸的跨程式通訊
(這說明解耦到一定的境界,就天下與我同,然而我將無處不在,手動滑稽)
二、BroadcastReceiver動態使用
BroadcastReceiver動態使用分為註冊和登出,
不需要在AndroidManifest.xml註冊
只有在註冊後和登出前的時間段才能使用,否則廣播無效(即onReceive方法不會掉)
1.註冊廣播與傳送訊息
/**
* 註冊廣播
*/
private fun register() {
val filter = IntentFilter()//建立意圖過濾器
filter.addAction("www.toly1994.com.br.toast2")//新增意圖
mReceiver = Toast2BroadcastReceiver()//建立 Toast2BroadcastReceiver
registerReceiver(mReceiver, filter)//註冊
}
/**
* 傳送廣播
*/
private fun sendMsg() {
val intent = Intent()
intent.action = "www.toly1994.com.br.toast2"
intent.putExtra("toast_data", id_et_txt.text.toString())
sendBroadcast(intent)
}
複製程式碼
2.登出廣播
你說,哥就不登出怎麼樣?---答:異常唄
如果不登出,崩了一個異常,原始碼也好心提醒你要unregisterReceiver
2019-01-22 14:10:50.940 4892-4892/com.toly1994.tolyservice E/ActivityThread:
Activity com.toly1994.tolyservice.receiver.BrActivity has leaked IntentReceiver
com.toly1994.tolyservice.receiver.receiver.Toast2BroadcastReceiver@32500e2 that
was originally registered here. Are you missing a call to unregisterReceiver()?
at android.app.LoadedApk$ReceiverDispatcher.<init>(LoadedApk.java:1333)
at android.app.LoadedApk.getReceiverDispatcher(LoadedApk.java:1114)
at android.app.ContextImpl.registerReceiverInternal(ContextImpl.java:1405)
at android.app.ContextImpl.registerReceiver(ContextImpl.java:1378)
at android.app.ContextImpl.registerReceiver(ContextImpl.java:1366)
at android.content.ContextWrapper.registerReceiver(ContextWrapper.java:603)
at com.toly1994.tolyservice.receiver.BrActivity.onCreate(BrActivity.kt:27)
複製程式碼
/**
* 登出廣播
*/
private fun unRegister() {
unregisterReceiver(mReceiver);
}
override fun onDestroy() {
super.onDestroy()
unRegister()//登出廣播
}
複製程式碼
3.靜態和動態廣播的區別
你可能會說:就一個200多行的類,還搞那麼多事...
動態註冊的廣播
|---優勢:可以自由的控制註冊和取消,有很大的靈活性。
|---劣勢:只有在註冊之後才能起作用,在Activity的onDestroy後如果未被登出,會報異常
----所以動態註冊的廣播存活時間最長也就約等於Activity的生命週期長度
靜態註冊的廣播
|---優勢:不受程式是否啟動的約束,隨時使用
|---劣勢:優勢同樣也是劣勢,無法取消,什麼時候都能用
複製程式碼
三、BroadcastReceiver有序廣播
先講個場景:男孩(Boy)說:一塊石頭的價值 1元
之後將石頭給了雕刻家,並將預期的價值1000元傳遞給雕刻家
之後雕刻家將石頭給了寶石家,並將預期的價值10W元傳遞給寶石家
之後寶石家將石頭給了收藏家,並將預期的價值100W元傳遞給收藏家
收藏家向外稱城自己的寶石價值100W
1.有序廣播(沒有指定順序時,按註冊順序)
1.1:四個廣播接收者
/**
* 作者:張風捷特烈<br></br>
* 時間:2019/1/21/021:16:53<br></br>
* 郵箱:1981462002@qq.com<br></br>
* 說明:男孩
*/
class BoyBReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context,
"男孩:$resultData",//[1]獲取結果並展示
Toast.LENGTH_LONG
).show()
// abortBroadcast();//[2]終止廣播
resultData = "價值1000元" //[3]傳遞資料---給下一個廣播
}
}
/**
* 說明:雕刻家
*/
class GraverBReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "雕刻家:$resultData", //[1]獲取結果並展示
Toast.LENGTH_LONG).show()
// abortBroadcast();//[2]終止廣播
resultData = "價值10W元"//[3]傳遞資料---給下一個廣播
}
}
/**
* 說明:寶石家
*/
class RubyManBReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "寶石家:$resultData",
Toast.LENGTH_LONG).show()
// abortBroadcast();//[2]終止廣播
resultData = "價值100W元"//[3]傳遞資料---給下一個廣播
}
}
/**
* 說明:收藏家
*/
class CollectorBReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context,
"收藏家:$resultData", //獲取結果並展示
Toast.LENGTH_LONG).show()
}
}
複製程式碼
1.2.動態註冊併傳送有序廣播
/**
* 註冊廣播
*/
private fun register() {
val filter = IntentFilter()//建立意圖過濾器
filter.addAction("www.toly1994.com.br.toast2")//新增意圖
boyReceiver = BoyBReceiver()
graverReceiver = GraverBReceiver()
rubyManReceiver = RubyManBReceiver()
registerReceiver(boyReceiver, filter)//註冊
registerReceiver(graverReceiver, filter)//註冊
registerReceiver(rubyManReceiver, filter)//註冊
}
/**
* 傳送有序廣播
*/
private fun sendOrder() {
val intent = Intent()
intent.action = "www.toly1994.com.br.toast2"
val collectorBReceiver = CollectorBReceiver()
sendOrderedBroadcast(
intent, null,
collectorBReceiver, null, 1,
"價值1元", null
)
}
/**
* 登出廣播
*/
private fun unRegister() {
unregisterReceiver(boyReceiver)
unregisterReceiver(graverReceiver)
unregisterReceiver(rubyManReceiver)
}
複製程式碼
2.中途終止廣播有序廣播
* 說明:雕刻家
*/
class GraverBReceiver : BroadcastReceiver() {
/**
* 接收時呼叫的方法
*/
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "雕刻家:$resultData", //[1]獲取結果並展示
Toast.LENGTH_LONG).show()
abortBroadcast();//[2]終止廣播
resultData = "價值10W元"//[3]傳遞資料---給下一個廣播
}
}
複製程式碼
3.自定義廣播順序
/**
* 註冊廣播
*/
private fun register() {
boyReceiver = BoyBReceiver()
val boyFilter = IntentFilter()//建立意圖過濾器
boyFilter.addAction("www.toly1994.com.br.toast2")//新增意圖
boyFilter.priority = 10//指定過濾器優先順序
graverReceiver = GraverBReceiver()
val graverFilter = IntentFilter()//建立意圖過濾器
graverFilter.addAction("www.toly1994.com.br.toast2")//新增意圖
graverFilter.priority = 20//指定過濾器優先順序
rubyManReceiver = RubyManBReceiver()
val rubyManFilter = IntentFilter()//建立意圖過濾器
rubyManFilter.addAction("www.toly1994.com.br.toast2")//新增意圖
rubyManFilter.priority = 21//指定過濾器優先順序
registerReceiver(boyReceiver, boyFilter)//註冊
registerReceiver(graverReceiver, graverFilter)//註冊
registerReceiver(rubyManReceiver, rubyManFilter)//註冊
}
複製程式碼
上面是BroadcastReceiver有序廣播的動態註冊形式的程式碼,
靜態註冊在AndroidManifest.xml裡配置類似,就不廢話了
還有一點注意的是sendOrderedBroadcast方法呼叫時傳入的BroadcastReceiver
為最後呼叫的BroadcastReceiver,不需要註冊!
四、廣播和系統行為的結合
以下皆使用動態註冊,很多系統級的行為靜態註冊都是無效的
1.開屏鎖屏廣播
/**
* 作者:張風捷特烈<br></br>
* 時間:2019/1/22/022:16:43<br></br>
* 郵箱:1981462002@qq.com<br></br>
* 說明:開屏鎖屏廣播
*/
class ScreenBReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
//[1]獲取到當前廣播的事件型別
val action = intent.action
//[2]對當前廣播事件型別做一個判斷
if ("android.intent.action.SCREEN_OFF" == action) {
Log.e(TAG, "螢幕鎖屏了")
} else if ("android.intent.action.SCREEN_ON" == action) {
Log.e(TAG, "螢幕解鎖了")
}
}
companion object {
private const val TAG = "ScreenBReceiver"
}
}
---->[ScreenBrActivity使用方法]------------------------------------
/**
* 動態的去註冊螢幕解鎖和鎖屏的廣播
*/
private fun register() {
// [1]動態的去註冊螢幕解鎖和鎖屏的廣播
mScreenReceiver = ScreenBReceiver()
// [2]建立intent-filter物件
val filter = IntentFilter()
// [3]新增要註冊的action
filter.addAction("android.intent.action.SCREEN_OFF")
filter.addAction("android.intent.action.SCREEN_ON")
// [4]註冊廣播接收者
registerReceiver(mScreenReceiver, filter)
}
複製程式碼
2.簡訊監聽廣播
注意許可權:
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
/**
* 作者:張風捷特烈<br></br>
* 時間:2019/1/22/022:16:43<br></br>
* 郵箱:1981462002@qq.com<br></br>
* 說明:簡訊監聽廣播
*/
class SMSBReceiver : BroadcastReceiver() {
//當簡訊到來的時候 就會執行這個方法
override fun onReceive(context: Context, intent: Intent) {
//[1]獲取發簡訊送的號碼 和內容
val objects = intent.extras!!.get("pdus") as Array<*>
val format = intent.getStringExtra("format")
for (pdu in objects) {
//[2]獲取smsmessage例項
val smsMessage =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
SmsMessage.createFromPdu(pdu as ByteArray, format)
} else {
SmsMessage.createFromPdu(pdu as ByteArray)
}
//[3]獲取傳送簡訊的內容
val body = smsMessage.messageBody
val date = Date(smsMessage.timestampMillis)//時間
val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA)
//[4]獲取傳送者
val address = smsMessage.originatingAddress
val receiveTime = format.format(date)
Log.e("SMSBReceiver", "body:$body---$address---$receiveTime")
}
}
}
---->[SMSBrActivity使用方法]------------------------------------
/**
* 動態註冊簡訊廣播接收者
*/
private fun register() {
//註冊簡訊廣播接收者
val smsFilter = IntentFilter()
smsFilter.addAction("android.provider.Telephony.SMS_RECEIVED")
mSmsReceiver = SMSBReceiver()
registerReceiver(mSmsReceiver, smsFilter)
}
複製程式碼
3.監聽電量變化廣播
這裡傳入一個Textview用於顯示電量
/**
* 作者:張風捷特烈<br></br>
* 時間:2019/1/22/022:16:43<br></br>
* 郵箱:1981462002@qq.com<br></br>
* 說明:監聽電量變化
*/
class BatteryBReceiver(private val mTV:TextView ) : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// 當前電量
val currLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
// 總電量
val total = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 1)
val percent = currLevel * 100 / total
Log.e("BatteryBReceiver", "battery: $percent%")
mTV.setTextColor(randomRGB())
mTV.text = "battery: $percent%"
}
/**
* 返回隨機顏色
*
* @return 隨機顏色
*/
fun randomRGB(): Int {
val random = Random()
val r = 30 + random.nextInt(200)
val g = 30 + random.nextInt(200)
val b = 30 + random.nextInt(200)
return Color.rgb(r, g, b)
}
}
---->[BatteryBrActivity使用方法]------------------------------------
/**
* 動態電量廣播接收者
*/
private fun register() {
val filter = IntentFilter()
filter.addAction(Intent.ACTION_BATTERY_CHANGED)
mBatteryChangeReceiver = BatteryBReceiver(id_tv_info)
registerReceiver(mBatteryChangeReceiver, filter)
}
複製程式碼
4.app安裝/解除安裝改變時廣播監聽
/**
* 作者:張風捷特烈<br></br>
* 時間:2019/1/22/022:16:43<br></br>
* 郵箱:1981462002@qq.com<br></br>
* 說明:app安裝/解除安裝改變時廣播監聽
*/
class AppBReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
val uri = intent.data
if (action == "android.intent.action.PACKAGE_ADDED") {
Log.e("AppBReceiver", uri.toString() + "被安裝了")
} else if (action == "android.intent.action.PACKAGE_REPLACED") {
Log.e("AppBReceiver", uri.toString() + "被更新了")
} else if (action == "android.intent.action.PACKAGE_REMOVED") {
Log.e("AppBReceiver", uri.toString() + "被解除安裝了")
}
}
}
---->[AppBrActivity使用方法]------------------------------------
/**
* 動態註冊app安裝/解除安裝改變時廣播監聽
*/
private fun register() {
val filter = IntentFilter()
filter.addAction("android.intent.action.PACKAGE_ADDED")
filter.addAction("android.intent.action.PACKAGE_REPLACED")
filter.addAction("android.intent.action.PACKAGE_REMOVED")
filter.addDataScheme("package")
mAppReceiver = AppBReceiver()
registerReceiver(mAppReceiver, filter)
}
//但是貌似這個用動態註冊並不怎麼有用
//因為一般解除安裝,安裝都不是在當前Activity中,加了一下靜態,便可以了
//注意,在測試中發現,只加靜態的配置也是無效的
<receiver android:name=".receiver.receiver.AppBReceiver">
<intent-filter >
<action android:name="android.intent.action.PACKAGE_ADDED"/>
<action android:name="android.intent.action.PACKAGE_REPLACED"/>
<action android:name="android.intent.action.PACKAGE_REMOVED"/>
<data android:scheme="package"/>
</intent-filter>
</receiver>
複製程式碼
還有一些系統行為套路都差不多,需要的時候查查對應的action就行了
五、使用廣播更新音樂進度條
在絕命暗殺官[-Service-]中實現過一個音樂播放條,其中音樂的播放進度是靠Handler+回撥實現的
BroadcastReceiver本職就在於通知,在這裡用BroadcastReceiver實現音樂的播放進度
---->[常量類]-----------------------------
public class Cons {
//廣播更新進度--資料
public static final String DATA_MUSIC_POSITION = "data_music_position";
//廣播更新進度--Action
public static final String ACTION_UPDATE = "action_update";
}
---->[MusicPlayerWithBrStub]-----------------------------
mTimer.schedule(new TimerTask() {
@Override
public void run() {
if (mPlayer.isPlaying()) {
int pos = mPlayer.getCurrentPosition();
int duration = mPlayer.getDuration();
//傳送廣播更新進度
---> Intent intent = new Intent(Cons.ACTION_UPDATE);
---> int progress = (int) (pos * 100.f / duration);
---> intent.putExtra(Cons.DATA_MUSIC_POSITION, progress);
---> mContext.sendBroadcast(intent);
}
}
}, 0, 1000);
---->[MusicActivity#registerReceiver]-----------------------------
|-- 這裡我新建一個類,你也可以直接在Activity中建個內部類,要簡單些
public class UpdateReceiver extends BroadcastReceiver {
@Nullable
private ProgressView progressView;
public UpdateReceiver(@Nullable ProgressView progressView) {
this.progressView = progressView;
}
@Override
public void onReceive(Context context, Intent intent) {
if (Cons.ACTION_UPDATE.equals(intent.getAction())) {
int progress = intent.getIntExtra(Cons.DATA_MUSIC_POSITION, 0);
if (progressView != null) {
---> progressView.setProgress(progress);
}
}
}
}
/**
* 註冊廣播
*/
---->[MusicActivity#registerReceiver]-----------------------------
private fun registerReceiver() {
mReceiver = UpdateReceiver(id_pv_pre)
val filter = IntentFilter()
filter.addAction(Cons.ACTION_UPDATE)
registerReceiver(mReceiver, filter)//註冊
}
---->[MusicActivity#onDestroy]-----------------------------
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(mReceiver)//登出廣播
mMusicPlayer.release()
}
複製程式碼
其實也就是發廣播-->收廣播-->操作,用起來並不困難
至於BroadcastReceiver的原始碼,暫時就不讀了(讀了一下,沒怎麼讀得通...),以後再開篇吧!
後記:捷文規範
1.本文成長記錄及勘誤表
專案原始碼 | 日期 | 附錄 |
---|---|---|
V0.1--無 | 2018-2-27 | 無 |
釋出名:
Android點將臺:傳令官[-BroadcastReceiver-]
捷文連結:juejin.im/post/5c7675…
2.更多關於我
筆名 | 微信 | |
---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 |
我的github:github.com/toly1994328
我的簡書:www.jianshu.com/u/e4e52c116…
我的簡書:www.jianshu.com/u/e4e52c116…
個人網站:www.toly1994.com
3.宣告
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援