閱讀郭林《第一行代碼》的筆記——第5章 全局大喇叭,詳解廣播機制

1、廣播機制簡介

Android中的每個應用程序都可以對自己感興趣的廣播進行註冊,這樣該程序就只會接收到自己所關心的廣播內容,這些廣播可能是來自於系統的,也可能是來自於其他應用程序的。Android提供了一套完整的API,允許應用程序自由地發送和接收廣播。發送廣播的方法就是藉助Intent,而接收廣播的方法則藉助廣播接收器(Broadcast Receiver)。
Android中的廣播主要可以分爲兩種類型,標準廣播和有序廣播。
標準廣播(Normal broadcasts)是一種完全異步執行的廣播,在廣播發出之後,所有的廣播接收器幾乎都會在同一時刻接收到這條廣播消息,因此它們之間沒有任何先後順序可言。這種廣播的效率會比較高,但同時也意味着它是無法被截斷的。標準廣播的工作流程如圖5.1所示。
這裏寫圖片描述
有序廣播(Ordered broadcasts)則是一種同步執行的廣播,在廣播發出之後,同一時刻只會有一個廣播接收器能夠收到這條廣播消息,當這個廣播接收器中的邏輯執行完畢後,廣播纔會繼續傳遞。所以此時的廣播接收器是有先後順序的,優先級高的廣播接收器就可以先收到廣播消息,並且前面的廣播接收器還可以截斷正在傳遞的廣播,這樣後面的廣播接收器就無法收到廣播消息了。有序廣播的工作流程如圖5.2所示。
這裏寫圖片描述

2、動態註冊監聽網絡變化

廣播接收器可以自由地對自己感興趣的廣播進行註冊,這樣當有相應的廣播發出時,廣播接收器就能夠收到該廣播,並在內部處理相應的邏輯。註冊廣播的方式一般有兩種,在代碼中註冊和在AndroidManifest.xml中註冊,其中前者也被稱爲動態註冊,後者也被稱爲靜態註冊。
那麼該如何創建一個廣播接收器呢?其實只需要新建一個類,讓它繼承自BroadcastReceiver,並重寫父類的onReceive()方法就行了。這樣當有廣播到來時,onReceive()方法就會得到執行,具體的邏輯就可以在這個方法中處理。
那我們就先通過動態註冊的方式編寫一個能夠監聽網絡變化的程序,藉此學習一下廣播接收器的基本用法吧。新建一個 BroadcastTest項目,然後修改MainActivity中的代碼,如下所示:

public class MainActivity extends Activity {

    private IntentFilter intentFilter;
    private NetworkChangeReceiver networkChangeReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        intentFilter = new IntentFilter();//創建了一個IntentFilter的實例
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");//給這個IntentFilter添加了一個值爲android.net.conn.CONNECTIVITY_CHANGE的action
        networkChangeReceiver = new NetworkChangeReceiver();//創建了一個NetworkChangeReceiver的實例
        registerReceiver(networkChangeReceiver, intentFilter);//調用registerReceiver() 方法進行註冊,將 NetworkChangeReceiver的實例和IntentFilter的實例都傳了進去
    }

//動態註冊的廣播接收器一定都要取消註冊才行,這裏我們是在onDestroy()方法中通過調用unregisterReceiver()方法來實現的。
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(networkChangeReceiver);
    }

    class NetworkChangeReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {//當網絡狀態發生變化時,onReceive()方法就會得到執行
//通過getSystemService()方法得到了ConnectivityManager的實例,這是一個系統服務類,專門用於管理網絡連接的。
           ConnectivityManager connectionManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
//調用它的getActiveNetworkInfo() 方法可以得到 NetworkInfo的實例
       NetworkInfo networkInfo = connectionManager.getActiveNetworkInfo();
//調用NetworkInfo 的isAvailable()方法,就可以判斷出當前是否有網絡了
       if (networkInfo != null && networkInfo.isAvailable()) {
            Toast.makeText(context, "network is available",Toast.LENGTH_SHORT).show();
       } else {
            Toast.makeText(context, "network is unavailable",Toast.LENGTH_SHORT).show();
       }
        }
    }

}

另外,這裏有非常重要的一點需要說明,Android系統爲了保證應用程序的安全性做了規定,如果程序需要訪問一些系統的關鍵性信息,必須在配置文件中聲明權限纔可以,否則程序將會直接崩潰,比如這裏查詢系統的網絡狀態就是需要聲明權限的。打開AndroidManifest.xml文件,在裏面加入如下權限就可以查詢系統網絡狀態了:

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

3、靜態註冊實現開機啓動

動態註冊的廣播接收器可以自由地控制註冊與註銷,在靈活性方面有很大的優勢,但是它也存在着一個缺點,即必須要在程序啓動之後才能接收到廣播,因爲註冊的邏輯是寫在onCreate()方法中的。那麼有沒有什麼辦法可以讓程序在未啓動的情況下就能接收到廣播呢?這就需要使用靜態註冊的方式了。
這裏我們準備讓程序接收一條開機廣播,當收到這條廣播時就可以在onReceive()方法裏執行相應的邏輯,從而實現開機啓動的功能。新建一個BootCompleteReceiver繼承自BroadcastReceiver,代碼如下所示:

public class BootCompleteReceiver extends BroadcastReceiver {     
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show();
    }
}

可以看到,這裏不再使用內部類的方式來定義廣播接收器,因爲稍後我們需要在AndroidManifest.xml中將這個廣播接收器的類名註冊進去。在onReceive()方法中,還是簡單地使用Toast彈出一段提示信息。
然後修改AndroidManifest.xml文件,代碼如下所示:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.broadcasttest"
    android:versionCode="1"
    android:versionName="1.0" >
    ……
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
          ……
//<application>標籤內出現了一個新的標籤,所有靜態註冊的廣播接收器都是在這裏進行註冊的。
        <receiver android:name=".BootCompleteReceiver" >//通過android:name來指定具體註冊哪一個廣播接收器
            <intent-filter>//在<intent-filter> 標籤里加入想要接收的廣播就行了
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>
    </application>
</manifest>

另外,監聽系統開機廣播也是需要聲明權限的,可以看到,我們使用標籤又加入了一條android.permission.RECEIVE_BOOT_COMPLETED權限。
需要注意的是,不要在onReceive()方法中添加過多的邏輯或者進行任何的耗時操作,因爲在廣播接收器中是不允許開啓線程的,當onReceive()方法運行了較長時間而沒有結束時,程序就會報錯。因此廣播接收器更多的是扮演一種打開程序其他組件的角色,比如創建一條狀態欄通知,或者啓動一個服務等。

4、發送標準廣播

例子:Intent發送標準廣播

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

  <receiver android:name=".MyBroadcastReceiver">
            <intent-filter>
                <action android:name="com.example.broadcasttest. MY_BROADCAST"/>
            </intent-filter>
  </receiver>

Intent intent = new Intent("com.example.broadcasttest. MY_BROADCAST");
sendBroadcast(intent);

5、發送有序廣播

廣播是一種可以跨進程的通信方式,這一點從前面接收系統廣播的時候就可以看出來了。因此在我們應用程序內發出的廣播,其他的應用程序應該也是可以收到的。
修改尚一個例子:

Intent intent = new Intent("com.example.broadcasttest. MY_BROADCAST");
sendOrderedBroadcast(intent, null);//sendOrderedBroadcast()方法接收兩個參數,第一個參數仍然是Intent,第二個參數是一個與權限相關的字符串,這裏傳入null就行了。
那麼該如何設定廣播接收器的先後順序呢?當然是在註冊的時候進行設定的了,修改AndroidManifest.xml中的代碼,如下所示:
<receiver android:name=".MyBroadcastReceiver">
   <intent-filter android:priority="100" >//通過android:priority 屬性給廣播接收器設置了優先級,優先級比較高的廣播接收器就可以先收到廣播。
     <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
   </intent-filter>
</receiver>

既然已經獲得了接收廣播的優先權,那麼 MyBroadcastReceiver就可以選擇是否允許廣播繼續傳遞了。修改 MyBroadcastReceiver中的代碼,如下所示:

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

如果在onReceive() 方法中調用了 abortBroadcast()方法,就表示將這條廣播截斷,後面的廣播接收器將無法再接收到這條廣播。

6、使用本地廣播

前面我們發送和接收的廣播全部都是屬於系統全局廣播,即發出的廣播可以被其他任何的任何應用程序接收到,並且我們也可以接收來自於其他任何應用程序的廣播。這樣就很容易會引起安全性的問題,比如說我們發送的一些攜帶關鍵性數據的廣播有可能被其他的應用程序截獲,或者其他的程序不停地向我們的廣播接收器裏發送各種垃圾廣播。
爲了能夠簡單地解決廣播的安全性問題,Android引入了一套本地廣播機制,使用這個機制發出的廣播只能夠在應用程序的內部進行傳遞,並且廣播接收器也只能接收來自本應用程序發出的廣播,這樣所有的安全性問題就都不存在了。
本地廣播的用法並不複雜,主要就是使用了一個 LocalBroadcastManager來對廣播進行管理,並提供了發送廣播和註冊廣播接收器的方法。下面我們就通過具體的實例來嘗試一下它的用法,修改 MainActivity中的代碼,如下所示:

public class MainActivity extends Activity {

    private IntentFilter intentFilter;
    private LocalReceiver localReceiver;
    private LocalBroadcastManager localBroadcastManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        localBroadcastManager = LocalBroadcastManager.getInstance(this); // 獲取實例
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("com.example.broadcasttest. LOCAL_BROADCAST");
                localBroadcastManager.sendBroadcast(intent); // 發送本地廣播
            }
        });
        intentFilter = new IntentFilter();
        intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");
        localReceiver = new LocalReceiver();
        localBroadcastManager.registerReceiver(localReceiver, intentFilter); // 註冊本地廣播監聽器
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        localBroadcastManager.unregisterReceiver(localReceiver);
    }

    class LocalReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show();
        }
    }

}

另外還有一點需要說明,本地廣播是無法通過靜態註冊的方式來接收的。其實這也完全可以理解,因爲靜態註冊主要就是爲了讓程序在未啓動的情況下也能收到廣播,而發送本地廣播時,我們的程序肯定是已經啓動了,因此也完全不需要使用靜態註冊的功能。
使用本地廣播的幾點優勢吧。
1.可以明確地知道正在發送的廣播不會離開我們的程序,因此不需要擔心機密數據泄漏的問題。
2.其他的程序無法將廣播發送到我們程序的內部,因此不需要擔心會有安全漏洞的隱患。
3.發送本地廣播比起發送系統全局廣播將會更加高效。
廣播的最佳實踐——實現強制下線功能
例子:
省略部分代碼……
創建一個廣播接收器了,新建 ForceOfflineReceiver繼承自BroadcastReceiver ,代碼如下所示:

public class ForceOfflineReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(final Context context, Intent intent) {
        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
        dialogBuilder.setTitle("Warning");
        dialogBuilder.setMessage("You are forced to be offline. Please try to login again.");
        dialogBuilder.setCancelable(false);
        dialogBuilder.setPositiveButton("OK",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        ActivityCollector.finishAll(); // 銷燬所有活動
                        Intent intent = new Intent(context, LoginActivity.class);
                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        context.startActivity(intent); // 重新啓動 LoginActivity
                    }
                });
        AlertDialog alertDialog = dialogBuilder.create();
          // 需要設置 AlertDialog的類型,保證在廣播接收器中可以正常彈出
        alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
        alertDialog.show();
    }

}

由於我們是在廣播接收器裏啓動活動的,因此一定要給Intent加入 FLAG_ACTIVITY_NEW_TASK這個標誌。最後,還需要把對話框的類型設爲 TYPE_SYSTEM_ALERT,不然它將無法在廣播接收器裏彈出。
對AndroidManifest.xml 文件進行配置,代碼如下所示:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.broadcastbestpractice"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk
        android:minSdkVersion="14"
        android:targetSdkVersion="19" />

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

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".LoginActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MainActivity" >
        </activity>
        <receiver android:name=".ForceOfflineReceiver" >
            <intent-filter>
                <action android:name="com.example.broadcastbestpractice. FORCE_OFFLINE" />
            </intent-filter>
        </receiver>
    </application>
</manifest>
發佈了259 篇原創文章 · 獲贊 47 · 訪問量 39萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章