Android基礎回顧(四)| 關于廣播機制

參考書籍:《第一行代碼》 第二版 郭霖
如有錯漏癌幕,請批評指出!

廣播機制簡介

Android中的廣播主要分兩種類型:標準廣播和有序廣播

  • 標準廣播:一種完全異步執(zhí)行的廣播褪迟,在廣播發(fā)出之后美浦,所有的廣播接收器幾乎都會在同一時刻接收到這條廣播信息,沒有先后順序可言戚哎。這種廣播效率比較高裸诽,但是無法截斷。

  • 有序廣播:一種同步執(zhí)行的廣播型凳,在廣播發(fā)出之后丈冬,同一時刻只會有一個廣播接收器能夠收到這條廣播消息,當這個廣播接收器中的邏輯執(zhí)行完畢后廣播才會繼續(xù)傳遞甘畅。此時廣播接收器有先后順序埂蕊,優(yōu)先級高的廣播接收器可以先收到廣播消息,并且前面的廣播接收器可以截斷廣播疏唾。

接收系統(tǒng)廣播

Android內(nèi)置了很多系統(tǒng)級別的廣播蓄氧,我們可以在應用程序中通過監(jiān)聽這些廣播來得到各種系統(tǒng)的狀態(tài)信息。使用廣播接收器可以自由地對需要的廣播進行注冊荸实,當有相應廣播發(fā)出時匀们,廣播接收器就能夠收到該廣播,并對其進行邏輯處理准给。
注冊廣播的方式一般有兩種:一是動態(tài)注冊泄朴,即在代碼中注冊重抖;二是靜態(tài)注冊,即在AndroidManifest文件中注冊祖灰。
創(chuàng)建廣播接收器的方法是新建一個類钟沛,繼承BroadcastReceiver類,并重寫onReceive()方法局扶,當有廣播到來時恨统,onReceive()方法會被調(diào)用,我們可以在onReceive()方法中添加具體邏輯三妈。

  • 動態(tài)注冊監(jiān)聽網(wǎng)絡變化
    下面我們使用動態(tài)注冊的方式來完成一個監(jiān)聽網(wǎng)絡變化的demo:
    首先畜埋,在AndroidManifest文件中聲明系統(tǒng)網(wǎng)絡狀態(tài)的權限,因為我們要監(jiān)聽系統(tǒng)網(wǎng)絡狀態(tài)的變化畴蒲,就必須擁有這個權限:

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    

    接下來修改BroadcastActivity中的代碼:

    public class BroadcastActivity extends AppCompatActivity {
    
        private NetworkChangeReceiver networkChangeReceiver;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            initBroadCast();
        }
    
        private void initBroadCast() {
            IntentFilter intentFilter = new IntentFilter();
            intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
            networkChangeReceiver = new NetworkChangeReceiver();
            registerReceiver(networkChangeReceiver, intentFilter);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            unregisterReceiver(networkChangeReceiver);
        }
    
        class NetworkChangeReceiver extends BroadcastReceiver {
    
            @Override
            public void onReceive(Context context, Intent intent) {
                ConnectivityManager manager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
                NetworkInfo networkInfo = manager.getActiveNetworkInfo();
                if (networkInfo != null && networkInfo.isConnected()) {
                    ToastUtil.showShortToast(context, "Network is connected");
                }else {
                    ToastUtil.showShortToast(context, "Network not connected");
                }
            }
        }
    }
    
    1. 第一步悠鞍,創(chuàng)建一個內(nèi)部類NetworkChangeReceiver,繼承BroadcastReceiver類模燥,重寫它的onReceive()方法咖祭。在onReceive()方法中通過getSystemService()方法得到一個ConnectivityManager的實例,這是一個系統(tǒng)服務類蔫骂,專門用于管理網(wǎng)絡連接的么翰。然后調(diào)用它的getActiveNetworkInfo()方法可以得到NetworkInfo的實例,接著調(diào)用NetworkInfo的isConnected()方法辽旋,就可以判斷出當前是否有網(wǎng)絡了浩嫌。當然,想要onReceive()方法被觸發(fā)戴已,我們需要對網(wǎng)絡狀態(tài)變化時發(fā)出的系統(tǒng)廣播進行注冊固该。
    2. 第二步,因為系統(tǒng)網(wǎng)絡狀態(tài)發(fā)生變化時糖儡,會發(fā)出一條值為
      android.net.conn.CONNECTIVITY_CHANGE 的廣播伐坏,所以我們創(chuàng)建一個IntentFilter實例,并給它添加值為 android.net.conn.CONNECTIVITY_CHANGE 的action(也就是說握联,我們想要監(jiān)聽什么廣播桦沉,就添加其對應的action)。
    3. 第三步金闽,創(chuàng)建一個NetworkChangeReceiver的實例纯露,然后調(diào)用 registerReceiver() 方法進行注冊,將NetworkChangeReceiver的實例和IntentFilter的實例傳進去代芜,這樣就完成了注冊埠褪,也就是說,當系統(tǒng)發(fā)出值為android.net.conn.CONNECTIVITY_CHANGE的廣播時,我們的onReceive()方法就會被觸發(fā)钞速。
    4. 第四步贷掖,重寫onDestroy()方法,調(diào)用unregisterReceiver()方法取消注冊渴语。這里要注意苹威,我們動態(tài)注冊的廣播都需要取消注冊。下面看效果(打開我們的Demo驾凶,然后切到網(wǎng)絡設置的頁面牙甫,注意,不要按back鍵退出demo调违,不然BroadcastActivity會被銷毀):
  • 靜態(tài)注冊實現(xiàn)開機啟動
    動態(tài)注冊的廣播接收器可以自由地控制注冊與注銷窟哺,在靈活性上有很大的優(yōu)勢,但是它也有自己的局限性技肩,即必須在程序啟動后才能接收到廣播脏答,因為注冊邏輯是寫在onCreate()方法中的。這時候亩鬼,靜態(tài)注冊的方式就有用武之地了。接下來阿蝶,我們通過靜態(tài)注冊的方式來接收一條開機廣播雳锋。

    1. 第一步,新建一個BootCompleteReceiver類羡洁,繼承BroadcastReceiver類玷过,并重寫其onReceive()方法,使用Toast彈出一條消息:
    public class BootCompleteReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            ToastUtil.showShortToast(context, "Boot Completed");
        }
    }
    
    1. 第二步筑煮,在AndroidManifest文件中注冊廣播:
    <receiver
        android:name=".broadcast.BootCompleteReceiver"
        android:enabled="true"
        android:exported="true" >
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED"/>
        </intent-filter>
    </receiver>
    

    通過android:name屬性指定我們創(chuàng)建的廣播接收器(完整路徑)辛蚊;android:enabled
    屬性定義系統(tǒng)是否能夠?qū)嵗@個廣播接收器,為true時這個廣播接收器才能被啟用真仲,默認為true袋马;android:exported屬性用于指示該廣播接收器是否能夠接收來自應用程序外部的消息。然后在<intent-filter>標簽中添加系統(tǒng)開機廣播對應的action秸应,這和動態(tài)注冊創(chuàng)建Intentfilter實例并添加action的作用是一樣的虑凛。

    1. 第三步,由于我們需要監(jiān)聽系統(tǒng)開機廣播软啼,因此也需要為其聲明權限桑谍,在AndroidManifest文件中聲明權限:
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    

這樣,我們的靜態(tài)注冊廣播就完成了祸挪,具體結果自行驗證锣披。
注意:在onReceive()方法中不能進行耗時操作,因為廣播接收器中不允許開啟線程,若onReceive()方法運行較長時間雹仿,程序就會報錯增热。

自定義廣播

前面是關于如何使用廣播接收器來接收系統(tǒng)廣播,接下來我們來看看如何發(fā)送自定義廣播盅粪。

  • 發(fā)送標準廣播
    首先來新建一個DiyBroadcastReceiver類钓葫,繼承BroadcastReceiver類,重寫onReceive()方法:

    public class DiyBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            ToastUtil.showShortToast(context, "received in my broadcast receiver");
            abortBroadcast();
        }
    }
    

    然后在AndroidManifest文件中對這個廣播接收器進行注冊:

    <receiver android:name=".broadcast.broadcast.DiyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter android:priority="13">
                <action android:name="com.laughter.broadcast.DIY_BROADCAST"/>
            </intent-filter>
        </receiver>
    

    我們添加一條值為 com.laughter.broadcast.DIY_BROADCAST 的action票顾,也就意味著础浮,我們待會兒發(fā)出一條值為這個的廣播,我們的廣播接收器就能收到奠骄。
    接下來豆同,在我們的布局文件中添加一個Button,用于觸發(fā)發(fā)送廣播(很簡單含鳞,我就不貼代碼了)影锈,然后修改BroadcastActivity中的代碼:

    public class BroadcastActivity extends AppCompatActivity {
    
        @BindView(R.id.but_send)
        Button send;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_broadcast);
            ButterKnife.bind(this);
        }
    
        @OnClick(R.id.but_send)
        public void send() {
            Intent intent = new Intent("com.laughter.broadcast.DIY_BROADCAST");
            sendBroadcast(intent);
        }
    }
    

    發(fā)送廣播的方法很簡單,就是創(chuàng)建一個Intent對象蝉绷,將需要發(fā)送的廣播的值作為參數(shù)傳遞進去鸭廷,然后調(diào)用sendBroadcast()方法將廣播發(fā)送出去,這樣監(jiān)聽 com.laughter.broadcast.DIY_BROADCAST 這個值的廣播接收器就能收到這條廣播(這里就不貼效果圖了熔吗,大家可以自己驗證)辆床。由于是用Intent發(fā)送廣播,因此桅狠,還可以用這個Intent對象攜帶一些參數(shù)讼载。
    廣播是一種可以跨進程的通信方式,因此中跌,我們在應用程序中發(fā)送的廣播咨堤,別的應用程序也是可以收到的。下面我們來驗證一下:
    首先創(chuàng)建一個BroadcastTest項目漩符,然后定義一個廣播接收器:

    public class AnotherReceiver extends BroadcastReceiver {
    
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "received in another receiver", Toast.LENGTH_SHORT).show();
        }
    }
    

    在AndroidManifest文件中進行注冊:

    <receiver
        android:name=".AnotherReceiver"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="com.laughter.broadcast.DIY_BROADCAST"/>
        </intent-filter>
    </receiver>
    

    這樣一喘,我們的兩個程序就都能說收到這條廣播了,下面將兩個app都運行起來嗜暴,驗證一下:

    可以看到津滞,當我們點擊Button時,兩條Toast都彈出了灼伤, 也就意味著触徐,兩個app中的廣播接收器都收到了這條廣播。

  • 發(fā)送有序廣播
    要發(fā)送一條有序廣播狐赡,我們只需要在前面的BroadcastActivity中撞鹉,將sendBroadcast(intent) 方法換成 sendOrderedBroadcast(intent, null) 方法就行了(第一個參數(shù)還是Intent對象,第二個參數(shù)是與權限相關的字符串,這里直接傳null就行):

    public class BroadcastActivity extends AppCompatActivity {
    
        ···
    
        @OnClick(R.id.but_send)
        public void send() {
            Intent intent = new Intent("com.laughter.broadcast.DIY_BROADCAST");
            sendOrderedBroadcast(intent, null);
        }
    }
    

    前面說過鸟雏,發(fā)送有序廣播的時候享郊,廣播接收器之間存在優(yōu)先級,那么如何指定優(yōu)先級呢孝鹊?

    <receiver android:name=".broadcast.broadcast.DiyBroadcastReceiver"
        android:enabled="true"
        android:exported="true">
        <intent-filter android:priority="13">
            <action android:name="com.laughter.broadcast.DIY_BROADCAST"/>
        </intent-filter>
    </receiver>
    

    我們只需要在注冊廣播接收器的時候給<intent-filter>標簽指定一個android:priority 屬性就行了炊琉,里面的值就是優(yōu)先級,值越大又活,優(yōu)先級越高苔咪。同樣的,給AnotherReceiver指定一個優(yōu)先級柳骄,數(shù)字比這里的13小就行团赏。這樣,優(yōu)先級高的廣播接收器就會先收到廣播耐薯。(這里也不貼圖了舔清,自行驗證)
    前面還提到過,有序廣播是可以被攔截的曲初,如何攔截呢体谒?我們只需要在廣播接收器的onReceive()方法中調(diào)用 abortBroadcast(); 方法就可以攔截廣播,這樣優(yōu)先級比這個廣播接收器低的就收不到這條廣播了臼婆。

    public class DiyBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            ToastUtil.showShortToast(context, "received in my broadcast receiver");
            abortBroadcast();
        }
    }
    

    還是剛才的兩個app营密,我們再來運行起來看看:

    可以看到,現(xiàn)在只有一個優(yōu)先級高的廣播接收器的onReceive()方法被觸發(fā)目锭,彈出了Toast,而由于廣播被攔截纷捞,優(yōu)先級低的AnotherReceiver沒有收到廣播痢虹,所以沒有彈出Toast。

使用本地廣播

前面我們發(fā)送和接收的廣播全部屬于系統(tǒng)全局廣播主儡,即發(fā)出的廣播可以被任何其他的應用程序接收到奖唯,并且也可以接受其他任何應用程序的廣播。這樣就很容易引起安全性問題糜值。為此丰捷,Android引入了一套本地廣播機制,使用本地廣播機制寂汇,廣播只能在應用程序內(nèi)部進行傳遞病往,并且廣播接收器也只會接收到應用程序內(nèi)部的廣播。
其實本地廣播的用法和前面差不多骄瓣,我們先來看代碼:

public class BroadcastActivity extends AppCompatActivity {

    @BindView(R.id.but_send)
    Button send;

    LocalBroadcastManager manager;
    LocalReceiver localReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_broadcast);
        ButterKnife.bind(this);
        initView();
    }

    private void initView() {
        manager = LocalBroadcastManager.getInstance(this);
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.laughter.broadcast.LOCAL_BROADCAST");
        localReceiver = new LocalReceiver();
        manager.registerReceiver(localReceiver, intentFilter);
    }

    @OnClick(R.id.but_send)
    public void send() {
        Intent intent = new Intent("com.laughter.broadcast.LOCAL_BROADCAST");
        manager.sendBroadcast(intent);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        manager.unregisterReceiver(localReceiver);
    }

    class LocalReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            ToastUtil.showShortToast(context, "received local broadcast");
        }
    }
}

和前面一樣停巷,我們首先定義一個內(nèi)部類,即廣播接收器,然后定義一個 LocalBroadcastManager 對象畔勤,通過它的 getInstance() 方法獲取一個實例蕾各,接下來就是定義IntentFilter對象和廣播接收器對象,給IntentFilter添加一條值為 com.laughter.broadcast.LOCAL_BROADCAST 的action庆揪,然后調(diào)用 LocalBroadcastManager 的 registerReceiver() 方法注冊本地廣播監(jiān)聽器式曲,接下來在Button的點擊事件中通過 LocalBroadcastManager 的 sendBroadcast() 方法發(fā)送廣播,最后別忘了在onDestroy() 方法中取消注冊缸榛。這樣看起來吝羞,本地廣播區(qū)別于全局廣播的地方僅僅就是通過一個LocalBroadcastmanager 來管理廣播的注冊和發(fā)送。這樣我們的廣播就是僅僅在應用程序內(nèi)部傳遞的了(感興趣的自行驗證)仔掸。
注意:既然需要通過 LocalBroadcastmanager 來管理廣播的注冊和發(fā)送脆贵,那么對應的靜態(tài)注冊的方式怎么實現(xiàn)呢?很遺憾起暮,本地廣播是無法通過靜態(tài)注冊的方式來接收的卖氨。不過仔細想想,本地廣播的發(fā)送很顯然是需要在應用程序啟動的情況下完成的负懦,既然要在應用程序啟動的情況下發(fā)送廣播筒捺,那么也不用考慮在應用程序未啟動的情況下接收廣播了。而我們使用靜態(tài)注冊主要就是為了在應用程序未啟動的情況下也能收到廣播纸厉,所以思路就很清晰了系吭。


上一篇:Android基礎回顧(三)| 關于Fragment
下一篇:Android基礎回顧(五)| 數(shù)據(jù)存儲——持久化技術


最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市颗品,隨后出現(xiàn)的幾起案子肯尺,更是在濱河造成了極大的恐慌,老刑警劉巖躯枢,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件则吟,死亡現(xiàn)場離奇詭異,居然都是意外死亡锄蹂,警方通過查閱死者的電腦和手機氓仲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來得糜,“玉大人敬扛,你說我怎么就攤上這事〕叮” “怎么了啥箭?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長治宣。 經(jīng)常有香客問我捉蚤,道長抬驴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任缆巧,我火速辦了婚禮布持,結果婚禮上,老公的妹妹穿的比我還像新娘陕悬。我一直安慰自己题暖,他們只是感情好,可當我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布捉超。 她就那樣靜靜地躺著胧卤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拼岳。 梳的紋絲不亂的頭發(fā)上枝誊,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天,我揣著相機與錄音惜纸,去河邊找鬼叶撒。 笑死,一個胖子當著我的面吹牛耐版,可吹牛的內(nèi)容都是我干的祠够。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼粪牲,長吁一口氣:“原來是場噩夢啊……” “哼古瓤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起腺阳,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤落君,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后亭引,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绎速,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年痛侍,在試婚紗的時候發(fā)現(xiàn)自己被綠了德澈。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妇蛀。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖尿孔,靈堂內(nèi)的尸體忽然破棺而出待德,到底是詐尸還是另有隱情君丁,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布将宪,位于F島的核電站绘闷,受9級特大地震影響橡庞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜印蔗,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一扒最、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧华嘹,春花似錦吧趣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至薛躬,卻和暖如春俯渤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背型宝。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工八匠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人诡曙。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓臀叙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親价卤。 傳聞我的和親對象是個殘疾皇子劝萤,可洞房花燭夜當晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容