Android には、アプリでウェブ コンテンツを表示する WebView
オブジェクトの管理に役立つ API がいくつか用意されています。
このページでは、これらの API を使用して WebView
オブジェクトをより効果的に操作し、アプリの安定性とセキュリティを向上させる方法について説明します。
Version API
Android 7.0(API レベル 24)以降では、ウェブ コンテンツを WebView
オブジェクト内に表示する際に、複数のパッケージの中から選択できます。AndroidX.webkit ライブラリには、アプリにウェブ コンテンツを表示しているパッケージに関する情報を取得するための getCurrentWebViewPackage()
メソッドが含まれています。このメソッドは、特定のパッケージの WebView
の実装を使用してアプリがウェブ コンテンツを表示しようとしたときにのみ発生するエラーを分析する場合に役立ちます。
このメソッドを使用するには、次のコード スニペットに示すロジックを追加します。
Kotlin
val webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(appContext) Log.d("MY_APP_TAG", "WebView version: ${webViewPackageInfo.versionName}")
Java
PackageInfo webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(appContext); Log.d("MY_APP_TAG", "WebView version: " + webViewPackageInfo.versionName);
Google セーフ ブラウジング サービス
ユーザーに安全なブラウジング エクスペリエンスを提供するため、WebView
オブジェクトは Google セーフ ブラウジングを使用して URL を検証します。これにより、ユーザーが安全でない可能性のあるウェブサイトに移動しようとすると、アプリで警告が表示されます。
EnableSafeBrowsing
のデフォルト値は true ですが、セーフ ブラウジングを条件付きでのみ有効にしたり無効にしたりすることもできます。Android 8.0(API レベル 26)以降では、setSafeBrowsingEnabled()
を使用して、個々の WebView
オブジェクトについてセーフ ブラウジングを切り替えることができます。
すべての WebView
オブジェクトでセーフ ブラウジング チェックをオプトアウトするには、アプリのマニフェスト ファイルに次の <meta-data>
要素を追加します。
<manifest> <application> <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing" android:value="false" /> ... </application> </manifest>
プログラマティック アクションを定義する
WebView
のインスタンスが、Google によって既知の脅威として分類されたページを読み込もうとすると、WebView
にはデフォルトで、既知の脅威についてユーザーに警告するインタースティシャルが表示されます。この画面では、この URL を読み込むか、安全な前のページに戻るかを選択できます。
Android 8.1(API レベル 27)以降をターゲットとしている場合は、既知の脅威にアプリが対応する方法を次の方法でプログラムで定義できます。
- アプリからセーフ ブラウジングに既知の脅威を報告するかどうかは、ご自身で管理できます。
- 既知の脅威に分類された URL にアクセスするたびに、安全なページに戻るなどの特定のアクションがアプリに自動的に実行されるように設定できます。
次のコード スニペットは、既知の脅威を検出した後に必ず安全な状態に戻るよう、アプリの WebView
インスタンスに指示する方法を示しています。
Kotlin
private lateinit var superSafeWebView: WebView private var safeBrowsingIsInitialized: Boolean = false // ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) superSafeWebView = WebView(this) superSafeWebView.webViewClient = MyWebViewClient() safeBrowsingIsInitialized = false if (WebViewFeature.isFeatureSupported(WebViewFeature.START_SAFE_BROWSING)) { WebViewCompat.startSafeBrowsing(this, ValueCallback<Boolean> { success -> safeBrowsingIsInitialized = true if (!success) { Log.e("MY_APP_TAG", "Unable to initialize Safe Browsing!") } }) } }
Java
private WebView superSafeWebView; private boolean safeBrowsingIsInitialized; // ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); superSafeWebView = new WebView(this); superSafeWebView.setWebViewClient(new MyWebViewClient()); safeBrowsingIsInitialized = false; if (WebViewFeature.isFeatureSupported(WebViewFeature.START_SAFE_BROWSING)) { WebViewCompat.startSafeBrowsing(this, new ValueCallback<Boolean>() { @Override public void onReceiveValue(Boolean success) { safeBrowsingIsInitialized = true; if (!success) { Log.e("MY_APP_TAG", "Unable to initialize Safe Browsing!"); } } }); } }
Kotlin
class MyWebViewClient : WebViewClientCompat() { // Automatically go "back to safety" when attempting to load a website that // Google identifies as a known threat. An instance of WebView calls this // method only after Safe Browsing is initialized, so there's no conditional // logic needed here. override fun onSafeBrowsingHit( view: WebView, request: WebResourceRequest, threatType: Int, callback: SafeBrowsingResponseCompat ) { // The "true" argument indicates that your app reports incidents like // this one to Safe Browsing. if (WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY)) { callback.backToSafety(true) Toast.makeText(view.context, "Unsafe web page blocked.", Toast.LENGTH_LONG).show() } } }
Java
public class MyWebViewClient extends WebViewClientCompat { // Automatically go "back to safety" when attempting to load a website that // Google identifies as a known threat. An instance of WebView calls this // method only after Safe Browsing is initialized, so there's no conditional // logic needed here. @Override public void onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType, SafeBrowsingResponseCompat callback) { // The "true" argument indicates that your app reports incidents like // this one to Safe Browsing. if (WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY)) { callback.backToSafety(true); Toast.makeText(view.getContext(), "Unsafe web page blocked.", Toast.LENGTH_LONG).show(); } } }
HTML5 Geolocation API
Android 6.0(API レベル 23)以降をターゲットとするアプリの場合、Geolocation API は HTTPS などの安全なオリジンでのみサポートされます。保護されていないオリジンからの Geolocation API へのリクエストは、対応する onGeolocationPermissionsShowPrompt()
メソッドを呼び出すことなく自動的に拒否されます。
指標の収集を無効にする
WebView
では、ユーザーの同意が得られたときに匿名の診断データを Google にアップロードできます。データは、WebView
をインスタンス化するアプリごとにアプリごとに収集されます。この機能を無効にするには、マニフェストの <application>
要素に次のタグを作成します。
<manifest> <application> ... <meta-data android:name="android.webkit.WebView.MetricsOptOut" android:value="true" /> </application> </manifest>
ユーザーが同意し、かつアプリがオプトアウトしていない場合にのみ、アプリからデータがアップロードされます。診断データのレポートをオプトアウトする方法については、WebView レポートにおけるユーザーのプライバシーをご覧ください。
Termination Handling API
Termination Handling API は、システムがレンダラを強制終了して必要なメモリを再利用した場合や、レンダラ プロセスがクラッシュした場合に、WebView
オブジェクトのレンダラ プロセスが消失した場合に対応します。この API を使用すると、レンダラ プロセスが終了してもアプリを実行し続けることができます。
特定のウェブページの読み込み中にレンダラがクラッシュした場合、同じページを再度読み込もうとすると、新しい WebView
オブジェクトによって同じレンダリング クラッシュ動作が発生しない場合があります。
次のコード スニペットは、Activity
内でこの API を使用する方法を示しています。
Kotlin
inner class MyRendererTrackingWebViewClient : WebViewClient() { private var mWebView: WebView? = null override fun onRenderProcessGone(view: WebView, detail: RenderProcessGoneDetail): Boolean { if (!detail.didCrash()) { // Renderer is killed because the system ran out of memory. The app // can recover gracefully by creating a new WebView instance in the // foreground. Log.e("MY_APP_TAG", ("System killed the WebView rendering process " + "to reclaim memory. Recreating...")) mWebView?.also { webView -> val webViewContainer: ViewGroup = findViewById(R.id.my_web_view_container) webViewContainer.removeView(webView) webView.destroy() mWebView = null } // By this point, the instance variable "mWebView" is guaranteed to // be null, so it's safe to reinitialize it. return true // The app continues executing. } // Renderer crashes because of an internal error, such as a memory // access violation. Log.e("MY_APP_TAG", "The WebView rendering process crashed!") // In this example, the app itself crashes after detecting that the // renderer crashed. If you handle the crash more gracefully and let // your app continue executing, you must destroy the current WebView // instance, specify logic for how the app continues executing, and // return "true" instead. return false } }
Java
public class MyRendererTrackingWebViewClient extends WebViewClient { private WebView mWebView; @Override public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) { if (!detail.didCrash()) { // Renderer is killed because the system ran out of memory. The app // can recover gracefully by creating a new WebView instance in the // foreground. Log.e("MY_APP_TAG", "System killed the WebView rendering process " + "to reclaim memory. Recreating..."); if (mWebView != null) { ViewGroup webViewContainer = (ViewGroup) findViewById(R.id.my_web_view_container); webViewContainer.removeView(mWebView); mWebView.destroy(); mWebView = null; } // By this point, the instance variable "mWebView" is guaranteed to // be null, so it's safe to reinitialize it. return true; // The app continues executing. } // Renderer crashes because of an internal error, such as a memory // access violation. Log.e("MY_APP_TAG", "The WebView rendering process crashed!"); // In this example, the app itself crashes after detecting that the // renderer crashed. If you handle the crash more gracefully and let // your app continue executing, you must destroy the current WebView // instance, specify logic for how the app continues executing, and // return "true" instead. return false; } }
Renderer Importance API
WebView
オブジェクトがマルチプロセス モードで動作する場合、メモリ不足の状況をアプリで柔軟に処理できます。Android 8.0 で導入された Renderer Importance API を使用して、特定の WebView
オブジェクトに割り当てられたレンダラに優先度ポリシーを設定できます。特に、アプリの WebView
オブジェクトを表示するレンダラが強制終了された場合に、アプリの主要部分の実行を継続したい場合があります。たとえば、WebView
オブジェクトを長時間表示しないことが予想される場合などに、レンダラが使用していたメモリをシステムが回収できるようにします。
次のコード スニペットは、アプリの WebView
オブジェクトに関連付けられたレンダラ プロセスに優先度を割り当てる方法を示しています。
Kotlin
val myWebView: WebView = ... myWebView.setRendererPriorityPolicy(RENDERER_PRIORITY_BOUND, true)
Java
WebView myWebView; myWebView.setRendererPriorityPolicy(RENDERER_PRIORITY_BOUND, true);
このスニペットでは、レンダラの優先度はアプリのデフォルトの優先度と同じか、バインドされています。true
引数は、関連付けられた WebView
オブジェクトが表示されなくなったときに、レンダラの優先度を RENDERER_PRIORITY_WAIVED
に下げています。つまり、true
引数は、システムがレンダラ プロセスを維持しているかどうかをアプリで考慮しないことを示します。実際、この優先度が低いと、メモリ不足でレンダラ プロセスが強制終了される可能性が高くなります。
メモリ不足の状況にシステムが対応する仕組みについては、プロセスとアプリのライフサイクルをご覧ください。