廣播 goAsync 原始碼分析,為什麼 Google 大佬都在使用它

語言: CN / TW / HK

hi 大家好,我是 DHL。公眾號:ByteCode ,專注有用、有趣的硬核原創內容,Kotlin、Jetpack、效能優化、系統原始碼、演算法及資料結構、大廠面經。

近期在分析問題過程中,需要反編譯 Google 的一些庫,在看原始碼的時候,發現使用廣播的場景都會手動呼叫 goAsync() 方法。

goAsync() 是一個冷門但是非常有用的知識點,很少有文章會去分析 goAsync() 方法,因此這個方法在實際專案中使用的人也非常的少,我之前對這個方法也只是有一點了解,帶著我的好奇心,研究了一下。

通過這篇文章你將學習到以下內容:

  • goAsync() 是什麼,它的作用是什麼
  • BroadcastReceiver 如何處理靜態接受者和動態接受者
  • 為什麼 goAsync 方法,可以保證廣播處於活躍狀態
  • 在什麼場景下使用 goAsync()
  • 對程序的影響

goAsync 是什麼

根據 BroadcastReceiver 原始碼中的介紹,goAsync() 方法返回 PendingResult,可以在 BroadcastReceiver.onReceive() 中使用,如果呼叫了這個方法,當 onReceive() 方法執行完返回時,並不會終止當前的廣播,廣播依然處於活躍狀態,直到呼叫 PendingResult.finish() 方法,才會結束掉當前的廣播。

goAsync() 方法並不會影響廣播超時的策略,從呼叫 goAsync() 方法開始,一直到呼叫 finish() 方法結束,如果超過了原始碼中設定的廣播超時時間(10s/60s),依然會產生 ANR。

為什麼 goAsync() 方法,可以保證廣播處於活躍狀態,我們需要先了解一下 BroadcastReceiver 排程流程,以 android-11.0.0_r3 原始碼為例。

BroadcastReceiver 的排程流程

AMS 和應用程序之間的通訊是通過 ApplicationThread 進行的,而廣播處理的方式分為靜態處理和動態處理,在 ApplicationThread 中分別對這兩種方式做了處理。

動態處理

動態處理流程,如下所示:

首先會呼叫 ActivityThread#ApplicationThread 類中 scheduleRegisteredReceiver 方法,最終會呼叫 LoadedApk#ReceiverDispatcher 類中的 performReceive 方法。
frameworks/base/core/java/android/app/LoadedApk #ReceiverDispatcher . java

```kotlin public void performReceive(Intent intent, ...) { final Args args = new Args(intent, resultCode, ...);

if (intent == null || !mActivityThread.post(args.getRunnable())) {
    ....
}

} ```

通過 mActivityThread. post () 傳送一個 Runnable, 我看一下 Args 中的 Runnable 實現。
frameworks/base/core/java/android/app/LoadedApk #ReceiverDispatcher #Args . java

```kotlin public void Runnable getRunnable() { return () -> { // 這個是我們註冊的 BroadcastReceiver final BroadcastReceiver receiver = mReceiver; try { ...... // 為註冊的廣播接受者設定 PendingResult receiver.setPendingResult(this);

        // 執行 BroadcastReceiver#onReceive 方法
        receiver.onReceive(mContext, intent);
    } catch (Exception e) {
    }

    // 判斷 PendingResult 是否為空,如果為空,就不會結束掉當前註冊的 Receiver
    // 應用層可以呼叫 BroadcastReceiver.goAsync,將 PendingResult 設定為null,從而打斷廣播後續處理流程
    if (receiver.getPendingResult() != null) {
        finish();
    }
};

} ```

Runnable 方法實現分為兩個部分:

  • 執行 BroadcastReceiver.onReceive() 方法之前會設定 PendingResult
  • BroadcastReceiver.onReceive() 方法執行完後,檢查 PendingResult 是否為空,如果為空,就不會結束掉當前註冊的 BroadcastReceiver

靜態處理

首先會呼叫 ActivityThread#ApplicationThread 類中 scheduleReceiver 方法。
frameworks/base/core/java/android/app/ActivityThread #ApplicationThread . java

kotlin public final void scheduleReceiver(Intent intent, ...) { sendMessage(H.RECEIVER, r); }

通過 sendMessage(H.RECEIVER, r) 方法往主執行緒拋一個 RECEIVER 訊息,傳送 RECEIVER 訊息的同時會攜帶 ReceiverData 例項,其中 rReceiverData 例項, ReceiverDataBroadcastReceiver.PendingResult 的子類。

在主執行緒訊息佇列中接受 RECEIVER 訊息,最後會呼叫 ActivityThread 中的 handleMessage 方法。
frameworks/base/core/java/android/app/ActivityThread. java

```kotlin private void handleReceiver(ReceiverData data) { BroadcastReceiver receiver; try { // 通過反射構造一個 BroadcastReceiver 例項 receiver = packageInfo.getAppFactory() .instantiateReceiver(cl, data.info.name, data.intent); } catch (Exception e) {

}

......

try {
    // 為註冊的廣播接受者設定 PendingResult
    // data 是 ReceiverData 例項, ReceiverData 是 BroadcastReceiver.PendingResult 的子類
    receiver.setPendingResult(data);

    // 執行 BroadcastReceiver#onReceive 方法
    receiver.onReceive(context.getReceiverRestrictedContext(),
            data.intent);
} catch (Exception e) {
   ......
}

// 判斷 PendingResult 是否為空,如果為空,就不會結束掉當前註冊的 Receiver
// 應用層可以呼叫 BroadcastReceiver.goAsync,將 PendingResult 設定為 null,從而打斷廣播後續處理流程
if (receiver.getPendingResult() != null) {
    data.finish();
}

} ```

handleMessage 方法實現分為兩個部分:

  • 通過反射構造一個 BroadcastReceiver 例項
  • 執行 BroadcastReceiver.onReceive() 方法之前會設定 PendingResult
  • BroadcastReceiver.onReceive() 方法執行完後,檢查 PendingResult 是否為空,如果為空,就不會結束掉當前註冊的 BroadcastReceiver

靜態處理和動態處理,最終的處理流程都是一樣的,唯一的區別靜態處理是通過反射構造一個 BroadcastReceiver 例項。

為什麼 goAsync 方法,可以保證廣播處於活躍狀態

通過上面的原始碼分析,我們可以知道只需要將 PendingResult 設定為 null,不會馬上結束掉當前的廣播,相當於 "延長了廣播的生命週期",因此 Google 提供了 goAsync() 方法給開發者呼叫,當呼叫 goAsync() 時,不會結束掉當前的廣播,讓廣播依然處於活躍狀態。goAsync() 方法的實現很簡單。

kotlin public final PendingResult goAsync() { PendingResult res = mPendingResult; mPendingResult = null; return res; }

goAsync() 方法主要將 PendingResult 設定為 null,當 BroadcastReceiver.onReceive() 方法執行結束,會檢查 PendingResult 是否為 null,如果為 null 不會結束掉當前的 BroadcastReceiver,需要開發者在合適的時機主動呼叫 PendingResult.finish() 方法,手動結束掉當前 BroadcastReceiver,否則會觸發廣播的超時機制(10s/60s) 發生 ANR。

對程序的影響

BroadcastReceiver 的狀態會影響其所在程序的狀態,而程序的狀態又會影響它被系統回收的可能性。因為前臺程序和後臺程序,系統對它們的影響是不同的。

如何區分前臺程序

如果滿足以下任一條件,則程序會被認為位於前臺。

  • 它正在使用者的互動螢幕上執行一個 Activity(其 onResume() 方法已被呼叫)。
  • 它有一個 BroadcastReceiver 目前正在執行(其 BroadcastReceiver.onReceive() 方法正在執行)
  • 它有一個 Service 目前正在執行其某個回撥(Service.onCreate()Service.onStart()Service.onDestroy())中的程式碼。

所以你不應該在 onReceive() 中啟動一個長時間執行的子執行緒,當 onReceive() 方法執行完返回時,BroadcastReceiver 就不再活躍,系統會將其程序視為低優先順序程序,系統會根據記憶體情況來回收,在此過程中,也會終止程序中執行的派生執行緒。

所以如果你要在子執行緒中執行一個長時間的任務,我們可以使用 goAsync() 方法,它會中斷廣播後續處理流程,讓 BroadcastReceiver 處於活躍狀態,即使 onReceive() 方法執行完,也不會結束掉當前 BroadcastReceiver,除非主動呼叫 PendingResult.finish() 方法。

在什麼場景下使用 goAsync

BroadcastReceiver. onReceive () 方法執行在主執行緒中,如果我們在主執行緒做耗時任務就會出現 ANR。

PS:關於廣播 ANR 發生的場景、解決方案、原始碼分析,將會在後面穩定性系列文章中分析

如果有耗時任務,大部分同學的做法是,直接在 onReceive () 方法中起子執行緒處理耗時任務,當 onReceive () 方法返回時,BroadcastReceiver 不會在處於活躍狀態,那麼廣播所在的程序也會受到影響,如果當前 BroadcastReceiver 所在的程序被系統回收了,那麼子執行緒中的任務也會受到影響。

一般的處理方式會通過 IntentService、JobService 方式,保證任務能夠正常的執行完,但是使用 Service 的方式會帶來很多的問題,因為 Service 是通過 AMS 進行跨程序排程,AMS 排程也會有超時機制,如果因為系統原因,或者未知原因,導致 AMS 排程延遲了,ANR 的概率會增大,而且程式碼的複雜度也變高了。

Google 也注意到這一點,所以在 BroadcastReceiver 排程流程中留出來一個入口。增加了一個靜態內部類 PendingResult,並且提供了 goAsync () 方法給開發者呼叫,如果你需要執行一個長時間的任務,在切換到子執行緒之前,需要呼叫 goAsync () 方法,讓廣播處於活躍狀態,在系統限制的時間內,處理完任務之後,主動呼叫 PendingResult. finish () 方法,結束掉當前的廣播。

如何使用 goAsync

這裡我以 Google play services cloud messaging 中的原始碼為例。

```kotlin public abstract class CloudMessagingReceiver extends BroadcastReceiver { public final void onReceive(final Context context, final Intent intent) { // 呼叫 goAsync() 返回新的 PendingResult,並將原 PendingResult 設定為 null final BroadcastReceiver.PendingResult goAsync = goAsync();

    // 開啟執行緒處理接受的訊息,並將 goAsync 傳遞到子執行緒
    getBroadcastExecutor().execute(new Runnable() {
        @Override 
        public final void run() {
            parseIntent(intent, , goAsync);
        }
    });
}

public final void parseIntent(Intent intent, BroadcastReceiver.PendingResult goAsync) {
    try {
        /**
        * 處理耗時任務,如果任務在限定時間內處理完所有訊息,主動呼叫 goAsync.finish() 方法結束當前的 Receiver
        **/
    } finally {
        goAsync.finish();
    }
}

} ```


全文到這裡就結束了,感謝你的閱讀,堅持原創不易,歡迎在看、點贊、分享給身邊的小夥伴,我會持續分享原創乾貨!!!


我開了一個雲同步編譯工具(SyncKit),主要用於本地寫程式碼,同步到遠端裝置,在遠端裝置上進行編譯,最後將編譯的結果同步到本地,程式碼已經上傳到 Github,歡迎前往倉庫 hi-dhl/SyncKit 檢視。


真誠推薦你關注我,公眾號:ByteCode ,持續分享硬核原創內容,Kotlin、Jetpack、效能優化、系統原始碼、演算法及資料結構、動畫、大廠面經。


最新文章

開源新專案

  • 雲同步編譯工具(SyncKit),本地寫程式碼,遠端編譯,歡迎前去檢視 SyncKit

  • KtKit 小巧而實用,用 Kotlin 語言編寫的工具庫,歡迎前去檢視 KtKit

  • 最全、最新的 AndroidX Jetpack 相關元件的實戰專案以及相關元件原理分析文章,正在逐漸增加 Jetpack 新成員,倉庫持續更新,歡迎前去檢視 AndroidX-Jetpack-Practice

  • LeetCode / 劍指 offer,包含多種解題思路、時間複雜度、空間複雜度分析,線上閱讀