Android四大組件重要性已經不言而喻了,今天談談的是Android中的廣播機制。在咱們上學的時候,每一個班級的教室裏都會裝有一個喇叭,這些喇叭都是接入到學校的廣播室的,一旦有什麼重要的通知,就會播放一條廣播來告知全校的師生。相似的工做機制其實在計算機領域也有很普遍的應用,若是你瞭解網絡通訊原理應該會知道,在一個 IP 網絡範圍中最大的 IP 地址是被保留做爲廣播地址來使用的。好比某個網絡的 IP 範圍是 192.168.0.XXX,子網掩碼是 255.255.255.0,那麼這個網絡的廣播地址就是 192.168.0.255。 廣播數據包會被髮送到同一網絡上的全部端口,這樣在該網絡中的每臺主機都將會收到這條廣播。爲了方便於進行系統級別的消息通知,Android 也引入了一套相似的廣播消息機制。html
爲何說 Android中的廣播機制更加靈活呢?這是由於 Android中的每一個應用程序均可以對本身感興趣的廣播進行註冊,這樣該程序就只會接收到本身所關心的廣播內容,這些 播多是來自於系統的,也多是來自於其餘應用程序的。Android提供了一套完整的 API, 容許應用程序自由地發送和接收廣播。接收廣播的方法則須要引入一個新的概念,廣播接收器(Broadcast Receiver),它就是用來接收來自系統和應用中的廣播。java
在Android廣播中,主要分爲兩種類型:標準廣播和有序廣播。android
標準廣播(Normal broadcasts)是一種徹底異步執行的廣播,在廣播發出以後,全部的 廣播接收器幾乎都會在同一時刻接收到這條廣播消息,所以它們之間沒有任何前後順序可 言。這種廣播的效率會比較高,但同時也意味着它是沒法被截斷的。標準廣播的工做流程如圖所示。安全
有序廣播(Ordered broadcasts)則是一種同步執行的廣播,在廣播發出以後,同一時刻只會有一個廣播接收器可以收到這條廣播消息,當這個廣播接收器中的邏輯執行完畢後,廣播纔會繼續傳遞。因此此時的廣播接收器是有前後順序的,優先級高的廣播接收器就能夠先收到廣播消息,而且前面的廣播接收器還能夠截斷正在傳遞的廣播,這樣後面的廣播接收器就沒法收到廣播消息了。性能優化
BroadcastReceiver主要包括兩方面的內容,一個是廣播的註冊過程,另外一個是廣播的發送和接收過程。那麼該如何建立一個廣播接收器呢?其實只須要新建一個類,讓它繼承自BroadcastReceiver, 並重寫父類的 onReceive()方法就好了。這樣當有廣播到來時,onReceive()方法就會獲得執行, 具體的邏輯就能夠在這個方法中處理。 廣播的使用方法有兩個:靜態方法和動態方法。網絡
動態方法app
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.addAction("android.net.conn.CONNECTIVITY_CHANGE"); networkChangeReceiver = new NetworkChangeReceiver(); registerReceiver(networkChangeReceiver, intentFilter); } @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(networkChangeReceiver); } private class NetworkChangeReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { 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()方法來實現的。ide
靜態方法
動態註冊的廣播接收器能夠自由地控制註冊與註銷,在靈活性方面有很大的優點,可是 它也存在着一個缺點,即必需要在程序啓動以後才能接收到廣播,由於註冊的邏輯是寫在 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" > <receiver android:name=".BootCompleteReceiver" > <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver> </application> </manifest>
另外,監聽系統開機廣播也是須要聲明權限的,能夠看到,咱們使用
如今你已經學會了經過廣播接收器來接收系統廣播,接下來咱們就要學習一下如何在應用程序中發送自定義的廣播。前面已經介紹過了,廣播主要分爲兩種類型,標準廣播和有序 廣播。
在API文檔中關於BroadcastReceiver的概述:
那麼廣播事件的流程如何呢,以下:
註冊廣播事件:註冊方式有兩種,一種是靜態註冊,就是在AndroidManifest.xml文件中定義,註冊的廣播接收器必需要繼承BroadcastReceiver;另外一種是動態註冊,是在程序中使用Context.registerReceiver註冊,註冊的廣播接收器至關於一個匿名類。兩種方式都須要IntentFIlter。
發送廣播事件:經過Context.sendBroadcast來發送,由Intent來傳遞註冊時用到的Action。
接收廣播事件:當發送的廣播被接收器監聽到後,會調用它的onReceive()方法,並將包含消息的Intent對象傳給它。onReceive中代碼的執行時間不要超過10s,不然Android會彈出超時dialog。
具體作法:
在發送廣播以前,咱們仍是須要先定義一個廣播接收器來準備接收此廣播才行,否則發 出去也是白髮。所以新建一個 MyBroadcastReceiver繼承自 BroadcastReceiver
public class MyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context, "接收到廣播消息", Toast.LENGTH_SHORT).show(); } }
這裏當 MyBroadcastReceiver收到自定義的廣播時,就會彈出提示語。而後在 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" > <receiver android:name=".MyBroadcastReceiver" > <intent-filter> <action android:name="com.example.broadcasttest.MY_BROADCAST" /> </intent-filter> </receiver> </application> </manifest>
能夠看到,這裏讓 MyBroadcastReceiver 接收一條值爲 com.example.broadcasttest. MY_BROADCAST的廣播,所以待會兒在發送廣播的時候,咱們就須要發出這樣的一條廣播。
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button button = (Button) findViewById(R.id.button); button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST"); sendBroadcast(intent); } }); } }
能夠看到,咱們在按鈕的點擊事件裏面加入了發送自定義廣播的邏輯。首先構建出了一 個 Intent對象,並把要發送的廣播的值傳入,而後調用了 Context的 sendBroadcast()方法將廣 播發送出去,這樣全部監聽 com.example.broadcasttest.MY_BROADCAST 這條廣播的廣播接 收器就會收到消息。此時發出去的廣播就是一條標準廣播。
前面咱們發送和接收的廣播所有都是屬於系統全局廣播,即發出的廣播能夠被其餘任何的任何應用程序接收到,而且咱們也能夠接收來自於其餘任何應用程序的廣播。這樣就很容易會引發安全性的問題,好比說咱們發送的一些攜帶關鍵性數據的廣播有可能被其餘的應用 程序截獲,或者其餘的程序不停地向咱們的廣播接收器裏發送各類垃圾廣播。
爲了可以簡單地解決廣播的安全性問題,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); } private class LocalReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show(); } } }
有沒有感受這些代碼很熟悉?沒錯,其實這基本上就和咱們前面所學的動態註冊廣播接 收器以及發送廣播的代碼是同樣。只不過如今首先是經過 LocalBroadcastManager的 getInstance() 方法獲得了它的一個實例,而後在註冊廣播接收器的時候調用的是 LocalBroadcastManager 的 registerReceiver()方法,在發送廣播的時候調用的是 LocalBroadcastManager的 sendBroadcast() 方法,僅此而已。
另外還有一點須要說明,本地廣播是沒法經過靜態註冊的方式來接收的。其實這也徹底 能夠理解,由於靜態註冊主要就是爲了讓程序在未啓動的狀況下也能收到廣播,而發送本地 廣播時,咱們的程序確定是已經啓動了,所以也徹底不須要使用靜態註冊的功能。
總結下使用本地廣播的幾點優點吧。
咱們如今知道了廣播的註冊有靜態註冊和動態註冊,其中靜態註冊的廣播在應用安裝時由系統自動完成註冊的。具體來講是由PMS(PackageManagerService)來完成整個註冊過程的,除了廣播覺得,其餘三大組件也都是應用安裝時由PMS解析並註冊的,這裏分析下廣播的動態註冊過程,動態註冊過程是從ContextWrapper的registerReceiver方法開始的,和Activity以及Service同樣。ContextWrapper並無作實際的工做,基本將註冊過程直接交給ContextImpl來完成。
@Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { return registerReceiver(receiver, filter, null, null); }
ContextImpl的registerReceiver方法調用了本身的registerReceiverInternal
方法,具體實現以下:
private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId, IntentFilter filter, String broadcastPermission,Handler scheduler, Context context) { IIntentReceiver rd = null; if (receiver != null) { if (mPackageInfo != null && context != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } rd = mPackageInfo.getReceiverDispatcher( receiver, context, scheduler, mMainThread.getInstrumentation(), true); } else { if (scheduler == null) { scheduler = mMainThread.getHandler(); } rd = new LoadedApk.ReceiverDispatcher( receiver, context, scheduler, null, true).getIIntentReceiver(); } } try { return ActivityManagerNative.getDefault().registerReceiver( mMainThread.getApplicationThread(), mBasePackageName, rd, filter, broadcastPermission, userId); } catch (RemoteException e) { return null; } }
系統首先從mPackageInfo獲取IIntentReceiver對象,而後再採用跨進程的方式向AMS發送廣播註冊的請求。之因此用IIntentReceiver而不是直接採用BroadcastReceiver,這是由於上述註冊過程是一個進程間通訊的過程,而BroadcastReceiver做爲一個Android組件是不能直接跨進程傳遞的,因此須要經過IIntentReceiver來中轉一下,毫無疑問,IIntentReceiver必須是一個Binder接口,它的具體實現是LoadedApk.ReceiverDispatcher,ReceiverDispatcher的內部同時保存了BroadcastReceiver和InnerReceiver,這樣當接收到廣播時ReceiverDispatcher能夠很方便地調用BroadcastReceiver的onReceiver方法。
這裏的ActivityManagerNative.getDefault()實際上就是一個AMS。具體代碼以下:
public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,Context context, Handler handler,Instrumentation instrumentation, boolean registered) { synchronized (mReceivers) { LoadedApk.ReceiverDispatcher rd = null; ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = null; if (registered) { map = mReceivers.get(context); if (map != null) { rd = map.get(r); } } if (rd == null) { rd = new ReceiverDispatcher(r, context, handler,instrumentation, registered); if (registered) { if (map == null) { map = new ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>(); mReceivers.put(context, map); } map.put(r, rd); } } else { rd.validate(context, handler); } rd.mForgotten = false; return rd.getIIntentReceiver(); }
因爲註冊的廣播真正的實現過程是在AMS中,最終會把遠程的InnerReceiver對象以及IntentFilter對象存儲起來,這樣整個廣播的註冊過程就完成了。
當經過send方法來發送廣播時,AMS會查找出匹配的廣播接收者並將廣播發送給他們處理。廣播的發送有幾種類型:普通廣播,有序廣播和粘性廣播。這裏分析下普通廣播的實現。
廣播的發送和接收。其本質是一個過程的兩個階段。廣播的發送仍然開始於ContextWrapper的sendBroadcast方法,之因此不是Context,那是由於Context的sendBroadcast是一個抽象方法。和廣播的註冊過程同樣ContextWrapper的sendBroadcast方法仍然什麼都不作,只是把事情交給ContextImpl去處理,ContextImpl的sendBroadcast方法源碼以下:
public void sendBroadcast(Intent intent) { warnIfCallingFromSystemProcess(); String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, false, false, getUserId()); } catch (RemoteException e) { } }
它直接向AMS發起了一個異步請求用於發送廣播。那麼AMS的broadcastIntent方法的源碼以下:
public final int broadcastIntent(IApplicationThread caller, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, String requiredPermission, boolean serialized, boolean sticky, int userId) { synchronized(this) { intent = verifyBroadcastLocked(intent); final ProcessRecord callerApp = getRecordForAppLocked(caller); final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); int res = broadcastIntentLocked(callerApp, callerApp != null ? callerApp.info.packageName : null, intent, resolvedType, resultTo, resultCode, resultData, map, requiredPermission, serialized, sticky, callingPid, callingUid, userId); Binder.restoreCallingIdentity(origId); return res; } }
從代碼上看,broadcastIntent調用了broadcastIntentLocked方法,但在AMS的broadcastIntentLocked方法裏有這麼一句:
// By default broadcasts do not go to stopped apps. intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
這表示在Android5.0下,默認狀況下廣播不會發送給已經中止的應用。FLAG_EXCLUDE_STOPPED_PACKAGES的含義是表示 不包含已經中止的應用,這個時候廣播不會發送給已經中止的應用。
在broadcastIntentLocked的內部,會根據intent-filter查找出匹配的廣播接收者並通過一系列的條件過濾,最終會將知足條件的廣播接收者添加到BroadcastQueue中,接着BroadcastQueue將會廣播發送給相應的廣播接收者。
if ((receivers != null && receivers.size() > 0) || resultTo != null) { BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callingPid, callingUid, requiredPermission, receivers, resultTo, resultCode, resultData, map, ordered, sticky, false); if (DEBUG_BROADCAST) Slog.v( TAG, "Enqueueing ordered broadcast " + r + ": prev had " + queue.mOrderedBroadcasts.size()); if (DEBUG_BROADCAST) { int seq = r.intent.getIntExtra("seq", -1); Slog.i(TAG, "Enqueueing broadcast " + r.intent.getAction() + " seq=" + seq); } boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r); if (!replaced) { queue.enqueueOrderedBroadcastLocked(r); queue.scheduleBroadcastsLocked(); } }
下面看下BroadcastQueue中廣播的發送過程的實現。以下所示:
public void scheduleBroadcastsLocked() { if (DEBUG_BROADCAST) Slog.v(TAG, "Schedule broadcasts [" + mQueueName + "]: current=" + mBroadcastsScheduled); if (mBroadcastsScheduled) { return; } mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this)); mBroadcastsScheduled = true; }
BroadcastQueue的scheduleBroadcastsLocked方法並無當即發送廣播,而是發送了一個BROADCAST_INTENT_MSG類型的消息,BroadcastQueue收到消息後會調用processNextBroadcast方法,BroadcastQueue的processNextBroadcast方法對普通廣播的處理方式以下:
// First, deliver any non-serialized broadcasts right away. while (mParallelBroadcasts.size() > 0) { r = mParallelBroadcasts.remove(0); r.dispatchTime = SystemClock.uptimeMillis(); r.dispatchClockTime = System.currentTimeMillis(); final int N = r.receivers.size(); if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing parallel broadcast [" + mQueueName + "] " + r); for (int i=0; i<N; i++) { Object target = r.receivers.get(i); if (DEBUG_BROADCAST) Slog.v(TAG, "Delivering non-ordered on [" + mQueueName + "] to registered " + target + ": " + r); deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false); } addBroadcastToHistoryLocked(r); if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Done with parallel broadcast [" + mQueueName + "] " + r); }
能夠看到,無序廣播存儲在mParallelBroadcasts中,系統會遍歷mParallelBroadcasts並將其中的廣播發送給它們全部接收者,具體的發送過程是經過deliverToRegisteredReceiverLocked方法來實現的。
最終呢,會調用ApplicationThread的scheduleRegisteredReceiver的實現比較簡單,它經過InnerReceiver來實現廣播的接收。而後InnerReceiver的performReceive方法會調用LoadedApk.ReceiverDispatcher的PerformReceive方法。最終會回調到receiver.onReceive()這個方法。
很顯然,這個時候BroadcastReceiver的onReceive方法被執行了,也就是說應用收到廣播了,同時,onReceive方法是在廣播接收者的主線程中被調用,因此不能作耗時操做,由於是在ApplicationThread的主線程上執行的。
總結一下,Android中應用程序發送廣播的過程:
做爲Android中四大組件之一的廣播,能夠應用不少場景的,好比用戶異地登錄強制下線,應用開機啓動服務,網絡狀態變化通知等等,掌握好其中的定義,使用方法,背後的註冊流程,發送和接收消息流程機制,對於咱們在開發時是頗有幫助的。
參考信息:
1,http://blog.csdn.net/zuolongsnail/article/details/6450156
2,《第一行代碼》
源於對掌握的Android開發基礎點進行整理,羅列下已經總結的文章,從中能夠看到技術積累的過程。
1,Android系統簡介
2,ProGuard代碼混淆
3,講講Handler+Looper+MessageQueue關係
4,Android圖片加載庫理解
5,談談Android運行時權限理解
6,EventBus初理解
7,Android 常見工具類
8,對於Fragment的一些理解
9,Android 四大組件之 " Activity "
10,Android 四大組件之" Service "
11,Android 四大組件之「 BroadcastReceiver "
12,Android 四大組件之" ContentProvider "
13,講講 Android 事件攔截機制
14,Android 動畫的理解
15,Android 生命週期和啓動模式
16,Android IPC 機制
17,View 的事件體系
18,View 的工做原理
19,理解 Window 和 WindowManager
20,Activity 啓動過程分析
21,Service 啓動過程分析
22,Android 性能優化
23,Android 消息機制
24,Android Bitmap相關
25,Android 線程和線程池
26,Android 中的 Drawable 和動畫
27,RecylerView 中的裝飾者模式
28,Android 觸摸事件機制
29,Android 事件機制應用
30,Cordova 框架的一些理解
31,有關 Android 插件化思考
32,開發人員必備技能——單元測試