Android 四大元件之“ BroadcastReceiver ”

cryAllen發表於2016-08-19

前言

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

相關文章