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

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

メモリ不足の状況におけるシステムの対処方法について詳しくは、 プロセスとアプリの ライフサイクルをご覧ください。