アプリで WebView を初めて使用するとき、システムは特定の起動タスクを実行します。
この起動プロセスは負荷が高く、デフォルトでは、アプリが android.webkit パッケージまたは androidx.webkit パッケージ内の多くの API を初めて呼び出すとき、または WebView タグを含むレイアウトを拡張するときに、UI スレッドで暗黙的に実行されます。
重要である理由
この暗黙的な起動はすべてメインスレッドで行われるため、アプリがユーザー入力を処理できなくなり、Application Not Responding(ANR)エラーのリスクが大幅に高まります。Android がシングルスレッド実行モデルを処理する方法について詳しくは、プロセスとスレッド の概要をご覧ください。
暗黙的な起動のトリガー
暗黙的な起動は、次の方法でトリガーできます。
- プログラム:
WebSettings.getUserAgentString()などの API を呼び出す。 - レイアウトの使用:
setContentView()またはlayoutInflater.inflate()を含む XML リソースで<WebView>を呼び出す。
暗黙的な起動は、アプリ
の起動時間や初回表示までの時間など、ビジネス指標に悪影響を及ぼす可能性もあります。暗黙的な初期化がアプリに最適でない場合は、代わりに startUpWebView を使用してください。
このページでは、startUpWebView API を使用して WebView の起動パフォーマンスを最適化する方法について説明します。
WebView の起動を制御する
パフォーマンスを向上させ、ANR を最小限に抑えるには、Jetpack Webkit ライブラリで利用可能な startUpWebView API を使用します。この API を使用すると、WebView の起動タイミングを明示的に制御できます。起動ワークロードの大部分をバックグラウンド スレッドに移行し、UI スレッドで実行する必要がある作業を、1 つの大きなモノリシック ブロックではなく、チャンク単位で実行できるようにします。これにより、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 の起動が完了するまで待つことはできません。このシナリオでは、アプリのライフサイクルの早い段階(Application.onCreateなど)でstartUpWebViewを呼び出す必要がありますが、コールバックがトリガーされるまで待つ必要はありません。代わりに、必要なときに WebView API を直接使用します。
非同期起動のメリットを最大限に活用するには、他のクリティカル パス上の UI スレッド オペレーション(レイアウト階層の拡張、他の SDK の初期化、最初のフレームの描画など)が実行されなくなるまで、WebView のインスタンス化または WebView API の呼び出しを遅延させることが重要です。
startUpWebView を呼び出した後、メインスレッドで WebView API をすぐに呼び出すと、初期化が追いつくまで UI スレッドがブロックされます。このシナリオでは、パフォーマンス上のメリットはありません。
WebView の使用がクリティカル パス上になる可能性があるが、WebView を完全に起動したくない場合は、バックグラウンド スレッドで実行できる WebView 起動タスクを選択的に実行し、他のアプリの重要なタスクのために UI スレッドを解放できます。これには、shouldRunUiThreadStartUpTasks(false) を使用します。
アプリのライフサイクルの後半で、shouldRunUiThreadStartUpTasks(true) を指定して startUpWebView を再度呼び出し、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。
アプリの起動時に WebView API をトリガーする APK に挿入された
ContentProviders。予期せず早く発生するレイアウトの拡張 やプログラムによる呼び出し(ユーザー エージェント文字列の取得など)。
これらの予期しない初期化がどこで、なぜ発生しているかを診断するために、WebViewStartUpResult オブジェクトには組み込みの監査機能が用意されています。
getUiThreadBlockingStartUpLocations(): WebView 起動タスクがメイン UI スレッドをブロックした場所を表すStartUpLocationオブジェクトのリストを返します。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 を使用することをおすすめします。startUpWebView API は、Jetpack Webkit ライブラリでサポートされているすべてのバージョンの Android と WebView で動作します。
アプリの復元力と安定性を確保する
Jetpack Webkit 実装を使用すると、Android エコシステム全体で一貫した動作を確保できます。この API の主な利点は復元力です。新しい最適化が利用できない古いデバイスでは、API は手動の回避策と同等のパフォーマンスを維持します。これにより、古いデバイスでパフォーマンスが低下することなく、新しいデバイスで最新の起動のメリットを活用できます。
WebView の起動を最適化すると、アプリの起動時に ANR エラーが発生するリスクが軽減されますが、ランタイム レンダラ クラッシュやシステム メモリの再利用からもアプリを保護する必要があります。WebView の実行後にアプリの安定性を維持するには、WebView の終了を処理するをご覧ください。
startUpWebView API に関する問題が発生した場合やフィードバックがある場合は、公開の Issue Tracker で
バグを報告してください。