Android內置了很多系統級別的廣播,我們可以在應用程序中通過監聽這些廣播來得到各種系統的狀態信息。比如手機開機完成后會發出一條廣播,電池的電量發生變化會發出一條廣播,時間或時區發生改變也會發出一條廣播等等。如果想要接收到這些廣播,就需要使用廣播接收器,下面我們就來看一下它的具體用法。
1、動態注冊監聽網絡變化
廣播接收器可以自由地對自己感興趣的廣播進行注冊,這樣當有相應的廣播發出時,廣播接收器就能夠收到該廣播,並在內部處理相應的邏輯。注冊廣播的方式一般有兩種,在代碼中注冊和在AndroidManifest.xml中注冊,其中前者也被稱為動態注冊,后者也被稱為靜態注冊。
那么該如何創建一個廣播接收器呢?其實只需要新建一個類,讓它繼承自BroadcastReceiver,並重寫父類的onReceive()方法就行了。這樣當有廣播到來時,onReceive()方法就會得到執行,具體的邏輯就可以在這個方法中處理。
那我們就先通過動態注冊的方式編寫一個能夠監聽網絡變化的程序,借此學習一下廣播接收器的基本用法吧。新建一個BroadcastTest項目,然后修改MainActivity中的代碼,如下所示:
public class MainActivity extends Activity { private NetworkChangeReceiver networkChangeReceiver; private IntentFilter intentFilter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //實例一個網絡發生改變的接收器 this.networkChangeReceiver = new NetworkChangeReceiver(); //創建了一個IntentFilter的實例,並給它添加了一個值為android.net.conn.CONNECTIVITY_CHANGE的action //當網絡狀態發生變化時,系統發出的正是一條值為android.net.conn.CONNECTIVITY_ CHANGE的廣播,也就是說我們的廣播接收器想要監聽什么廣播,就在這里添加相應的action就行了 this.intentFilter = new IntentFilter(); this.intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE"); //調用registerReceiver()方法進行注冊,將NetworkChangeReceiver的實例和IntentFilter的實例都傳了進去,這樣NetworkChangeReceiver就會收到所有值為android.net.conn.CONNECTIVITY_CHANGE的廣播,也就實現了監聽網絡變化的功能 Log.d("BroadcastReceiverTest", "注冊"); registerReceiver(networkChangeReceiver, intentFilter); } @Override protected void onDestroy() { // TODO Auto-generated method stub //最后要記得,動態注冊的廣播接收器一定都要取消注冊才行,這里我們是在onDestroy()方法中通過調用unregisterReceiver()方法來實現 unregisterReceiver(networkChangeReceiver); super.onDestroy(); }
新建網絡改變的接收器:
public class NetworkChangeReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // TODO Auto-generated method stub Toast.makeText(context, "網絡發生變化", Toast.LENGTH_SHORT).show(); } }
可以看到,我們在MainActivity中定義了一個內部類NetworkChangeReceiver,這個類是繼承自BroadcastReceiver的,並重寫了父類的onReceive()方法。這樣每當網絡狀態發生變化時,onReceive()方法就會得到執行,這里只是簡單地使用Toast提示了一段文本信息。
然后觀察onCreate()方法,首先我們創建了一個IntentFilter的實例,並給它添加了一個值為android.net.conn.CONNECTIVITY_CHANGE的action,為什么要添加這個值呢?因為當網絡狀態發生變化時,系統發出的正是一條值為android.net.conn.CONNECTIVITY_ CHANGE的廣播,也就是說我們的廣播接收器想要監聽什么廣播,就在這里添加相應的action就行了。接下來創建了一個NetworkChangeReceiver的實例,然后調用registerReceiver()方法進行注冊,將NetworkChangeReceiver的實例和IntentFilter的實例都傳了進去,這樣NetworkChangeReceiver就會收到所有值為android.net.conn.CONNECTIVITY_CHANGE的廣播,也就實現了監聽網絡變化的功能。
最后要記得,動態注冊的廣播接收器一定都要取消注冊才行,這里我們是在onDestroy()方法中通過調用unregisterReceiver()方法來實現的。
整體來說,代碼還是非常簡單的,現在運行一下程序。首先你會在注冊完成的時候收到一條廣播,然后按下Home鍵回到主界面(注意不能按Back鍵,否則onDestroy()方法會執行),接着按下Menu鍵→System settings→Data usage進入到數據使用詳情界面,然后嘗試着開關Mobile Data來啟動和禁用網絡,你就會看到有Toast提醒你網絡發生了變化。
不過只是提醒網絡發生了變化還不夠人性化,最好是能准確地告訴用戶當前是有網絡還是沒有網絡,因此我們還需要對上面的代碼進行進一步的優化。修改MainActivity中的代碼,如下所示:
public class MainActivity extends Activity { private AppNetworkChangeReceiver networkChangeReceiver; private IntentFilter intentFilter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //實例一個網絡發生改變的接收器 this.networkChangeReceiver = new AppNetworkChangeReceiver(); //創建了一個IntentFilter的實例,並給它添加了一個值為android.net.conn.CONNECTIVITY_CHANGE的action //當網絡狀態發生變化時,系統發出的正是一條值為android.net.conn.CONNECTIVITY_ CHANGE的廣播,也就是說我們的廣播接收器想要監聽什么廣播,就在這里添加相應的action就行了 this.intentFilter = new IntentFilter(); this.intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE"); //調用registerReceiver()方法進行注冊,將NetworkChangeReceiver的實例和IntentFilter的實例都傳了進去,這樣NetworkChangeReceiver就會收到所有值為android.net.conn.CONNECTIVITY_CHANGE的廣播,也就實現了監聽網絡變化的功能 Log.d("BroadcastReceiverTest", "注冊"); registerReceiver(networkChangeReceiver, intentFilter); } @Override protected void onDestroy() { //最后要記得,動態注冊的廣播接收器一定都要取消注冊才行,這里我們是在onDestroy()方法中通過調用unregisterReceiver()方法來實現 unregisterReceiver(networkChangeReceiver); super.onDestroy(); } class AppNetworkChangeReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { //首先通過getSystemService()方法得到了ConnectivityManager的實例,這是一個系統服務類,專門用於管理網絡連接的 ConnectivityManager connectivityManager=(ConnectivityManager)getSystemService(context.CONNECTIVITY_SERVICE); //然后調用它的getActiveNetworkInfo()方法可以得到NetworkInfo的實例,接着調用NetworkInfo的isAvailable()方法,就可以判斷出當前是否有網絡了,最后我們還是通過Toast的方式對用戶進行提示。 NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); 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(); } } }
在onReceive()方法中,首先通過getSystemService()方法得到了ConnectivityManager的實例,這是一個系統服務類,專門用於管理網絡連接的。然后調用它的getActiveNetworkInfo()方法可以得到NetworkInfo的實例,接着調用NetworkInfo的isAvailable()方法,就可以判斷出當前是否有網絡了,最后我們還是通過Toast的方式對用戶進行提示。
另外,這里有非常重要的一點需要說明,Android系統為了保證應用程序的安全性做了規定,如果程序需要訪問一些系統的關鍵性信息,必須在配置文件中聲明權限才可以,否則程序將會直接崩潰,比如這里查詢系統的網絡狀態就是需要聲明權限的。打開AndroidManifest.xml文件,在里面加入如下權限就可以查詢系統網絡狀態了:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.broadcasttest1" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="19" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
訪問http://developer.android.com/reference/android/Manifest.permission.html可以查看Android系統所有可聲明的權限。
現在重新運行程序,然后按下Home鍵→按下Menu鍵→System settings→Data usage進入到數據使用詳情界面,關閉Mobile Data會彈出無網絡可用的提示,如下圖所示。
2、靜態注冊實現開機啟動
動態注冊的廣播接收器可以自由地控制注冊與注銷,在靈活性方面有很大的優勢,但是它也存在着一個缺點,即必須要在程序啟動之后才能接收到廣播,因為注冊的邏輯是寫在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文件,代碼如下所示:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.broadcasttest1" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="19" /> <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" > <receiver android:name=".BootCompleteReceiver"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter> </receiver> <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
終於,<application>標簽內出現了一個新的標簽<receiver>,所有靜態注冊的廣播接收器都是在這里進行注冊的。它的用法其實和<activity>標簽非常相似,首先通過android:name來指定具體注冊哪一個廣播接收器,然后在<intent-filter>標簽里加入想要接收的廣播就行了,由於Android系統啟動完成后會發出一條值為android.intent.action.BOOT_COMPLETED的廣播,因此我們在這里添加了相應的action。
另外,監聽系統開機廣播也是需要聲明權限的,可以看到,我們使用<uses-permission>標簽又加入了一條android.permission.RECEIVE_BOOT_COMPLETED權限。
現在重新運行程序后,我們的程序就已經可以接收開機廣播了,首先打開到應用程序管理界面來查看一下當前程序所擁有的權限。在桌面按下Menu鍵→System settings→Apps,然后點擊BroadcastTest。
到目前為止,我們在廣播接收器的onReceive()方法中都只是簡單地使用Toast提示了一段文本信息,當你真正在項目中使用到它的時候,就可以在里面編寫自己的邏輯。需要注意的是,不要在onReceive()方法中添加過多的邏輯或者進行任何的耗時操作,因為在廣播接收器中是不允許開啟線程的,當onReceive()方法運行了較長時間而沒有結束時,程序就會報錯。因此廣播接收器更多的是扮演一種打開程序其他組件的角色,比如創建一條狀態欄通知,或者啟動一個服務等。