WebView オブジェクトを管理する

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 インスタンスに指示する方法を示しています。

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 引数は、システムがレンダラ プロセスを維持しているかどうかをアプリで考慮しないことを示します。実際、この優先度が低いと、メモリ不足でレンダラ プロセスが強制終了される可能性が高くなります。

メモリ不足の状況にシステムが対応する仕組みについては、プロセスとアプリのライフサイクルをご覧ください。