Android には、アプリでウェブ コンテンツを表示する WebView オブジェクトの管理に役立つ API がいくつか用意されています。
このページでは、これらの API を使用して WebView オブジェクトをより効果的に操作し、アプリの安定性とセキュリティを向上させる方法について説明します。
Version API
Android 7.0(API レベル 24)以降では、ユーザーは、WebView オブジェクトにウェブ
コンテンツを表示するパッケージを、複数のパッケージの中から選択できます。
Jetpack 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 インスタンスに対して安全なページに必ず戻るよう指示する方法を示しています。
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 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 を指定すると、システムがレンダラ
プロセスを維持し続けているかどうかを、アプリ側では考慮しなくなります。実際、優先度がこのレベルまで下がると、メモリ不足に陥った際に、レンダラ
プロセスが強制終了される可能性が高くなります。
メモリ不足の状況におけるシステムの対処方法について詳しくは、 プロセスとアプリの ライフサイクルをご覧ください。