第九章:四大元件之Broadcast Receiver

自助者天助發表於2014-12-03

一、廣播的功能和特徵

  • 廣播的生命週期很短,經過呼叫物件-->實現onReceive-->結束,整個過程就結束了。從實現的複雜度和程式碼量來看,廣播無疑是最迷你的Android 元件,實現往往只需幾行程式碼。廣播物件被構造出來後通常只執行BroadcastReceiver.onReceive方法,便結束了其生命週期。所以有的時候我們可以把它當做函式看也未必不可。
  • Android中的四大元件是 Activity、Service、Broadcast和Content Provider。而Intent是一個對動作和行為的抽象描述,負責元件之間程式之間進行訊息傳遞。那麼Broadcast Receiver元件就提供了一種把Intent作為一個訊息廣播出去,由所有對其感興趣的程式對其作出反應的機制。
  • 和所有元件一樣,廣播物件也是在應用程式的主執行緒中被構造,所以廣播物件的執行必須是要同步且快速的。也不推薦在裡面開子執行緒,因為往往執行緒還未結束,廣播物件就已經執行完畢被系統銷燬。如果需要完成一項比較耗時的工作 , 應該通過傳送 Intent 給 Service, 由 Service 來完成。
  • 每次廣播到來時 , 會重新建立 BroadcastReceiver 物件 , 並且呼叫 onReceive() 方法 , 執行完以後 , 該物件即被銷燬 . 當 onReceive() 方法在 10 秒內沒有執行完畢, Android 會認為該程式無響應。

 

二、接收系統廣播:

廣播接收器可以自由地對自己感興趣的廣播進行註冊,這樣當有相應的廣播發出時,廣播接收器就能收到該廣播,並在內部處理相應的邏輯。註冊廣播的方式有兩種,在程式碼中註冊和在清單檔案中註冊,前者稱為動態註冊,後者稱為靜態註冊。

1、動態註冊監聽網路變化:

新建工程檔案,首先在MainActivity中定義一個內部類netWorkChangeReceiver,並重寫父類的onReceive()方法,這樣每當網路狀態發生變化時,onReceive()方法就會得到執行,這裡使用Toast提示一段文字資訊,程式碼如下:

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

緊接著在onCreate方法中進行動態註冊,然後在onDestroy方法中進行取消註冊:

複製程式碼
 1     private IntentFilter intentFilter;
 2     private netWorkChangeReceiver netWorkChangeReceiver;
 3     
 4     @Override
 5     protected void onCreate(Bundle savedInstanceState) {
 6         super.onCreate(savedInstanceState);
 7         setContentView(R.layout.activity_main);
 8         
 9         //動態註冊:建立一個IntentFilter的例項,新增網路變化的廣播(功能是對元件進行過濾,只獲取需要的訊息)
10         intentFilter = new IntentFilter();
11         intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
12         //建立NetWorkChangeReceiver的例項,並呼叫registerReceiver()方法進行註冊
13         netWorkChangeReceiver = new netWorkChangeReceiver();
14         registerReceiver(netWorkChangeReceiver, intentFilter);
15         
16     }
17 
18     //取消註冊,一定要記得,不然系統會報錯
19     @Override
20     protected void onDestroy() {
21         super.onDestroy();
22         unregisterReceiver(netWorkChangeReceiver);
23     }
複製程式碼

上方程式碼解釋如下:

11行:給意圖過濾器intentFilter新增一個值為android.net.conn.CONNECTIVITY_CHANGE的action。因為每當網路狀態發生變化時,系統就會發出一條值為android.net.conn.CONNECTIVITY_CHANG的廣播。

注:最後要記得,動態註冊的廣播接收器一定要取消註冊才行。

執行程式,就可以了。

不過只是提醒網路發生變化還不夠人性化,為了能夠準確的告訴使用者當前是有網路還是沒有網路,我們還需要對上述程式碼進一步優化,修改netWorkChangeReceiver中的程式碼如下:

複製程式碼
 1     class netWorkChangeReceiver extends BroadcastReceiver {
 2         
 3         @Override
 4         public void onReceive(Context context, Intent intent) {
 5             //通過getSystemService()方法得到connectionManager這個系統服務類,專門用於管理網路連線
 6             ConnectivityManager connectionManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
 7             NetworkInfo networkInfo = connectionManager.getActiveNetworkInfo();
 8             if(networkInfo != null && networkInfo.isAvailable()){
 9                 Toast.makeText(context, "network is available",Toast.LENGTH_SHORT).show();
10             }else{
11                 Toast.makeText(context, "network is unavailable",Toast.LENGTH_SHORT).show();
12             }
13                 
14         }
15     }
複製程式碼

上方程式碼解釋:

06行:在onReceive()方法中,首先通過通過getSystemService()方法得到connectionManager這個系統服務類,專門用於管理網路連線。

07行:然後呼叫它的getActiveNetworkInfo()方法可以得到NetworkInfo的例項,接著呼叫NetworkInfo的isAvailable()方法,就可以判斷當前是否有網路了,最後通過Toast提示使用者。

另外,查詢系統的網路狀態是需要申明許可權的,開啟清單檔案,新增如下許可權:

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

注:訪問http://developer.android.com/reference/android/Manifest.permission.html可以檢視Android系統所有的可宣告的許可權。

現在執行程式,就可以了。

上方程式完整版程式碼如下:

View Code 

 

2、靜態註冊實現開機啟動:

動態註冊的方式比較靈活,但缺點是:必須在程式啟動之後才能接收到廣播,因為註冊的邏輯是寫在onCreate()方法中的。為了讓程式在未啟動的情況下就能接收到廣播,這裡就需要使用到靜態註冊。

這裡我們準備讓程式接收一條開機廣播,當收到這條廣播時,就可以在onReceive()方法中執行相應的邏輯,從而實現開機啟動的功能。

新建一個類:BootCompleteReceiver,讓他繼承BroadcastReceiver,在onReceive()方法中簡單地Toast一下,程式碼如下:

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

可以看到,這裡不再使用內部類的方式來定義廣播接收器,因為稍後我們需要在清單檔案AndroidManifest.xml中將這個廣播接收器的類名註冊進去。

然後修改清單檔案AndroidManifest.xml,程式碼如下:

複製程式碼
 1 <uses-sdk
 2         android:minSdkVersion="8"
 3         android:targetSdkVersion="16" />
 4     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
 5     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
 6 
 7     <application
 8         android:allowBackup="true"
 9         android:icon="@drawable/ic_launcher"
10         android:label="@string/app_name"
11         android:theme="@style/AppTheme" >
12         <activity
13             android:name="com.example.m05_broadcastreceiver01.MainActivity"
14             android:label="@string/app_name" >
15             <intent-filter>
16                 <action android:name="android.intent.action.MAIN" />
17 
18                 <category android:name="android.intent.category.LAUNCHER" />
19             </intent-filter>
20         </activity>
21         
22         <receiver android:name=".BootCompleteReceiver">
23             <intent-filter >
24                 <action android:name="android.intent.action.BOOT_COMPLETED"/>
25             </intent-filter>
26         </receiver>
27     </application>
複製程式碼

程式碼解釋如下:

終於,<application>標籤內多了個子標籤<receiver>,所有的靜態註冊的廣播接收器都是在這裡進行註冊的。

22行:name中為廣播接收器的名字

24行:想要接收的廣播。Android系統啟動完成後,會發出這條名為android.intent.action.BOOT_COMPLETED的廣播。

05行:監聽系統開機廣播需要宣告許可權。

執行程式後,將手機關機重啟,就能收到這條廣播了。

 

三、傳送自定義廣播

1、傳送標準廣播  

新建工程檔案。在發廣播之前,我們先定義一個廣播接收器來接收此廣播才行。因此,新建一個類:MyBroadcastReceiver,讓他繼承BroadcastReceiver,程式碼如下:

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

這裡,當MyBroadcastReceiver 收到自定義的廣播時,就會執行onReceive()方法中的邏輯,彈出一個Toast。

緊接著,要在清單檔案AndroidManifest.xml中對這個廣播接收器進行註冊:

複製程式碼
 1     <application
 2         android:allowBackup="true"
 3         android:icon="@drawable/ic_launcher"
 4         android:label="@string/app_name"
 5         android:theme="@style/AppTheme" >
 6         <activity
 7             android:name="com.example.m05_broadcastreceiver02.MainActivity"
 8             android:label="@string/app_name" >
 9             <intent-filter>
10                 <action android:name="android.intent.action.MAIN" />
11 
12                 <category android:name="android.intent.category.LAUNCHER" />
13             </intent-filter>
14         </activity>
15         
16         <receiver android:name=".MyBroadcastReceiver">
17             <intent-filter >
18                 <action android:name="com.example.m05_broadcastreceiver02.MY_BROADCAST"/>
19             </intent-filter>
20         </receiver>
21     </application>
複製程式碼

程式碼解釋:

18行:讓MyBroadcastReceiver接收一條值為om.example.m05_broadcastreceiver02.MY_BROADCAST的廣播,因此待會兒在傳送廣播的時候,我們就需要發出這樣的一條廣播。

緊接著,修改activity.xml中的程式碼,新增一個按鈕Button。

然後,修改MainActivity.java中的程式碼,新增Button的監聽事件:點選按鈕時,傳送廣播

 

複製程式碼
        Button button1=(Button)findViewById(R.id.button1);
        button1.setOnClickListener(new OnClickListener() {            
            @Override
            public void onClick(View v) {
                Intent intent =new Intent("com.example.m05_broadcastreceiver02.MY_BROADCAST");
                sendBroadcast(intent);
            }
        });
複製程式碼

總結:可以看到,點選按鈕時,傳送com.example.m05_broadcastreceiver02.MY_BROADCAST這條廣播,這樣,所有能夠監聽com.example.m05_broadcastreceiver02.MY_BROADCAST這條廣播的廣播接收器就都會同時收到訊息,此時發出去的就是一條標準廣播,即無序廣播。所以接下來就需要講到有序廣播。

 

2、傳送有序廣播:

廣播是一種可以跨程式的通訊方式,其他應用程式是可以收到的。現在我們來發一條有序廣播。

有序廣播不僅有先後順序,而且前面的廣播還可以將後面的廣播截斷。

在3.1的程式碼基礎之上,將按鈕的監聽事件修改如下:

複製程式碼
1         Button button1=(Button)findViewById(R.id.button1);
2         button1.setOnClickListener(new OnClickListener() {            
3             @Override
4             public void onClick(View v) {
5                 Intent intent =new Intent("com.example.m05_broadcastreceiver02.MY_BROADCAST");
6                 sendOrderedBroadcast(intent, null);
7             }
8         });
複製程式碼

即將06行程式碼修改一下,將sendBroadcast()方法改為sendOrderedBroadcast()方法,sendOrderedBroadcast()方法接收兩個引數,第二個引數是一個與許可權相關的字串,這裡傳入null即可。

緊接著,修改清單檔案AndroidManifest.xml中對廣播接收器的註冊,設定優先順序:

1         <receiver android:name=".MyBroadcastReceiver">
2             <intent-filter android:priority="100">
3                 <action android:name="com.example.m05_broadcastreceiver02.MY_BROADCAST"/>
4             </intent-filter>
5         </receiver>

即新增第02行程式碼。可以看到,通過android:priority屬性給廣播接收器設定了優先順序。這個屬性的範圍在-1000到1000,數值越大,優先順序越高。

接下來,如果想要攔截這個廣播,防止讓後面的廣播接收器也接收到了這個廣播。可以修改MyBroadcastReceiver中的程式碼:

複製程式碼
1 public class MyBroadcastReceiver extends BroadcastReceiver {
2 
3     @Override
4     public void onReceive(Context context, Intent intent) {
5         Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
6         abortBroadcast();//攔截廣播,防止後面的接收到
7     }
8 }
複製程式碼

即新增第06行程式碼。如果在onReceive()方法中呼叫了abortBroadcast()方法,就表示是將這條廣播攔截,後面的廣播接收器將無法再接收到。

特別關注:

  • 廣播接收器的生命週期:關鍵在於BroadcastReceiver中的onReceive()方法,從onReceive()裡的第一行程式碼開始,onReceive()裡的最後一行程式碼結束。
  • 一個廣播到來的時候,用什麼方式提醒使用者是最友好的呢?第一種方式是吐司,第二種方式是通知。注:不要使用對話方塊,以免中斷了使用者正在進行的操作。

 

四、使用本地廣播:

之前我們傳送和接收的廣播全部都是屬於全域性廣播,即發出去的廣播可以被其他任何應用程式接收到,並且我們也可以接收來自於其他任何應用程式的廣播。這樣一來,必然會造成安全問題。於是便有了本地廣播:即只能在本應用程式中傳送和接收廣播。這就要使用到了LocalBroadcastManager這個類來對廣播進行管理。

我們修改2.1中動態註冊廣播接收器的程式碼,即修改MainActivity.java中的程式碼如下:

複製程式碼
package com.example.broadcasttest;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

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的getInstance()方法得到它的一個例項
        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);//呼叫sendBroadcast()方法傳送廣播
            }
        });
        //動態註冊本地的廣播接收器
        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();
        }
    }
}
複製程式碼

注:本地廣播是無法通過靜態註冊的方式來接收的。其實也完全可以理解,因為靜態註冊主要就是為了讓程式在未啟動的情況下也能收到廣播。而傳送本地廣播時,我們的程式肯定是已經啟動了,沒有必要使用到靜態註冊的功能。

 

五、各種各樣的廣播: 

在android中有很多系統自帶的intent.action,通過監聽這些事件我們可以完成很多功能。

    1. 開機: String BOOT_COMPLETED_ACTION 廣播:在系統啟動後。這個動作被廣播一次(只有一次)。監聽: “android.intent.action.BOOT_COMPLETED”
    2. 電話撥入: String ANSWER_ACTION 動作:處理撥入的電話。監聽: “android.intent.action.ANSWER”
    3. 電量變化: String BATTERY_CHANGED_ACTION 廣播:充電狀態,或者電池的電量發生變化。監聽: “android.intent.action.BATTERY_CHANGED”
    4. 日期改變: String DATE_CHANGED_ACTION 廣播:日期被改變。 監聽:“android.intent.action.DATE_CHANGED”
    5. 取消更新下載: String FOTA_CANCEL_ACTION 廣播:取消所有被掛起的 (pending) 更新下載。 監聽:“android.server.checkin.FOTA_CANCEL”
    6. 更新開始安裝: String FOTA_READY_ACTION 廣播:更新已經被下載 可以開始安裝。監聽 “android.server.checkin.FOTA_READY”
    7. 主螢幕: String HOME_CATEGORY 類別:主螢幕 (activity)。裝置啟動後顯示的第一個 activity。 監聽:"android.intent.category.HOME”
    8. 新應用: String PACKAGE_ADDED_ACTION 廣播:裝置上新安裝了一個應用程式包。監聽: “android.intent.action.PACKAGE_ADDED”
    9. 刪除應用: String PACKAGE_REMOVED_ACTION 廣播:裝置上刪除了一個應用程式包。監聽: “android.intent.action.PACKAGE_REMOVED”
    10. 螢幕關閉: String SCREEN_OFF_ACTION 廣播:螢幕被關閉。監聽: “android.intent.action.SCREEN_OFF”
    11. 螢幕開啟: String SCREEN_ON_ACTION 廣播:螢幕已經被開啟。 監聽:“android.intent.action.SCREEN_ON”
    12. 時區改變: String TIMEZONE_CHANGED_ACTION 廣播:時區已經改變。監聽: “android.intent.action.TIMEZONE_CHANGED”
    13. 時間改變: String TIME_CHANGED_ACTION 廣播:時間已經改變(重新設定)。 “android.intent.action.TIME_SET”
    14. 時間流逝: String TIME_TICK_ACTION 廣播:當前時間已經變化(正常的時間流逝)。 “android.intent.action.TIME_TICK”
    15. 進入大容量儲存模式: String UMS_CONNECTED_ACTION 廣播:裝置進入 USB 大容量儲存模式。 “android.intent.action.UMS_CONNECTED”
    16. 退出大容量儲存模式: String UMS_DISCONNECTED_ACTION 廣播:裝置從 USB 大容量儲存模式退出。 “android.intent.action.UMS_DISCONNECTED”
    17. 桌布改變: String WALLPAPER_CHANGED_ACTION 廣播:系統的牆紙已經改變。 “android.intent.action.WALLPAPER_CHANGED”
    18. web搜尋: String WEB_SEARCH_ACTION 動作:執行 web 搜尋。 “android.intent.action.WEB_SEARCH”
    19. 網路變化: String CONNECTIVITY_CHANGE_ACTION 動作:網路變化。“android.intent.action.CONNECTIVITY_CHANGE_ACTION”

 

六、例項:使用動態註冊,監聽手機的電量變化。

完整版程式碼如下:

複製程式碼
 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:paddingBottom="@dimen/activity_vertical_margin"
 6     android:paddingLeft="@dimen/activity_horizontal_margin"
 7     android:paddingRight="@dimen/activity_horizontal_margin"
 8     android:paddingTop="@dimen/activity_vertical_margin"
 9     tools:context=".MainActivity" >
10 
11     <TextView
12         android:id="@+id/textView1"
13         android:layout_width="match_parent"
14         android:layout_height="wrap_content"
15         android:textSize="30dp"
16         android:gravity="center"/>
17 
18 </LinearLayout> 
複製程式碼

activity_main.xml程式碼如下:

複製程式碼
 1 package com.example.m05_broadcastreceiver02;
 2 
 3 import android.app.Activity;
 4 import android.content.BroadcastReceiver;
 5 import android.content.Context;
 6 import android.content.Intent;
 7 import android.content.IntentFilter;
 8 import android.os.Bundle;
 9 import android.widget.TextView;
10 
11 public class MainActivity extends Activity {
12 
13     
14     private BatteryBroadcastReceiver batteryBroadcastReceiver;
15     private TextView textView;
16     @Override
17     protected void onCreate(Bundle savedInstanceState) {
18         super.onCreate(savedInstanceState);
19         setContentView(R.layout.activity_main);
20         textView=(TextView)findViewById(R.id.textView1);
21         
22         //動態註冊監聽電量的廣播接收器
23         IntentFilter intentFilter = new IntentFilter();
24         intentFilter.addAction("android.intent.action.BATTERY_CHANGED");
25         batteryBroadcastReceiver = new BatteryBroadcastReceiver();
26         registerReceiver(batteryBroadcastReceiver, intentFilter);       
27     }
28     
29     //取消註冊監聽電量的廣播接收器
30     @Override
31     protected void onDestroy() {
32         super.onDestroy();
33         unregisterReceiver(batteryBroadcastReceiver);
34     }
35 
36     //新建一個廣播接收器,監聽電量的變化
37     public class BatteryBroadcastReceiver extends BroadcastReceiver {
38         @Override
39         public void onReceive(Context context, Intent intent) {
40             if(intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) {
41                 //獲取當前電量
42                 int level = intent.getIntExtra("level", 0);
43                 //電量的總刻度
44                 int scale = intent.getIntExtra("scale", 100);
45                 textView.setText("電池電量為"+((level*100) / scale)+"%");
46                 
47                 //當電量低時,可以進行一些操作,例如彈出通知等
48 /*                if(level<15){
49                     do something
50                 }*/
51             }
52         }
53         
54     }
55     
56 }
複製程式碼

緊接著,在清單檔案中進行許可權宣告:

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

MainActivity.java的程式碼解釋如下:

40至45行:固定程式碼,用於獲取當前電量

48至50行:當電量低時,可以進行一些操作,例如彈出通知等

執行後,介面如下:

相關文章