Android 四大組件之“ BroadcastReceiver ”


前言

Android四大組件重要性已經不言而喻了,今天談談的是Android中的廣播機制。在我們上學的時候,每個班級的教室里都會裝有一個喇叭,這些喇叭都是接入到學校的廣播室的,一旦有什么重要的通知,就會播放一條廣播來告知全校的師生。類似的工作機制其實在計算機領域也有很廣泛的應用,如果你了解網絡通信原理應該會知道,在一個 IP 網絡范圍中最大的 IP 地址是被保留作為廣播地址來使用的。比如某個網絡的 IP 范圍是 192.168.0.XXX,子網掩碼是 255.255.255.0,那么這個網絡的廣播地址就是 192.168.0.255。 廣播數據包會被發送到同一網絡上的所有端口,這樣在該網絡中的每台主機都將會收到這條廣播。為了方便於進行系統級別的消息通知,Android 也引入了一套類似的廣播消息機制。

目錄

  • 廣播機制介紹
  • BroadcastReceiver用法
  • 發送自定義廣播
  • 本地廣播介紹
  • 廣播的注冊過程
  • 廣播的發送和接受過程
  • 總結

廣播機制介紹

為什么說 Android中的廣播機制更加靈活呢?這是因為 Android中的每個應用程序都可以對自己感興趣的廣播進行注冊,這樣該程序就只會接收到自己所關心的廣播內容,這些 播可能是來自於系統的,也可能是來自於其他應用程序的。Android提供了一套完整的 API, 允許應用程序自由地發送和接收廣播。接收廣播的方法則需要引入一個新的概念,廣播接收器(Broadcast Receiver),它就是用來接收來自系統和應用中的廣播。

在Android廣播中,主要分為兩種類型:標准廣播和有序廣播。

標准廣播(Normal broadcasts)是一種完全異步執行的廣播,在廣播發出之后,所有的 廣播接收器幾乎都會在同一時刻接收到這條廣播消息,因此它們之間沒有任何先后順序可 言。這種廣播的效率會比較高,但同時也意味着它是無法被截斷的。標准廣播的工作流程如圖所示。

標准廣播

有序廣播(Ordered broadcasts)則是一種同步執行的廣播,在廣播發出之后,同一時刻只會有一個廣播接收器能夠收到這條廣播消息,當這個廣播接收器中的邏輯執行完畢后,廣播才會繼續傳遞。所以此時的廣播接收器是有先后順序的,優先級高的廣播接收器就可以先收到廣播消息,並且前面的廣播接收器還可以截斷正在傳遞的廣播,這樣后面的廣播接收器就無法收到廣播消息了。

有序廣播

BroadcastReceiver用法

BroadcastReceiver主要包括兩方面的內容,一個是廣播的注冊過程,另一個是廣播的發送和接收過程。那么該如何創建一個廣播接收器呢?其實只需要新建一個類,讓它繼承自BroadcastReceiver, 並重寫父類的 onReceive()方法就行了。這樣當有廣播到來時,onReceive()方法就會得到執行, 具體的邏輯就可以在這個方法中處理。 廣播的使用方法有兩個:靜態方法和動態方法。

動態方法

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()方法來實現的。

靜態方法

動態注冊的廣播接收器可以自由地控制注冊與注銷,在靈活性方面有很大的優勢,但是 它也存在着一個缺點,即必須要在程序啟動之后才能接收到廣播,因為注冊的邏輯是寫在 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> 

標簽內出現了一個新的標簽 ,所有靜態注冊的廣播接收器 都是在這里進行注冊的。它的用法其實和 標簽非常相似,首先通過 android:name 來指定具體注冊哪一個廣播接收器,然后在 標簽里加入想要接收的廣播就行了, 由於Android系統啟動完成后會發出一條值為android.intent.action.BOOT_COMPLETED的廣 播,因此我們在這里添加了相應的 action。

另外,監聽系統開機廣播也是需要聲明權限的,可以看到,我們使用 標簽又加入了一條 android.permission.RECEIVE_BOOT_COMPLETED權限

發送自定義廣播

現在你已經學會了通過廣播接收器來接收系統廣播,接下來我們就要學習一下如何在應用程序中發送自定義的廣播。前面已經介紹過了,廣播主要分為兩種類型,標准廣播和有序 廣播。

在API文檔中關於BroadcastReceiver的概述:

  • 廣播接收器是一個專注於接收廣播通知信息,並做出對應處理的組件。很多廣播是源自於系統代碼的──比如,通知時區改變、電池電量低、拍攝了一張照片或者用戶改變了語言選項。應用程序也可以進行廣播──比如說,通知其它應用程序一些數據下載完成並處於可用狀態。
  • 應用程序可以擁有任意數量的廣播接收器以對所有它感興趣的通知信息予以響應。所有的接收器均繼承自BroadcastReceiver基類。
  • 廣播接收器沒有用戶界面。然而,它們可以啟動一個activity來響應它們收到的信息,或者用NotificationManager來通知用戶。通知可以用很多種方式來吸引用戶的注意力──閃動背燈、震動、播放聲音等等。一般來說是在狀態欄上放一個持久的圖標,用戶可以打開它並獲取消息。

那么廣播事件的流程如何呢,如下:

  • 注冊廣播事件:注冊方式有兩種,一種是靜態注冊,就是在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() 方法,僅此而已。

另外還有一點需要說明,本地廣播是無法通過靜態注冊的方式來接收的。其實這也完全 可以理解,因為靜態注冊主要就是為了讓程序在未啟動的情況下也能收到廣播,而發送本地 廣播時,我們的程序肯定是已經啟動了,因此也完全不需要使用靜態注冊的功能。

總結下使用本地廣播的幾點優勢吧。

  1. 可以明確地知道正在發送的廣播不會離開我們的程序,因此不需要擔心機密數據泄 漏的問題。
  2. 其他的程序無法將廣播發送到我們程序的內部,因此不需要擔心會有安全漏洞的隱 患。
  3. 發送本地廣播比起發送系統全局廣播將會更加高效。

廣播的注冊過程

我們現在知道了廣播的注冊有靜態注冊和動態注冊,其中靜態注冊的廣播在應用安裝時由系統自動完成注冊的。具體來說是由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中應用程序發送廣播的過程:

  • 通過sendBroadcast把一個廣播通過Binder發送給ActivityManagerService,ActivityManagerService根據這個廣播的Action類型找到相應的廣播接收器,然后把這個廣播放進自己的消息隊列中,就完成第一階段對這個廣播的異步分發。
  • ActivityManagerService在消息循環中處理這個廣播,並通過Binder機制把這個廣播分發給注冊的ReceiverDispatcher,ReceiverDispatcher把這個廣播放進MainActivity所在線程的消息隊列中,就完成第二階段對這個廣播的異步分發。
  • ReceiverDispatcher的內部類Args在MainActivity所在的線程消息循環中處理這個廣播,最終是將這個廣播分發給所注冊的BroadcastReceiver實例的onReceive函數進行處理:

作為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,開發人員必備技能——單元測試


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM