應用程式首次使用 WebView 時,系統會執行特定啟動工作。啟動程序會耗用大量資源,根據預設,當應用程式首次在 android.webkit 或 androidx.webkit 套件中呼叫多個 API,或膨脹含有 WebView 標記的版面配置時,系統會在 UI 執行緒上隱含地執行這項作業。
重要性
由於這項隱含啟動作業完全在主執行緒上進行,因此會阻斷應用程式處理使用者輸入內容,並大幅增加「應用程式無回應」(ANR) 錯誤的風險。如要進一步瞭解 Android 如何處理單一執行緒執行模型,請參閱「處理程序和執行緒總覽」。
隱含啟動的觸發條件
您可以透過下列方式觸發隱含啟動:
- 以程式輔助方式:呼叫
WebSettings.getUserAgentString()等 API。 - 使用版面配置:在包含
<WebView>的 XML 資源上呼叫setContentView()或layoutInflater.inflate()。
隱含啟動也可能對業務指標造成負面影響,例如應用程式啟動時間和首次顯示時間。如果隱含初始化不適合您的應用程式,請改用 startUpWebView。
本頁說明如何使用 startUpWebView API,將 WebView 啟動效能最佳化。
控管 WebView 啟動程序
如要提升效能並盡量減少 ANR,請使用 Jetpack Webkit 程式庫提供的 startUpWebView API。這個 API 可讓您明確控制 WebView 的啟動時間。這項功能可將大量啟動工作負載移至背景執行緒,並讓必須在 UI 執行緒上執行的任何工作,都能以區塊形式完成,而非以單一大型單體式區塊完成。這樣一來,UI 執行緒就能同時處理其他重要的應用程式工作,減少阻礙使用者體驗的機會。
API 會使用 androidx.webkit.WebViewOutcomeReceiver 回呼,方便您追蹤初始化作業是否成功。
如要使用這項 API,請將 Jetpack Webkit 程式庫新增至 build.gradle 檔案。請確認您使用的是 1.16.0 以上版本:
dependencies {
implementation("androidx.webkit:webkit:1.16.0")
}
使用 startUpWebView API
如何將啟動流程調整到最佳狀態,取決於應用程式實際需要顯示 WebView 的時間。
WebView 不在關鍵路徑上時
如果應用程式不需要立即載入 WebView,您可以完全隱藏初始化成本。在應用程式生命週期初期呼叫 startUpWebView,並等待成功回呼觸發。
建議您先等待回呼,再呼叫其他 WebView API。如果您觸發 startUpWebView,但未等待完成就觸控其他 WebView 元件,系統會封鎖 UI 執行緒,等待初始化完成。應用程式可能會從已完成的背景作業獲得一些效能優勢,但並非最大效益。
WebView 位於重要路徑時
如果應用程式的核心使用者歷程需要立即使用 WebView,您可能無法等待 WebView 啟動完成。在這種情況下,您仍應盡早在應用程式生命週期中呼叫 startUpWebView (例如在 Application.onCreate 中),但請勿等待觸發回呼。請改為在需要時直接使用 WebView API。
如要盡量發揮非同步啟動的優勢,請務必延後例項化 WebView 或呼叫 WebView API,直到沒有其他重要路徑的 UI 執行緒作業要執行為止 (例如膨脹版面配置階層、初始化其他 SDK 或繪製初始影格)。
如果您呼叫 startUpWebView,然後立即在主執行緒上叫用 WebView API,UI 執行緒會封鎖,等待初始化作業趕上進度。在此情境中,效能不會有任何提升。
如果 WebView 用量可能成為重要路徑,但您不想完全啟動 WebView,可以選擇性地執行可在背景執行緒上執行的 WebView 啟動工作,將 UI 執行緒空出來執行其他應用程式的重要工作。為此,您可以使用 shouldRunUiThreadStartUpTasks(false)。
在應用程式生命週期的後續階段,您可以再次呼叫 startUpWebView 並使用 shouldRunUiThreadStartUpTasks(true),在 UI 執行緒上完成剩餘的啟動工作。您是否要等待該時間點的回呼,取決於 WebView 使用情形是否位於重要路徑。
導入範例
API 會使用 androidx.webkit.WebViewOutcomeReceiver 回呼,讓您追蹤初始化是否成功,或處理診斷失敗情形。
從應用程式的不同部分多次呼叫 startUpWebView 是安全的。建議您避免實作簡單的重試迴圈。
以下程式碼範例說明如何使用 WebViewCompat.startUpWebView API 進行非同步初始化。
Kotlin
import android.content.Context
import android.util.Log
import androidx.webkit.WebViewCompat
import androidx.webkit.WebViewOutcomeReceiver
import androidx.webkit.WebViewStartUpConfig
import androidx.webkit.WebViewStartUpResult
import androidx.webkit.WebViewStartupException
import java.util.concurrent.Executors
fun initializeWebView(context: Context) {
// 1. Create a startup configuration specifying the background thread
// that WebView will use to run its initialization tasks.
val startUpConfig = WebViewStartUpConfig.Builder(
Executors.newSingleThreadExecutor()
).build()
// 2. Trigger WebView startup asynchronously
WebViewCompat.startUpWebView(
context,
startUpConfig,
object : WebViewOutcomeReceiver<WebViewStartUpResult, WebViewStartupException> {
override fun onResult(result: WebViewStartUpResult) {
// Success: The WebView has finished its background initialization.
// This callback is guaranteed to be invoked on the UI thread.
setupWebView()
}
override fun onError(error: WebViewStartupException) {
// Failure: The initialization encountered a startup exception.
Log.e("WebViewStartup", "Failed to initialize WebView", error)
}
}
)
}
Java
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewOutcomeReceiver;
import androidx.webkit.WebViewStartUpConfig;
import androidx.webkit.WebViewStartUpResult;
import androidx.webkit.WebViewStartupException;
import java.util.concurrent.Executors;
public void initializeWebView(Context context) {
// 1. Create the startup configuration specifying the background thread pool
// to handle internal non-UI initialization processes.
WebViewStartUpConfig startUpConfig = new WebViewStartUpConfig.Builder(
Executors.newSingleThreadExecutor()
).build();
// 2. Trigger WebView startup asynchronously
WebViewCompat.startUpWebView(
context,
startUpConfig,
new WebViewOutcomeReceiver<WebViewStartUpResult, WebViewStartupException>() {
@Override
public void onResult(@NonNull WebViewStartUpResult result) {
// Success: The WebView has finished its background initialization.
// This callback is invoked directly on the UI thread.
setupWebView();
}
@Override
public void onError(@NonNull WebViewStartupException error) {
// Failure: Handled using the concrete WebViewStartupException
Log.e("WebViewStartup", "Failed to initialize WebView", error);
}
}
);
}
偵錯非同步啟動問題
如果 startUpWebView 未產生預期的效能優勢,通常是因為在執行呼叫之前,應用程式中的其他位置已隱含初始化 WebView。可能原因如下:
在應用程式生命週期初期初始化的第三方程式庫或 SDK。
ContentProviders插入 APK,在應用程式啟動期間觸發 WebView API。版面配置膨脹或程式輔助呼叫 (例如擷取使用者代理程式字串) 發生時間過早,超出預期。
為協助您診斷這些非預期初始化作業發生的位置和原因,WebViewStartUpResult 物件提供內建稽核功能:
getUiThreadBlockingStartUpLocations():傳回StartUpLocation物件清單,代表 WebView 啟動工作封鎖主要 UI 執行緒的位置。getNonUiThreadBlockingStartUpLocations():傳回特定呼叫網站,其中執行啟動工作會封鎖背景執行緒。
每個 StartUpLocation 都包含堆疊追蹤記錄,您可以記錄或檢查這些記錄,找出觸發初始化的確切類別和方法。
導入範例
您可以在 onResult 回呼中檢查這些位置,稽核啟動路徑:
override fun onResult(result: WebViewStartUpResult) {
// Check if WebView startup was blocked on the UI thread prior to or during initialization
val uiBlockingLocations = result.getUiThreadBlockingStartUpLocations()
if (!uiBlockingLocations.isNullOrEmpty()) {
for (location in uiBlockingLocations) {
// Log the stack trace of the call site that triggered the UI-blocking startup
Log.w("WebViewDebug", "WebView startup blocked the UI thread here:", location.getStack())
}
} else {
Log.i("WebViewDebug", "Excellent! No UI-blocking WebView startup detected.")
}
// Check where background initialization tasks were executed
val backgroundLocations = result.getNonUiThreadBlockingStartUpLocations()
backgroundLocations?.forEach { location ->
Log.d("WebViewDebug", "WebView background startup occurred at: ${location.getStack()}")
}
setupWebView()
}
如何在稽核期間使用這項資料
稽核應用程式的 WebView 啟動程序時,請使用下列策略分析診斷資料,並解決效能瓶頸:
尋找非預期的堆疊追蹤記錄:如果
getUiThreadBlockingStartUpLocations()不是空白,請查看列印的堆疊追蹤記錄。如果看到屬於第三方 SDK 的類別或非預期的元件,表示您已發現隱含的初始化瓶頸。驗證呼叫順序:如果記錄輸出內容顯示隱含初始化作業發生在手動
startUpWebView呼叫之前,您應將startUpWebView初始化作業移至應用程式中較早的位置,或設定違規 SDK,延遲其 WebView 相關工作。
從先前的規避措施遷移
過去,您可能使用過明確的解決方法,在背景執行緒上強制初始化 WebView,例如擷取使用者代理程式字串。
這些解決方法屬於不支援的做法,且基礎行為可能會在後續版本中變更。如果您的應用程式依賴任何未記錄的明確變通方法來觸發或管理 WebView 啟動作業,建議改用 startUpWebView API。Jetpack Webkit 程式庫支援的所有 Android 和 WebView 版本,都適用於 startUpWebView API。
使用 Jetpack Webkit 實作項目,有助於確保整個 Android 生態系統的行為一致。這項 API 的主要優點是復原力強大:在無法使用最新最佳化功能的舊版裝置上,這項 API 仍能維持與手動解決方法相同的效能。這樣一來,您就能在較新的裝置上採用現代啟動優勢,而不必在舊裝置上付出效能代價。
如果遇到問題或對 startUpWebView API 有任何意見,請在公開的 Issue Tracker 中回報錯誤。