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);
注: デバイスが適切にセットアップされていない場合、WebView
の使用をサポートしていない場合(Wear OS デバイスなど)、または Android 4.4(API レベル 19)以前のようにアップデート可能な WebView
の実装がない場合、getCurrentWebViewPackage()
メソッドは null
を返すことがあります。
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
オブジェクトの loadUrl()
メソッドはセーフ ブラウジングの初期化が完了してから呼び出してください。
以下のコード スニペットは、既知の脅威を検出したときに、アプリの WebView
インスタンスに対して安全なページに必ず戻るよう指示する方法を示しています。
MyWebActivity.java
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!"); } } }); } }
MyWebViewClient.java
Kotlin
class MyWebViewClient : WebViewClientCompat() { // Automatically go "back to safety" when attempting to load a website that // Google has identified 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 has identified 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>
ユーザーの同意があり、かつアプリでこの機能をオプトアウトしていない場合にのみ、アプリからデータがアップロードされます。
Termination Handling API
この API は、WebView
オブジェクトのレンダラ プロセスが消滅した場合に対処します。システムがレンダラを強制終了して本当に必要なメモリを回収した場合か、レンダラ プロセス自体がクラッシュした場合が、これに該当します。この API を使用すると、レンダラ プロセスが消滅してもアプリの実行を継続できます。
注意: レンダラ プロセスが消滅した後もアプリの実行が続く場合、レンダラ プロセスが強制終了されたか、クラッシュしたかにかかわらず、WebView
の関連インスタンスを再利用することはできません。アプリを実行し続けるには、そのインスタンスをビュー階層から削除して、インスタンスを破棄する必要があります。その後、さらにアプリで WebView
のまったく新しいインスタンスを作成して、ウェブページのレンダリングを継続する必要があります。
なお、特定のウェブページの読み込み中にレンダラがクラッシュした場合、同じページを再度読み込もうとすると、新しい WebView
オブジェクトで同じようにレンダリング クラッシュが発生する可能性があるため、ご注意ください。
次のコード スニペットは、この API の使用方法を示しています。
Kotlin
inner class MyRendererTrackingWebViewClient : WebViewClient() { private var mWebView: WebView? = null override fun onRenderProcessGone(view: WebView, detail: RenderProcessGoneDetail): Boolean { if (!detail.didCrash()) { // Renderer was 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 crashed 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 choose to handle the crash more gracefully // and allow your app to continue executing, you should 1) destroy the // current WebView instance, 2) specify logic for how the app can // continue executing, and 3) 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 was 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 crashed 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 choose to handle the crash more gracefully // and allow your app to continue executing, you should 1) destroy the // current WebView instance, 2) specify logic for how the app can // continue executing, and 3) 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
を指定すると、システムがレンダラ プロセスを維持し続けているかどうかを、アプリ側では考慮しなくなります。実際、優先度がこのレベルまで下がると、メモリ不足に陥った際に、レンダラ プロセスが強制終了される可能性が高くなります。
警告: アプリの安定性を維持するために、WebView
オブジェクトに対するレンダラの優先度ポリシーは変更しないでください。ただし、Termination Handle API を併用して、関連するレンダラが消滅した際の WebView
の動作を指定している場合は除きます。
メモリ不足の状況におけるシステムの対処方法について詳しくは、プロセスとアプリケーションのライフサイクルをご覧ください。