The Android Developer Challenge is back! Submit your idea before December 2.

WebView オブジェクトの制御

Android には、アプリ内にウェブ コンテンツを表示する WebView オブジェクトを制御するのに役立つ API がいくつか用意されています。

このページでは、それらの API を使用して、WebView オブジェクトをより効果的に使用し、アプリの安定性と安全性を高める方法を説明します。

Version API

Android 7.0(API レベル 24)以降では、WebView オブジェクトにウェブ コンテンツを表示する際に、ユーザーが複数のパッケージの中から任意のものを選択できます。Android 8.0(API レベル 26)以降のバージョンに含まれる API を使用すると、アプリ内にウェブ コンテンツを表示しているパッケージに関する情報を取得できます。この API は、特定のパッケージ用に実装した WebView を使用して、アプリがウェブ コンテンツの表示を試みたときにのみ発生するエラーを分析する際に特に役に立ちます。

この API を使用するには、以下のコード スニペットに示すロジックを追加します。

Kotlin

val webViewPackageInfo = WebView.getCurrentWebViewPackage()
Log.d("MY_APP_TAG", "WebView version: ${webViewPackageInfo.versionName}")

Java

PackageInfo webViewPackageInfo = WebView.getCurrentWebViewPackage();
Log.d("MY_APP_TAG", "WebView version: " + webViewPackageInfo.versionName);

注:端末が正しくセットアップされていない場合、WebView.getCurrentWebViewPackage() メソッドは null を返すことがあります。また、Wear OS 端末などの WebView をサポートしていない端末上でアプリを実行している場合も、null が返されます。

Google Safe Browsing API

ユーザーがより安全にウェブを閲覧できるようにするために、WebView オブジェクトでは Google セーフ ブラウジングを使用して URL を検証します。これにより、ユーザーが安全でない可能性のあるウェブサイトに移動しようとした場合に、アプリで警告を表示することができます。

EnableSafeBrowsing のデフォルト値は true ですが、ある条件でのみセーフ ブラウジングを有効にしたい場合や、無効のままにしたい場合があります。Android 8.0(API レベル 26)以降では、 setSafeBrowsingEnabled() を使用できます。これより前の API で作成されたアプリでは setSafeBrowsingEnabled() を使用できません。したがって、すべての WebView インスタンスでこの機能を無効にするには、マニフェストの EnableSafeBrowsing 値を false に変更する必要があります。

アプリが Android 7.1(API レベル 25)以降向けに作成されている場合には、次の <meta-data> 要素をアプリのマニフェスト ファイルに追加することで、Google セーフ ブラウジングの安全でないウェブサイトのリストを WebView オブジェクトで確認する必要がなくなります。

<manifest>
    <application>
        <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
                   android:value="false" />
        ...
    </application>
</manifest>

プログラム アクションの定義

Google によって既知の脅威があると判断されたページを WebView のインスタンスが読み込もうとすると、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

    superSafeWebView.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;

    superSafeWebView.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 : WebViewClient() {
    // 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: SafeBrowsingResponse
    ) {
        // The "true" argument indicates that your app reports incidents like
        // this one to Safe Browsing.
        callback.backToSafety(true)
        Toast.makeText(view.context, "Unsafe web page blocked.", Toast.LENGTH_LONG).show()
    }
}

Java

public class MyWebViewClient extends WebViewClient {
    // 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, SafeBrowsingResponse callback) {
        // The "true" argument indicates that your app reports incidents like
        // this one to Safe Browsing.
        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 の動作を指定している場合は除きます。

メモリ不足の状態におけるシステムの詳細な振る舞いについては、プロセスとアプリケーションのライフサイクルをご確認ください。