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

我的全部文章整理在此篇,將陸續更新收錄:知無涯,行者之路莫言終(個人編程之路)java

零、前言

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

2.BroadcastReceiver總覽

如今才發現BroadcastReceiver原來這麼精簡,純源碼才260
直接繼承Object,沒有實現接口,沒有家庭背景,能夠說是個很簡單的類android

BroadcastReceiver.png

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

1、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,纔有效ruby

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

輸入彈出吐司.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項目中觸發
這就是靜態廣播厲害的地方,也是我第一次接觸的跨進程通訊
(這說明解耦到必定的境界,就天下與我同,然而我將無處不在,手動滑稽)app

另外一個開啓廣播.png

另外一個app.png


2、BroadcastReceiver動態使用

BroadcastReceiver動態使用分爲註冊和註銷,不須要在AndroidManifest.xml註冊
只有在註冊後和註銷前的時間段才能使用,不然廣播無效(即onReceive方法不會掉)dom

動態廣播圖示.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.註銷廣播

你說,哥就不註銷怎麼樣?---答:異常唄
若是不註銷,崩了一個異常,源碼也好心提醒你要unregisterReceiveride

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多行的類,還搞那麼多事...post

動態註冊的廣播
|---優點:能夠自由的控制註冊和取消,有很大的靈活性。
|---劣勢:只有在註冊以後才能起做用,在Activity的onDestroy後若是未被註銷,會報異常
----因此動態註冊的廣播存活時間最長也就約等於Activity的生命週期長度

靜態註冊的廣播
|---優點:不受程序是否啓動的約束,隨時使用
|---劣勢:優點一樣也是劣勢,沒法取消,何時都能用
複製代碼

3、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,不須要註冊!


4、廣播和系統行爲的結合

如下皆使用動態註冊,不少系統級的行爲靜態註冊都是無效的

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就好了


5、使用廣播更新音樂進度條

絕命暗殺官[-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的源碼,暫時就不讀了(讀了一下,沒怎麼讀得通...),之後再開篇吧!

相關文章
相關標籤/搜索