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

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

廣播機制簡介

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

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

  • 有序廣播:一種同步執行的廣播,在廣播發出之后,同一時刻只會有一個廣播接收器能夠收到這條廣播消息,當這個廣播接收器中的邏輯執行完畢后廣播才會繼續傳遞。此時廣播接收器有先后順序,優先級高的廣播接收器可以先收到廣播消息,并且前面的廣播接收器可以截斷廣播。

接收系統廣播

Android內置了很多系統級別的廣播,我們可以在應用程序中通過監聽這些廣播來得到各種系統的狀態信息。使用廣播接收器可以自由地對需要的廣播進行注冊,當有相應廣播發出時,廣播接收器就能夠收到該廣播,并對其進行邏輯處理。
注冊廣播的方式一般有兩種:一是動態注冊,即在代碼中注冊;二是靜態注冊,即在AndroidManifest文件中注冊。
創建廣播接收器的方法是新建一個類,繼承BroadcastReceiver類,并重寫onReceive()方法,當有廣播到來時,onReceive()方法會被調用,我們可以在onReceive()方法中添加具體邏輯。

  • 動態注冊監聽網絡變化
    下面我們使用動態注冊的方式來完成一個監聽網絡變化的demo:
    首先,在AndroidManifest文件中聲明系統網絡狀態的權限,因為我們要監聽系統網絡狀態的變化,就必須擁有這個權限:

    <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. 第一步,創建一個內部類NetworkChangeReceiver,繼承BroadcastReceiver類,重寫它的onReceive()方法。在onReceive()方法中通過getSystemService()方法得到一個ConnectivityManager的實例,這是一個系統服務類,專門用于管理網絡連接的。然后調用它的getActiveNetworkInfo()方法可以得到NetworkInfo的實例,接著調用NetworkInfo的isConnected()方法,就可以判斷出當前是否有網絡了。當然,想要onReceive()方法被觸發,我們需要對網絡狀態變化時發出的系統廣播進行注冊。
    2. 第二步,因為系統網絡狀態發生變化時,會發出一條值為
      android.net.conn.CONNECTIVITY_CHANGE 的廣播,所以我們創建一個IntentFilter實例,并給它添加值為 android.net.conn.CONNECTIVITY_CHANGE 的action(也就是說,我們想要監聽什么廣播,就添加其對應的action)。
    3. 第三步,創建一個NetworkChangeReceiver的實例,然后調用 registerReceiver() 方法進行注冊,將NetworkChangeReceiver的實例和IntentFilter的實例傳進去,這樣就完成了注冊,也就是說,當系統發出值為android.net.conn.CONNECTIVITY_CHANGE的廣播時,我們的onReceive()方法就會被觸發。
    4. 第四步,重寫onDestroy()方法,調用unregisterReceiver()方法取消注冊。這里要注意,我們動態注冊的廣播都需要取消注冊。下面看效果(打開我們的Demo,然后切到網絡設置的頁面,注意,不要按back鍵退出demo,不然BroadcastActivity會被銷毀):
  • 靜態注冊實現開機啟動
    動態注冊的廣播接收器可以自由地控制注冊與注銷,在靈活性上有很大的優勢,但是它也有自己的局限性,即必須在程序啟動后才能接收到廣播,因為注冊邏輯是寫在onCreate()方法中的。這時候,靜態注冊的方式就有用武之地了。接下來,我們通過靜態注冊的方式來接收一條開機廣播。

    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屬性指定我們創建的廣播接收器(完整路徑);android:enabled
    屬性定義系統是否能夠實例化這個廣播接收器,為true時這個廣播接收器才能被啟用,默認為true;android:exported屬性用于指示該廣播接收器是否能夠接收來自應用程序外部的消息。然后在<intent-filter>標簽中添加系統開機廣播對應的action,這和動態注冊創建Intentfilter實例并添加action的作用是一樣的。

    1. 第三步,由于我們需要監聽系統開機廣播,因此也需要為其聲明權限,在AndroidManifest文件中聲明權限:
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    

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

自定義廣播

前面是關于如何使用廣播接收器來接收系統廣播,接下來我們來看看如何發送自定義廣播。

  • 發送標準廣播
    首先來新建一個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,也就意味著,我們待會兒發出一條值為這個的廣播,我們的廣播接收器就能收到。
    接下來,在我們的布局文件中添加一個Button,用于觸發發送廣播(很簡單,我就不貼代碼了),然后修改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);
        }
    }
    

    發送廣播的方法很簡單,就是創建一個Intent對象,將需要發送的廣播的值作為參數傳遞進去,然后調用sendBroadcast()方法將廣播發送出去,這樣監聽 com.laughter.broadcast.DIY_BROADCAST 這個值的廣播接收器就能收到這條廣播(這里就不貼效果圖了,大家可以自己驗證)。由于是用Intent發送廣播,因此,還可以用這個Intent對象攜帶一些參數。
    廣播是一種可以跨進程的通信方式,因此,我們在應用程序中發送的廣播,別的應用程序也是可以收到的。下面我們來驗證一下:
    首先創建一個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中的廣播接收器都收到了這條廣播。

  • 發送有序廣播
    要發送一條有序廣播,我們只需要在前面的BroadcastActivity中,將sendBroadcast(intent) 方法換成 sendOrderedBroadcast(intent, null) 方法就行了(第一個參數還是Intent對象,第二個參數是與權限相關的字符串,這里直接傳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);
        }
    }
    

    前面說過,發送有序廣播的時候,廣播接收器之間存在優先級,那么如何指定優先級呢?

    <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 屬性就行了,里面的值就是優先級,值越大,優先級越高。同樣的,給AnotherReceiver指定一個優先級,數字比這里的13小就行。這樣,優先級高的廣播接收器就會先收到廣播。(這里也不貼圖了,自行驗證)
    前面還提到過,有序廣播是可以被攔截的,如何攔截呢?我們只需要在廣播接收器的onReceive()方法中調用 abortBroadcast(); 方法就可以攔截廣播,這樣優先級比這個廣播接收器低的就收不到這條廣播了。

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

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

    可以看到,現在只有一個優先級高的廣播接收器的onReceive()方法被觸發,彈出了Toast,而由于廣播被攔截,優先級低的AnotherReceiver沒有收到廣播,所以沒有彈出Toast。

使用本地廣播

前面我們發送和接收的廣播全部屬于系統全局廣播,即發出的廣播可以被任何其他的應用程序接收到,并且也可以接受其他任何應用程序的廣播。這樣就很容易引起安全性問題。為此,Android引入了一套本地廣播機制,使用本地廣播機制,廣播只能在應用程序內部進行傳遞,并且廣播接收器也只會接收到應用程序內部的廣播。
其實本地廣播的用法和前面差不多,我們先來看代碼:

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");
        }
    }
}

和前面一樣,我們首先定義一個內部類,即廣播接收器,然后定義一個 LocalBroadcastManager 對象,通過它的 getInstance() 方法獲取一個實例,接下來就是定義IntentFilter對象和廣播接收器對象,給IntentFilter添加一條值為 com.laughter.broadcast.LOCAL_BROADCAST 的action,然后調用 LocalBroadcastManager 的 registerReceiver() 方法注冊本地廣播監聽器,接下來在Button的點擊事件中通過 LocalBroadcastManager 的 sendBroadcast() 方法發送廣播,最后別忘了在onDestroy() 方法中取消注冊。這樣看起來,本地廣播區別于全局廣播的地方僅僅就是通過一個LocalBroadcastmanager 來管理廣播的注冊和發送。這樣我們的廣播就是僅僅在應用程序內部傳遞的了(感興趣的自行驗證)。
注意:既然需要通過 LocalBroadcastmanager 來管理廣播的注冊和發送,那么對應的靜態注冊的方式怎么實現呢?很遺憾,本地廣播是無法通過靜態注冊的方式來接收的。不過仔細想想,本地廣播的發送很顯然是需要在應用程序啟動的情況下完成的,既然要在應用程序啟動的情況下發送廣播,那么也不用考慮在應用程序未啟動的情況下接收廣播了。而我們使用靜態注冊主要就是為了在應用程序未啟動的情況下也能收到廣播,所以思路就很清晰了。


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


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容