Android點將臺:傳令官[-BroadcastReceiver-](使用級)

張風捷特烈發表於2019-02-27

零、前言

1.本文的知識點
1).BroadcastReceiver`靜態`使用  
2).BroadcastReceiver`動態`使用   
3).BroadcastReceiver`有序`廣播    
4).BroadcastReceiver和`系統`行為的結合  
5).小例子:使用BroadcastReceiver更新音樂播放器進度條
複製程式碼

2.BroadcastReceiver總覽

現在才發現BroadcastReceiver原來這麼精簡,純原始碼才260
直接繼承Object,沒有實現介面,沒有家庭背景,可以說是個很簡單的類

BroadcastReceiver.png

類名:BroadcastReceiver      父類:Object      修飾:public abstract
實現的介面:[]
包名:android.content   依賴類個數:9
內部類/介面個數:1
原始碼行數:653       原始碼行數(除註釋):260
屬性個數:2       方法個數:36       public方法個數:36
複製程式碼

一、BroadcastReceiver靜態使用

靜態使用也就是配置在AndroidManifest.xml中配置意圖過濾器來匹配
關於intent的相關知識,見前一篇,這裡不做解釋

BroadcastReceiver靜態使用.png


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

傳送廣播彈吐司.png

---->[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,你應該明白怎麼傳資料了吧

輸入彈出吐司.png

---->[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.png

兩個app.png


可以發現在另一個app裡也能正常使用這個廣播
這就有點意思了,我在A專案中寫了一個類,它的方法可以在B專案中觸發
這就是靜態廣播厲害的地方,也是我第一次接觸的跨程式通訊
(這說明解耦到一定的境界,就天下與我同,然而我將無處不在,手動滑稽)

另一個開啟廣播.png

另一個app.png


二、BroadcastReceiver動態使用

BroadcastReceiver動態使用分為註冊和登出,不需要在AndroidManifest.xml註冊
只有在註冊後和登出前的時間段才能使用,否則廣播無效(即onReceive方法不會掉)

動態廣播圖示.png

動態廣播.png

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.有序廣播(沒有指定順序時,按註冊順序)

有序廣播.png

吐司情況.png


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.中途終止廣播有序廣播

中途終止廣播有序廣播.png

吐司情況.png

 * 說明:雕刻家
 */
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.自定義廣播順序

自定義廣播順序.png

指定順序吐司情況.png

 /**
  * 註冊廣播
  */
 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.開屏鎖屏廣播

BroadcastReceiver實現鎖屏及開屏監聽.gif

/**
 * 作者:張風捷特烈<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"/>

手機簡訊監聽.png

/**
 * 作者:張風捷特烈<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用於顯示電量

手機電量監聽.png

/**
 * 作者:張風捷特烈<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安裝/解除安裝改變時廣播監聽

解除安裝監聽.png

/**
 * 作者:張風捷特烈<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實現音樂的播放進度

更新進度條.png

---->[常量類]-----------------------------
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.更多關於我
筆名 QQ 微信
張風捷特烈 1981462002 zdl1994328

我的github:github.com/toly1994328
我的簡書:www.jianshu.com/u/e4e52c116…
我的簡書:www.jianshu.com/u/e4e52c116…
個人網站:www.toly1994.com

3.宣告

1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援

icon_wx_200.png

相關文章