廣播機制概述
Android廣播分為兩個方面: 廣播發送者和廣播接收者 ,通常情況下,BroadcastReceiver指的就是廣播接收者。廣播作為Android 組件間 的通信方式,可以使用的場景如下:
- 1.同一app內部的同一組件內的消息通信(單個或多個線程之間);
- 2.同一app內部的不同組件之間的消息通信(單個進程);
- 3.同一app具有多個進程的不同組件之間的消息通信;
- 4.不同app之間的組件之間消息通信;
- 5.Android系統在特定情況下與App之間的消息通信。
從實現原理看上,Android中的廣播使用了觀察者模式,基於消息的發布/訂閱事件模型。因此,從實現的角度來看,Android中的廣播將廣播的發送者和接受者極大程度上解耦,使得系統能夠方便集成,更易擴展。具體實現流程要點粗略概括如下:
- 1.廣播接收者BroadcastReceiver通過Binder機制向AMS(Activity Manager Service)進行注冊;
- 2.廣播發送者通過binder機制向AMS發送廣播;
- 3.AMS查找符合相應條件(IntentFilter/Permission等)的BroadcastReceiver,將廣播發送到BroadcastReceiver(一般情況下是Activity)相應的消息隊列中;
- 4.消息循環執行拿到此廣播,回調BroadcastReceiver中的onReceive()方法。
對於不同的廣播類型,以及不同的BroadcastReceiver注冊方式,具體實現上會有不同。但總體流程大致如上。由此看來,廣播發送者和廣播接收者分別屬於觀察者模式中的消息發布和訂閱兩端,AMS屬於中間的處理中心。廣播發送者和廣播接收者的執行是異步的,發出去的廣播不會關心有無接收者接收,也不確定接收者到底是何時才能接收到。 顯然,整體流程與EventBus非常類似。在上文說列舉的廣播機制具體可以使用的場景中,現分析實際應用中的適用性:
- 第一種情形:實際應用中肯定是不會用到廣播機制的(雖然可以用),無論是使用擴展變量作用域、基於接口的回調還是Handler-post/Handler-Message等方式,都可以直接處理此類問題,若適用廣播機制,顯然有些“殺雞牛刀”的感覺,會顯太“重”;
- 第二種情形:對於此類需求,在有些較復雜的情況下單純的依靠基於接口的回調等方式不好處理,此時可以直接使用EventBus等,相對而言,EventBus由於是針對統一進程,用於處理此類需求非常適合,且輕松解耦。
- 第三四五種情形:由於涉及不同進程間的消息通信,此時根據實際業務使用廣播機制會顯得非常適宜。
APP退出后靜態Receiver能否接收到廣播的問題
我們可能一直有一個觀點:靜態注冊的廣播接收者即使app已經退出,只要有相應的廣播發出,此 廣播接收者 依然可以接收到廣播。但此種描述自Android 3.1開始對於系統廣播不再成立了!
Android 3.1開始,系統在Intent中增加了與廣播相關的flag參數,分別是FLAG_INCLUDE_STOPPED_PACKAGES和FLAG_EXCLUDE_STOPPED_PACKAGES。FLAG_INCLUDE_STOPPED_PACKAGES:包含已經停止的包(即包所在的進程已經退出)FLAG_EXCLUDE_STOPPED_PACKAGES:不包含已經停止的包
主要原因是:自Android3.1開始,系統本身增加了對所有app當前是否處於運行狀態的跟蹤。在發送廣播時,系統默認直接增加了值為FLAG_EXCLUDE_STOPPED_PACKAGES的flag,導致即使是靜態注冊的廣播接收者 ,對於其所在進程已經退出的app,同樣無法接收到廣播。因此,對於系統廣播,由於是系統內部直接發出的,無法更改此flag值,所以,3.1開始對於靜態注冊的接收系統廣播的BroadcastReceiver,如果App進程已經退出,將不能接收到系統廣播。但是對於自定義的廣播,由於我們可以手動設置此flag為FLAG_INCLUDE_STOPPED_PACKAGES,使得靜態注冊的BroadcastReceiver,即使所在App進程已經退出,也能能接收到廣播,並會啟動應用進程。
在3.1以前,相信不少app可能通過靜態注冊方式監聽各種系統廣播,以此進行一些業務上的處理。但3.1以后,靜態注冊接受廣播方式的改變,將直接導致此類方案不再可行。於是,通過將Service與App本身設置成不同的進程已經成為實現此類需求的可行的替代方案。
MainActivity
![]()
public class MainActivity extends ListActivity {private TelephonyManager tm;private MyBRReceiver myReceiver;public static final String MY_BROADCAST_ACTION_UNORDERED = "com.bqt.broadcast.songwennuan_unordered";public static final String MY_BROADCAST_ACTION_ORDERED = "com.bqt.broadcast.songwennuan_ordered";protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);String[] array = { "動態注冊廣播,監聽網絡狀態變化", "取消注冊廣播", "發送自定義的無序廣播", "發送自定義的有序廣播" };ListAdapter mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, new ArrayList<String>(Arrays.asList(array)));setListAdapter(mAdapter);tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);tm.listen(new MyPhoneStateListener(this), PhoneStateListener.LISTEN_CALL_STATE);//權限 READ_PHONE_STATE//動態注冊。需要程序啟動才可以接收廣播。一般在onCreate中注冊,在onDestroy中取消注冊。myReceiver = new MyBRReceiver();registerReceiver(myReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));//權限:ACCESS_NETWORK_STATE}@Overrideprotected void onListItemClick(ListView l, View v, int position, long id) {switch (position) {case 0://可以重復注冊,不會有什么異常registerReceiver(myReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));//權限:ACCESS_NETWORK_STATEToast.makeText(this, "已注冊", Toast.LENGTH_SHORT).show();break;case 1:try {unregisterReceiver(myReceiver);//不管注冊多少次,只能取消注冊一次,若多次取消或在沒有注冊時取消會報IllegalArgumentExceptionToast.makeText(this, "已取消注冊", Toast.LENGTH_SHORT).show();} catch (Exception e) {Toast.makeText(this, "異常啦!", Toast.LENGTH_SHORT).show();}break;case 2:sendUnOrderedBroadcast();break;case 3:sendOrderedBroadcast();break;}}@Overrideprotected void onDestroy() {super.onDestroy();unregisterReceiver(myReceiver);//如果已取消注冊或沒有注冊,會報錯:Unable to destroy activity… : IllegalArgumentException: Receiver not registered…}//********************************************************************************************************************************//發送無序廣播。不可被攔截,不可終止。public void sendUnOrderedBroadcast() {Intent intent = new Intent(MY_BROADCAST_ACTION_UNORDERED);//自定義廣播動作。廣播一般用於【應用程序之間】消息的傳遞,使用隱式意圖。intent.putExtra("msg", "======發1萬塊======");intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);//包含已經停止的包。設置此Flag后,即使APP已退出,能匹配此Action的廣播接收者依然可以接收到此廣播//但是對於系統廣播,由於Intent是系統內部直接發出的,無法更改此flag值,所以,3.1以后,如果App進程已經退出,廣播接收者將不能接收到系統廣播sendBroadcast(intent);}//發送有序廣播。可被攔截,可終止,可以修改數據。public void sendOrderedBroadcast() {Intent intent = new Intent(MY_BROADCAST_ACTION_ORDERED);sendOrderedBroadcast(intent, null, null, null, Activity.RESULT_OK, "-----------給農民兄弟發10000塊錢----------------", null);intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);//String receiverPermission 只有指定權限的接受者才能接收到廣播;BroadcastReceiver 一定會收到廣播的接收者,不可以被攔截,但接收到的數據可被改變sendOrderedBroadcast(intent, null);//發送一條空廣播}private class MyBRReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {//廣播中不允許開辟線程, onReceiver()運行超過10秒會ANR。廣播更多的時候扮演的是一個"啟動者"的角色,比如收到廣播后啟動Service,Notification,Activity等NetWorkEnum netEnum = NetUtils.getNetWorkState(MainActivity.this);Toast.makeText(context, "網絡狀態:" + netEnum, Toast.LENGTH_SHORT).show();}}}
來電、去電、短信的BroadCastReceiver
/**監聽來電、去電、短信到來*/public class PhoneAndSmsReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) { //This method is called when the BroadcastReceiver is receiving an Intent broadcast。String resultData = getResultData(); //Retrieve the current result data, as set by the previous receiver. Often this is null.if (intent != null) {if (Intent.ACTION_NEW_OUTGOING_CALL.equalsIgnoreCase(intent.getAction())) {//去電。權限:CALL_PHONEdoWhenTo(context, resultData);} else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equalsIgnoreCase(intent.getAction())) {//電話狀態改變,和state一樣,有三種狀態if (TelephonyManager.EXTRA_STATE_RINGING.equalsIgnoreCase(intent.getStringExtra(TelephonyManager.EXTRA_STATE))) {//來電狀態doWhenFrom(context, resultData);}} else if (android.provider.Telephony.Sms.Intents.SMS_RECEIVED_ACTION.equalsIgnoreCase(intent.getAction())) {//沃日,這個字段隱藏的好深啊doWhenGetSms(context, intent);}}}/**當去電時,發送一個通知。這里可以直接獲取到去電號碼*/private void doWhenTo(Context context, String resultData) {Intent callIntent = new Intent(Intent.ACTION_CALL);callIntent.setData(Uri.parse("tel:" + resultData));//接聽到的內容就是電話號碼,如186********PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, callIntent, 0);//注意,這里要用getActivity,因為PendingIntent的【目的】是打開撥號界面Notification notification = new Notification.Builder(context).setSmallIcon(R.drawable.ic_launcher).setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher))//.setTicker("TickerText:" + "監聽到去電!").setAutoCancel(true).setContentTitle("包青天提醒你").setContentText("去電號碼為" + resultData)//.setContentIntent(pendingIntent).setNumber(7).setWhen(System.currentTimeMillis()).build();//這個方法API 16 及之后才可使用((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).notify(110, notification);//使用同一個id以替換舊的通知}/**當來電時,彈一個土司。注意,這里並不能獲取到來電號碼*/private void doWhenFrom(Context context, String resultData) {Toast.makeText(context, "監聽到來電,resultData是否為null:" + (resultData == null), Toast.LENGTH_LONG).show();//這里獲取到的resultData為null}/**當收到短信時,彈一個土司 */private void doWhenGetSms(Context context, Intent intent) {Object[] objs = (Object[]) intent.getExtras().get("pdus");for (Object obj : objs) {SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) obj);String sender = smsMessage.getOriginatingAddress();String body = smsMessage.getMessageBody();Toast.makeText(context, "收到消息-" + sender + "--" + body, Toast.LENGTH_LONG).show();}abortBroadcast();//根本干不過系統短信:①沒啟動時收不到此廣播(這個很正常) ②啟動時能收到但不能終止廣播(估計優先級不夠)}}
電話狀態監聽PhoneStateListener
/**使用TelephonyManager監聽電話狀態。權限:READ_PHONE_STATE*/public class MyPhoneStateListener extends PhoneStateListener {private Context context;public MyPhoneStateListener(Context context) {super();this.context = context;}public void onCallStateChanged(int state, String incomingNumber) {super.onCallStateChanged(state, incomingNumber);switch (state) {case TelephonyManager.CALL_STATE_IDLE:break;case TelephonyManager.CALL_STATE_OFFHOOK:break;case TelephonyManager.CALL_STATE_RINGING:Toast.makeText(context, "監聽到來電,號碼為:" + incomingNumber, Toast.LENGTH_LONG).show();break;}}}
開機啟動廣播接收者
/** Android 4.3以上允許將應用安裝在SD卡上,系統開機間隔一小段時間后才裝載SD卡,為能獲取開機廣播,我們需要既監聽開機廣播又監聽SD卡掛載廣播 */public class BootCompleteReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {Toast.makeText(context, "開機啦,趕快干壞事~", Toast.LENGTH_LONG).show();//基本上都會被手機屏蔽掉Intent mIntent = new Intent(context, MainActivity.class);mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//在廣播中啟動Activity需要添加FLAG_ACTIVITY_NEW_TASK,因為需要一個棧來存放Activitycontext.startActivity(mIntent);}}
網絡工具類
public class NetUtils {/*** 檢查是否聯網*/public static boolean checkNetwork(Context context) {ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);NetworkInfo network = manager.getActiveNetworkInfo();if (network == null) {return false;}return network.isAvailable();//或使用 return network.isConnectedOrConnecting();}/*** 判斷當前網絡連接是否為wifi*/public static boolean isWifiConnection(Context context) {ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo();if (activeNetInfo != null && activeNetInfo.getType() == ConnectivityManager.TYPE_WIFI) {return true;}return false;}/*** 網絡類型*/public static enum NetWorkEnum {NETWORK_NONE, NETWORK_WIFI, NETWORK_2G, NETWORK_3G, NETWORK_4G}/*** 判斷當前使用的網絡狀態* 返回值: 0: 沒網絡 1:WIFI 2:2G 3: 3G 4: 4G*/public static NetWorkEnum getNetWorkState(Context context) {ConnectivityManager connectivityMag = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);if (connectivityMag != null) {// 獲取WIFI網絡連接狀態State wifiState = connectivityMag.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState();if (State.CONNECTED == wifiState || State.CONNECTING == wifiState) return NetWorkEnum.NETWORK_WIFI;// 檢查是否有網絡連接NetworkInfo netWorkInfo = connectivityMag.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);if (netWorkInfo == null) return NetWorkEnum.NETWORK_NONE;else {State tempState = netWorkInfo.getState();if (State.CONNECTED == tempState || State.CONNECTING == tempState) return getNetworkClass(netWorkInfo.getSubtype());else return NetWorkEnum.NETWORK_NONE;}}return NetWorkEnum.NETWORK_NONE;}/*** 判斷網絡類型*/private static NetWorkEnum getNetworkClass(int networkType) {switch (networkType) {case TelephonyManager.NETWORK_TYPE_GPRS:case TelephonyManager.NETWORK_TYPE_EDGE:case TelephonyManager.NETWORK_TYPE_CDMA:case TelephonyManager.NETWORK_TYPE_1xRTT:case TelephonyManager.NETWORK_TYPE_IDEN:return NetWorkEnum.NETWORK_2G;case TelephonyManager.NETWORK_TYPE_LTE:return NetWorkEnum.NETWORK_4G;case TelephonyManager.NETWORK_TYPE_UMTS:case TelephonyManager.NETWORK_TYPE_EVDO_0:case TelephonyManager.NETWORK_TYPE_EVDO_A:case TelephonyManager.NETWORK_TYPE_HSDPA:case TelephonyManager.NETWORK_TYPE_HSUPA:case TelephonyManager.NETWORK_TYPE_HSPA:case TelephonyManager.NETWORK_TYPE_EVDO_B:case TelephonyManager.NETWORK_TYPE_EHRPD:case TelephonyManager.NETWORK_TYPE_HSPAP:default:return NetWorkEnum.NETWORK_3G;}}}
清單文件
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.bqt.broadcastreceiver"android:versionCode="1"android:versionName="1.0" ><uses-sdkandroid:minSdkVersion="17"android:targetSdkVersion="21" /><!-- 獲取網絡信息狀態 --><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><!-- 訪問電話狀態,使用TelephonyManager時需要 --><uses-permission android:name="android.permission.READ_PHONE_STATE" /><uses-permission android:name="android.permission.CALL_PHONE" /><!-- 監視、修改、放棄播出電話 --><uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" /><uses-permission android:name="android.permission.SEND_SMS" /><uses-permission android:name="android.permission.RECEIVE_SMS" /><uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /><applicationandroid:allowBackup="true"android:icon="@drawable/ic_launcher"android:label="@string/app_name"android:theme="@style/AppTheme" ><activityandroid: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><!-- —————————————————————-系統廣播————————————————————— --><!-- android:permission:如果設置,具有此權限的【廣播發送者】發送的廣播才能被此【廣播接收者】所接收 --><!-- android:process:指定運行時所處的進程。默認為APP的進程,四大組件都可以通過此屬性指定自己的獨立進程 --><!-- android:exported:能否接收其他App(並非以進程為界)的發出的廣播。同activity、service一樣,如果有intent-filter默認值為true,否則為false --><receiver android:name=".PhoneAndSmsReceiver" ><intent-filter><!-- 靜態注冊的廣播接收器即使app已經退出,只要有相應的廣播發出,依然可以接收到,但此種描述自Android 3.1開始有可能不再成立 --><action android:name="android.intent.action.NEW_OUTGOING_CALL" /><action android:name="android.intent.action.PHONE_STATE" /><action android:name="android.provider.Telephony.SMS_RECEIVED" /></intent-filter></receiver><!-- Android 4.3以上允許將應用安裝在SD卡上,系統開機間隔一小段時間后才裝載SD卡,所以我們需要既監聽開機廣播又監聽SD卡掛載廣播! --><receiver android:name=".BootCompleteReceiver" ><intent-filter><action android:name="android.intent.action.BOOT_COMPLETED" /></intent-filter><intent-filter><action android:name="ANDROID.INTENT.ACTION.MEDIA_MOUNTED" /><action android:name="ANDROID.INTENT.ACTION.MEDIA_UNMOUNTED" /><data android:scheme="file" /></intent-filter></receiver></application></manifest>
附件列表