【聲明】
歡迎轉載,但請保留文章原始出處→_→
生命壹號:http://www.cnblogs.com/smyhvae/
文章來源:http://www.cnblogs.com/smyhvae/p/3960623.html
【正文】
一、廣播的功能和特征
- 廣播的生命周期很短,經過調用對象-->實現onReceive-->結束,整個過程就結束了。從實現的復雜度和代碼量來看,廣播無疑是最迷你的Android 組件,實現往往只需幾行代碼。廣播對象被構造出來后通常只執行BroadcastReceiver.onReceive方法,便結束了其生命周期。所以有的時候我們可以把它當做函數看也未必不可。
- 和所有組件一樣,廣播對象也是在應用進程的主線程中被構造,所以廣播對象的執行必須是要同步且快速的。也不推薦在里面開子線程,因為往往線程還未結束,廣播對象就已經執行完畢被系統銷毀。如果需要完成一項比較耗時的工作 , 應該通過發送 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系統所有的可聲明的權限。
現在運行程序,就可以了。
上方程序完整版代碼如下:

1 package com.example.m05_broadcastreceiver01; 2 import android.app.Activity; 3 import android.content.BroadcastReceiver; 4 import android.content.Context; 5 import android.content.Intent; 6 import android.content.IntentFilter; 7 import android.net.ConnectivityManager; 8 import android.net.NetworkInfo; 9 import android.os.Bundle; 10 import android.widget.Toast; 11 public class MainActivity extends Activity { 12 private IntentFilter intentFilter; 13 private netWorkChangeReceiver netWorkChangeReceiver; 14 @Override 15 protected void onCreate(Bundle savedInstanceState) { 16 super.onCreate(savedInstanceState); 17 setContentView(R.layout.activity_main); 18 // 動態注冊:創建一個IntentFilter的實例,添加網絡變化的廣播 19 intentFilter = new IntentFilter(); 20 intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE"); 21 // 創建NetWorkChangeReceiver的實例,並調用registerReceiver()方法進行注冊 22 netWorkChangeReceiver = new netWorkChangeReceiver(); 23 registerReceiver(netWorkChangeReceiver, intentFilter); 24 } 25 // 取消注冊,一定要記得,不然系統會報錯 26 @Override 27 protected void onDestroy() { 28 super.onDestroy(); 29 unregisterReceiver(netWorkChangeReceiver); 30 } 31 class netWorkChangeReceiver extends BroadcastReceiver { 32 @Override 33 public void onReceive(Context context, Intent intent) { 34 //通過getSystemService()方法得到connectionManager這個系統服務類,專門用於管理網絡連接 35 ConnectivityManager connectionManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); 36 NetworkInfo networkInfo = connectionManager.getActiveNetworkInfo(); 37 if(networkInfo != null && networkInfo.isAvailable()){ 38 Toast.makeText(context, "network is available",Toast.LENGTH_SHORT).show(); 39 }else{ 40 Toast.makeText(context, "network is unavailable",Toast.LENGTH_SHORT).show(); 41 } 42 43 } 44 } 45 }
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,通過監聽這些事件我們可以完成很多功能。
- 開機:
String BOOT_COMPLETED_ACTION 廣播:在系統啟動后。這個動作被廣播一次(只有一次)。監聽: “android.intent.action.BOOT_COMPLETED” - 電話撥入:
String ANSWER_ACTION 動作:處理撥入的電話。監聽: “android.intent.action.ANSWER” - 電量變化:
String BATTERY_CHANGED_ACTION 廣播:充電狀態,或者電池的電量發生變化。監聽: “android.intent.action.BATTERY_CHANGED” - 日期改變:
String DATE_CHANGED_ACTION 廣播:日期被改變。 監聽:“android.intent.action.DATE_CHANGED” - 取消更新下載:
String FOTA_CANCEL_ACTION 廣播:取消所有被掛起的 (pending) 更新下載。 監聽:“android.server.checkin.FOTA_CANCEL” - 更新開始安裝:
String FOTA_READY_ACTION 廣播:更新已經被下載 可以開始安裝。監聽 “android.server.checkin.FOTA_READY” - 主屏幕:
String HOME_CATEGORY 類別:主屏幕 (activity)。設備啟動后顯示的第一個 activity。 監聽:"android.intent.category.HOME” - 新應用:
String PACKAGE_ADDED_ACTION 廣播:設備上新安裝了一個應用程序包。監聽: “android.intent.action.PACKAGE_ADDED” - 刪除應用:
String PACKAGE_REMOVED_ACTION 廣播:設備上刪除了一個應用程序包。監聽: “android.intent.action.PACKAGE_REMOVED” - 屏幕關閉:
String SCREEN_OFF_ACTION 廣播:屏幕被關閉。監聽: “android.intent.action.SCREEN_OFF” - 屏幕開啟:
String SCREEN_ON_ACTION 廣播:屏幕已經被打開。 監聽:“android.intent.action.SCREEN_ON” - 時區改變:
String TIMEZONE_CHANGED_ACTION 廣播:時區已經改變。監聽: “android.intent.action.TIMEZONE_CHANGED” - 時間改變:
String TIME_CHANGED_ACTION 廣播:時間已經改變(重新設置)。 “android.intent.action.TIME_SET” - 時間流逝:
String TIME_TICK_ACTION 廣播:當前時間已經變化(正常的時間流逝)。 “android.intent.action.TIME_TICK” - 進入大容量存儲模式:
String UMS_CONNECTED_ACTION 廣播:設備進入 USB 大容量存儲模式。 “android.intent.action.UMS_CONNECTED” - 退出大容量存儲模式:
String UMS_DISCONNECTED_ACTION 廣播:設備從 USB 大容量存儲模式退出。 “android.intent.action.UMS_DISCONNECTED” - 壁紙改變:
String WALLPAPER_CHANGED_ACTION 廣播:系統的牆紙已經改變。 “android.intent.action.WALLPAPER_CHANGED” - web搜索:
String WEB_SEARCH_ACTION 動作:執行 web 搜索。 “android.intent.action.WEB_SEARCH” - 網絡變化:
String CONNECTIVITY_CHANGE_ACTION 動作:網絡變化。“android.intent.action.CONNECTIVITY_CHANGE_ACTION”
六、實例:使用動態注冊,監聽手機的電量變化。
完整版代碼如下:
(1)activity_main.xml代碼如下:
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>
(2)MainActivity.java的代碼如下:
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行:當電量低時,可以進行一些操作,例如彈出通知等
運行后,界面如下: