第十五章 Android的廣播機制和BroadCast Receiver

1.引言

在《第四章 Android 四大應用組件》中,簡單介紹了下四大組件的成員,屬性,生命周期等。這里主要是介紹下Android中的廣播機制。在四大組件中,Activity的使用頻率是最高的。其他三個組件的使用頻率相對叫低,但是既然叫四大組件,那就說明了他們的不可或缺性。
??記得小時候上學的時候的大喇叭嗎?有時候聽力考試也會用到,學校的廣播室通過發送廣播到各個教室的小喇叭上,每次一開,那簡直是要命啊 。類似于喇叭的工作機制,在現在的計算機領域會有很廣泛的引用。為了便于系統級別的消息通知,Android也引入了廣播消息機制。當然相應的比較那個大喇叭例子,Android的廣播機制更加靈活。

2.簡介

為什么說Android的廣播機制靈活呢?我們知道在我們的手機上有很多個應用,有時候會接收到不同的應用的消息,但是,既然是大喇叭,發過來的消息,不是都可以接受么?這里簡單講一下它的靈活之處,Android中的每個應用程序都可以對自己感興趣的廣播注冊,這樣該程序就只能接收到自己關心的廣播內容。這些廣播的內容可以是來自于系統,也可以是來自其他應用程序。
??我們所使用的手機,既可以說是一個廣播接收器,也可以說是一個廣播發送器。Android 的廣播分為兩個方面:廣播發送者和廣播接收者。BroadCast Receiver指的是廣播接受者(廣播接收器)。在一些系統的廣播的使用場景:電量低彈窗,開機,鎖屏等。常見的廣播使用場景有下面幾種:
1.同一app內部的同一組件內的消息通信(單個或多個線程之間)
2.同一app內部的不同組件之間的消息通信(單個進程);
3.同一app具有多個進程的不同組件之間的消息通信;
4.不同app之間的組件之間消息通信;
5.Android系統在特定情況下與App之間的消息通信。

之所以叫做廣播,就是因為它只管"說"至于"聽不聽",那就不管了。另外廣播的另一個特點是,它可以被不止一個程序接收,也可以不被任何程序接收。廣播機制最大的特點就是發送方并不關心接收方是否接到數據,也不關心接收方是如何處理數據的。
Android中廣播的是操作系統中產生的各種各樣的事件。例如,收到一條短信就會產生一個收到短信息的事件。而Android操作系統一旦內部產生了這些事件,就會向所有的廣播接收器對象來廣播這些事件。

3.BroadCast Receiver廣播接收器

3.1 廣播的類型

1.標準廣播
標準關閉時一種完全一步執行的廣播,假設有很多個廣播接收器,當廣播發出去的時候,所有的接收器,同時接收到廣播消息。接收器之間沒有任何先后順序。這種廣播的頻率比較高,這也意味著它無法被截斷。標準廣播的工作流程如下圖所示:

標準廣播

2.有序廣播
這是一種同步執行的廣播,廣播接收器有先后之分,同一時刻只有一個接收器能夠接收廣播,而且,當優先級靠前的接收器沒有接收到廣播的時候,優先級靠后的接收器就無法接收到廣播了。而且優先級高的廣播還能截斷廣播。有序廣播的工作流程如下圖所示:

有序廣播

3.2 廣播接收器的注冊

1.靜態注冊
??靜態注冊方式是在AndroidManifest.xml的application里面定義receiver并設置要接收的action。如果在清單配置文件中配置了廣播接收器,那么程序在安裝后會自動注冊廣播接收器。
靜態注冊方式的特點:不管該應用程序是否處于活動狀態,都會進行監聽。程序即便未啟用,也可以接收廣播。
實現靜態注冊的步驟:
1.創建一個類BootBroadCastReceiver繼承BroadcastReceiver類,通過Android Studio創建,可以通過File->New->Other->Broadcast Receiver 這樣的快捷方式創建廣播接收器,AndroidManifest.xml文件會自動注冊。有一個<receiver> 標簽對。在onReceiver()中的方法很簡單,Toast一個就好了

public class BootBroadCastReceiver extends BroadcastReceiver {

    private static final String TAG = "BootBroadCastReceiver";
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "啟動Broadcast", Toast.LENGTH_SHORT).show();
    }
}

2.因為我們是需要接收廣播信息,所以需要權限RECEIVE_BOOT_COMPLETED。由于Android在啟動的時候會發出一條值為android.intent.action.BOOT_COMPLETED的廣播,所以 在<receiver>標簽對里面添加相應的activity.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.demo.broadcastreceiverdemo">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver
            android:name=".BootBroadCastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>
        </receiver>
    </application>

</manifest>

3.重啟模擬器,程序就可以接到開機廣播了。


靜態注冊

2.動態注冊
動態注冊也叫做代碼注冊,不需要在AndroidManifest.xml中注冊。而是在activity里面調用上下文對象的registerReceiver() 方法來注冊。和靜態的內容差不多。一個形參是receiver對象,另一個是IntentFilter對象。而IntentFilter構造方法的參數是要接收的action。
動態注冊方式特點:在代碼中進行注冊后,當應用程序關閉后,就不再進行監聽。
實現動態注冊的步驟,監聽網絡狀態:
1.創建一個類MyReceiver繼承BroadCast Receiver類,重寫父類的onReceiver()方法,這樣當網絡發生變化的時候,onReceiver的方法就會得到執行。

  class MyReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "網絡連接狀態發生改變", Toast.LENGTH_SHORT).show();
        }
    }

2.在onCreate()方法中創建一個MyReceiver實例,并創建一個IntentFilter的過濾器,指定action,因為當網絡狀態發生改變的時候,系統會發送android . net . conn . CONNECTIVITY _CHANGE這樣的廣播。如果想調用別的關閉可以使用相應的action。最后在調用registerReceiver()方法,將myReceiver和intentFilter的實例傳進去,這樣就完成了注冊。

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyReceiver   myReceiver = new MyReceiver();
       //創建過濾器,并制定action,使之用于接收同action的廣播
        IntentFilter  intentFilter=new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE");
        //注冊廣播接收器
        registerReceiver(myReceiver,intentFilter);

    }
       //創建過濾器的第二種寫法
          IntentFilter  intentFilter=new IntentFilter();
          intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");

3.最后在onDestory()的方法中調用 unregisterReceiver(),銷毀廣播,釋放內存

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

4.因為Android 系統為了保護用戶設備的隱私,所以對于一些相對于用戶來說的敏感權限需要在配置AndroidManifest.xml文件中添加權限聲明。

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

3.3發送自定義廣播

??因為我們知道我們所適應的實際既是廣播接收器,也是廣播發送器。如果只能接,不能發,那就和收音機沒啥區別了。前面已經介紹了,廣播主要分為兩種類型:標準廣播和有序廣播。這里介紹一下自定義的廣播,相信你看完之后會很興奮的,你可以定制你的Style了。

3.3.1 發送標準廣播

在思考發送廣播之前,我們需要有一個接收器,這樣發出去的東西,才能知道它到底有沒有被接收到。否則只管發了,發了有沒有結果不知道,這就做了無用功了。注冊的方式,就選擇靜態注冊,因為我們是自定義的廣播,所以,系統啟動的時候,不會直接發送廣播。
具體步驟:
1.創建一個類MyBroadCastReceiver繼承 BroadcastReceiver。這里就提示性的彈窗。

public class MyBroadCastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "快給我發過來啊,廣播", Toast.LENGTH_SHORT).show();
    }
}

2.在Androidmanifest.xml文件中,修改receiver標簽,添加需要發送的廣播。

   <receiver
            android:name=".myStyle.MyBroadCastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.demo.broadcastreceiverdemo.MY_BROADCAST" />
            </intent-filter>

        </receiver>

這里需要說明一下,我們在action標簽里面添加了一個com.demo.broadcastreceiverdemo. MY_BROADCAST的廣播。因此待會發送廣播的時候,我們就需要發送這樣的廣播。然后在receiver里面接收同樣的廣播標簽。特別說明一下,格式是全包名+自定義。要不然會報錯。你可以試試。
3.修改activity_main2.xml文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
  >

    <Button
        android:id="@+id/button"
        android:layout_width="150dp"
        android:layout_height="50dp"
        android:text="發送廣播"/>

</LinearLayout>

4.發送廣播

public class Main2Activity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Button button= (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent  intent=new Intent("com.demo.broadcastreceiverdemo.MY_BROADCAST");
                sendBroadcast(intent);
            }
        });
    }
}

我們把按鈕的點擊事件加入發送自定義廣播。首先構建Intent對象,然后把要發送的廣播傳入,然后調用Context的sendBroadCast()的方法,這樣所有監聽com.demo.broadcastreceiverdemo. MY_BROADCAST的接收器就會接收到這條廣播信息。運行效果如圖所示:

自定義標準廣播

這是一個簡單的小例子,我們通過在MainActivity中通過觸發點擊事件,來發送廣播,并且自定義接收器,接收廣播。

3.3.2 發送有序廣播

在開篇的廣播介紹中,我們知道有序廣播的原理,應用場景。我們知道廣播是可以跨進程進行通信的。因此在不同進程之間進行通信是可以實現的。為了測試有序廣播我們再新建一個project,然后在從第一個project開始發送廣播,第二個project實現接受。
實現步驟:
1.新建一個project ,然后新建一個類SecondBroadCastReceiver繼承BroadCastReceiver.


public class SecondBroadCastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "第二個進程接收廣播", Toast.LENGTH_SHORT).show();
    }

2.給第二個AndroidMainifest.xml文件中,修改receiver方法,設置action接受的廣播與第一個project的一樣com.demo.broadcastreceiverdemo.MY_BROADCAST。

    <receiver
            android:name=".SecondBroadCastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter >
                <action android:name="com.demo.broadcastreceiverdemo.MY_BROADCAST"/>
            </intent-filter>
        </receiver>

我們可以看到第一個和第一個project都可以接收同樣的廣播com.demo.broadcastreceiverdemo. MY_BROADCAST。然后回到第一個project界面,運行結果如下圖所示:

跨進程通信

我們可以看到,我們接受了兩次信息,分別為MyBroadCastReceiver和SecondBroadCastReceiver,所以我們的應用程序之間可以被通信。
不過到目前為止,我們的程序里發出的都是標準廣播,哪有序廣播呢?回到第一個project項目。

public class Main2Activity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Button button= (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent  intent=new Intent("com.demo.broadcastreceiverdemo.MY_BROADCAST");
//              sendBroadcast(intent);
                sendOrderedBroadcast(intent,null);
            }
        });
    }
}

修改intent的發送方法, sendOrderedBroadcast()。 這個方法接收兩個參數,第一個參數為intent,,第二個參數是一個與權限相關的字符串。這里傳個空值就可以了。然后重新運行第一個project,發現兩個應用程序還是可以接收廣播。

看上去好像沒有什么區別,但是我們知道有序廣播,是串行廣播,是有優先級的,廣播可以被攔截的。
1.設置優先級:

        <receiver
            android:name=".myStyle.MyBroadCastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter  android:priority="100">
                <action android:name="com.demo.broadcastreceiverdemo.MY_BROADCAST" />
            </intent-filter>
        </receiver>

在 intent-filter 添加 android:priority="100",數字可以自己設置。數字越大,優先級越高。當你將第二個project的 action 的值設置比第一個大,彈窗會第二個較第一個早。
2.攔截廣播

public class MyBroadCastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "快給我發過來啊,廣播", Toast.LENGTH_SHORT).show();
        abortBroadcast();//攔截廣播
    }
}

在重寫的onReceive()的方法里面加上 abortBroadcast()方法,這樣就完成了攔截廣播的操作。

4.使用本地廣播

我們之前講的廣播無論是發送還是接收全部都屬于系統全局廣播,發出的廣播可以被任何應用程序接收,而且也可以接收任何應用程序的廣播,所以這就會引發安全問題。通過廣播發送的信息被其他程序截獲了,或者別的程序不同的發送垃圾廣播給你,那還得了。
所以為了能夠解決安全性的問題,Android引入了本地廣播消息機制,這樣發送的廣播消息都只能在本應用程序里面傳遞,廣播接收器只能接收來自本應用的消息,這樣就不存在安全性問題了。代碼如下:
1.在MainActivity中,定義一個內部類集成BroadcCastReceiver

class LocalBroadCastReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "接受本地的廣播消息", Toast.LENGTH_SHORT).show();
        }
    }

2.設置發送的廣播和接收的廣播。我們這里因為要用到本地廣播,所以需要引入LocalBroadcastManager本地廣播管理類。通過getInstance()方法獲取實例,然后通過本地廣播調用sendBroadcast()去發送廣播,然后設置過濾器,過濾接收的廣播action,最后實例化本地廣播接收器,去注冊本地廣播。

    private LocalBroadcastManager localBroadcastManager;
    private IntentFilter intentFilter;
    private LocalBroadCastReceiver localBroadCastReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main3);
        //獲取實例
        localBroadcastManager = LocalBroadcastManager.getInstance(this);
        Button button= (Button) findViewById(R.id.button3);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent=new Intent("com.demo.broadcastreceiverdemo.MY_LOCAL_BROADCAST");
                localBroadcastManager.sendBroadcast(intent);
            }
        });
        intentFilter = new IntentFilter();
        intentFilter.addAction("com.demo.broadcastreceiverdemo.MY_LOCAL_BROADCAST");
        localBroadCastReceiver = new LocalBroadCastReceiver();
        localBroadcastManager.registerReceiver(localBroadCastReceiver,intentFilter);//注冊本地廣播監聽器
    }

3.還記得代碼注冊廣播,要銷毀

 @Override
    protected void onDestroy() {
        super.onDestroy();
      localBroadcastManager.unregisterReceiver(localBroadCastReceiver);
    }

完整代碼如下:


public class Main3Activity extends AppCompatActivity {

    private LocalBroadcastManager localBroadcastManager;
    private IntentFilter intentFilter;
    private LocalBroadCastReceiver localBroadCastReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main3);
        //獲取實例
        localBroadcastManager = LocalBroadcastManager.getInstance(this);
        Button button= (Button) findViewById(R.id.button3);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent=new Intent("com.demo.broadcastreceiverdemo.MY_LOCAL_BROADCAST");
                localBroadcastManager.sendBroadcast(intent);
            }
        });
        intentFilter = new IntentFilter();
        intentFilter.addAction("com.demo.broadcastreceiverdemo.MY_LOCAL_BROADCAST");
        localBroadCastReceiver = new LocalBroadCastReceiver();
        localBroadcastManager.registerReceiver(localBroadCastReceiver,intentFilter);//注冊本地廣播監聽器
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
      localBroadcastManager.unregisterReceiver(localBroadCastReceiver);
    }

    class LocalBroadCastReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "接受本地的廣播消息", Toast.LENGTH_SHORT).show();
        }
    }
}

實現效果如下所示:

本地廣播的使用

最后奉上github地址:https://github.com/wangxin3119/BroadCastDemo

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,237評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,957評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,248評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,356評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,081評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,485評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,534評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,720評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,263評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,025評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,204評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,787評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,461評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,874評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,105評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,945評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,205評論 2 375

推薦閱讀更多精彩內容