Android 4.4 での WebView への移行

Android 4.4(API レベル 19)では Chromium に基づく WebView の新しいバージョンが導入されています。この変更により、WebView のパフォーマンスと HTML5、CSS3、JavaScript の標準サポートがアップグレードされ、最新のウェブブラウザに対応しています。WebView を使用しているアプリは、Android 4.4 以降で動作するときは、これらのアップグレードを継承します。

このドキュメントでは、targetSdkVersion を「19」以降に設定する場合に注意が必要な、WebView の変更点について説明します。

注: targetSdkVersion が「18」以下に設定されている場合、WebView は「後方互換モード」で動作し、アプリにパフォーマンスとウェブ標準のアップグレードを提供する一方で、以下に説明する動作の変更を可能な限り忠実に回避します。ただし、狭い一列レイアウトデフォルトのズームレベルは Android 4.4 ではまったくサポートされておらず、他にも特定されていない動作の違いがある可能性があるため、targetSdkVersion を「18」以下に設定したままであっても、必ず Android 4.4 以降でアプリをテストしてください。

アプリを Android 4.4 の WebView に移行する際に発生する可能性のある問題を解決するために、デスクトップの Chrome で setWebContentsDebuggingEnabled() を呼び出して、リモート デバッグを有効にできます。この WebView の新機能により、ウェブ コンテンツ、スクリプト、ネットワーク アクティビティを WebView で実行中に検査および分析できます。詳細については、Android でのリモート デバッグをご覧ください。

ユーザー エージェントの変更

ユーザー エージェントに基づいて WebView にコンテンツを配信する場合、ユーザー エージェント文字列に若干変更があり、Chrome のバージョンが含まれるようになっているため、注意が必要です。

    Mozilla/5.0 (Linux; Android 4.4; Nexus 4 Build/KRT16H) AppleWebKit/537.36
    (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36
    

ユーザー エージェントを取得する必要があるが、アプリのために保存する必要がない場合や、WebView をインスタンス化したくない場合、静的メソッド getDefaultUserAgent() を使用する必要があります。ただし、WebView でユーザー エージェント文字列をオーバーライドする場合は、代わりに getUserAgentString() を使用できます。

マルチスレッドとスレッドのブロック

アプリの UI スレッド以外のスレッドから WebView のメソッドを呼び出すと、予期しない結果が発生する可能性があります。たとえば、アプリで複数のスレッドを使用している場合は、runOnUiThread() メソッドを使用して UI スレッドでコードが実行されるようにします。

Kotlin

    runOnUiThread {
        // Code for WebView goes here
    }
    

Java

    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            // Code for WebView goes here
        }
    });
    

また、UI スレッドをブロックしないように注意してください。一部のアプリでは JavaScript コールバックの待機中にこのようなエラーが発生します。たとえば、次のようなコードは使用しないでください

Kotlin

    // This code is BAD and will block the UI thread
    webView.loadUrl("javascript:fn()")
    while (result == null) {
        Thread.sleep(100)
    }
    

Java

    // This code is BAD and will block the UI thread
    webView.loadUrl("javascript:fn()");
    while(result == null) {
      Thread.sleep(100);
    }
    

代わりに新しいメソッド evaluateJavascript() を使用すると、JavaScript を非同期で実行できます。

カスタム URL の処理

新しい WebView は、リソースを要求したり、カスタム URL スキームを使用するリンクを解決したりするときに追加の制限を適用します。たとえば、shouldOverrideUrlLoading()shouldInterceptRequest() などのコールバックを実装すると、WebView は有効な URL に対してのみコールバックを呼び出します。

カスタム URL スキームまたはベース URL を使用しているときに、アプリでこうしたコールバックの呼び出しを受ける回数が少ない、または Android 4.4 でリソースの読み込みが失敗している、といった症状が見られる場合は、リクエストで RFC 3986 準拠の有効な URL を指定していることを確認してください。

たとえば、新しい WebView は、次のようなリンクに対して shouldOverrideUrlLoading() メソッドを呼び出さない場合があります。

<a href="showProfile">Show Profile</a>

ユーザーがこのようなリンクをクリックした場合、結果は異なる可能性があります。

  • 無効または null のベース URL で loadData()loadDataWithBaseURL() を呼び出してページを読み込んだ場合、ページ上のこのタイプのリンクに対し shouldOverrideUrlLoading() コールバックを受け取ることはありません。

    注: loadDataWithBaseURL() を使用し、ベース URL が無効であるか null に設定されている場合、読み込むコンテンツ内のリンクはすべて絶対リンクである必要があります。

  • loadUrl() を呼び出してページを読み込むか、loadDataWithBaseURL() で有効なベース URL を指定した場合、ページ上のこのタイプのリンクに対し shouldOverrideUrlLoading() コールバックを受け取りますが、受け取る URL は現在のページを基準とした絶対的な URL です。たとえば、受け取る URL は、単に "showProfile" ではなく "http://www.example.com/showProfile" になります。

上記のようにリンクで単純な文字列を使用する代わりに、次のようなカスタム スキームを使用できます。

<a href="example-app:showProfile">Show Profile</a>

次に、この URL を shouldOverrideUrlLoading() メソッドで以下のように処理できます。

Kotlin

    // The URL scheme should be non-hierarchical (no trailing slashes)
    const val APP_SCHEME = "example-app:"

    override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
        return if (url?.startsWith(APP_SCHEME) == true) {
            urlData = URLDecoder.decode(url.substring(APP_SCHEME.length), "UTF-8")
            respondToData(urlData)
            true
        } else {
            false
        }
    }
    

Java

    // The URL scheme should be non-hierarchical (no trailing slashes)
    private static final String APP_SCHEME = "example-app:";

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if (url.startsWith(APP_SCHEME)) {
            urlData = URLDecoder.decode(url.substring(APP_SCHEME.length()), "UTF-8");
            respondToData(urlData);
            return true;
        }
        return false;
    }
    

HTML を変更できないときは、loadDataWithBaseURL() を使用して、カスタム スキームと有効なホストで構成されるベース URL("example-app://<valid_host_name>/" など)を設定できる場合があります。次に例を示します。

Kotlin

    webView.loadDataWithBaseURL("example-app://example.co.uk/", HTML_DATA, null, "UTF-8", null)
    

Java

    webView.loadDataWithBaseURL("example-app://example.co.uk/", HTML_DATA,
            null, "UTF-8", null);
    

有効なホスト名は RFC 3986 に準拠する必要があります。重要なのは、末尾にスラッシュを含めることです。そうしないと、読み込まれたページからのリクエストが破棄される場合があります。

ビューポートの変更

ビューポート target-densitydpi のサポート終了

以前の WebView は、ウェブページで目的の画面密度を指定できるように、target-densitydpi と呼ばれるビューポート プロパティをサポートしていました。このプロパティは現在サポートされていません。WebView の Pixel-Perfect UI で説明している、画像と CSS の標準ソリューションを使用する手法に移行する必要があります。

値が小さいと拡大するビューポート

以前は、ビューポートの幅を「320」以下の値に設定すると「device-width」に設定され、ビューポートの高さを WebView の高さ以下の値に設定すると「device-height」に設定されました。ただし、新しい WebView で実行すると、幅または高さの値が適用され、WebView は画面の幅に合わせて拡大します。

複数のビューポート タグは未対応

以前は、ウェブページに複数のビューポート タグを含めると、WebView はすべてのタグのプロパティを統合していました。新しい WebView では、最後のビューポートのみが使用され、その他はすべて無視されます。

デフォルトのズームはサポート終了

ページの初期ズームレベルを取得および設定するメソッド getDefaultZoom()setDefaultZoom() はサポートされなくなりました。代わりに、ウェブページで適切なビューポートを定義する必要があります。

注意: これらの API は、Android 4.4 以降ではサポートされていません。targetSdkVersion を「18」以下に設定した場合でも、これらの API は無効です。

ビューポート プロパティを HTML で定義する方法については、WebView の Pixel-Perfect UI をご覧ください。

HTML でビューポートの幅を設定できない場合は、setUseWideViewPort() を呼び出して、ページに大きなビューポートが指定されていることを確認します。次に例を示します。

Kotlin

    webView.settings.apply {
        useWideViewPort = true
        loadWithOverviewMode = true
    }
    

Java

    WebSettings settings = webView.getSettings();
    settings.setUseWideViewPort(true);
    settings.setLoadWithOverviewMode(true);
    

スタイルの変更

背景の CSS の省略表現により background-size をオーバーライドする

当面の間、Chrome と他のブラウザの動作は上記のとおりでしたが、background スタイルも指定すると、WebViewbackground-size の CSS 設定もオーバーライドします。たとえば、ここのサイズはデフォルト値にリセットされます。

    .some-class {
      background-size: contain;
      background: url('images/image.png') no-repeat;
    }
    

この問題を解決するには、2 つのプロパティを切り替えるだけです。

    .some-class {
      background: url('images/image.png') no-repeat;
      background-size: contain;
    }
    

サイズが画面ピクセルではなく CSS ピクセルである

以前は、window.outerWidth および window.outerHeight などのサイズ パラメータは、実際の画面ピクセルの値を返していました。新しい WebView では、CSS ピクセルに基づいて値を返します。

要素のサイズ調整やその他の計算のためにピクセル単位で物理サイズを計算しようとするのは、一般的におすすめできません。ただし、ズームを無効にし、初期スケールが 1.0 に設定されている場合、window.devicePixelRatio を使用してスケールを取得し、この値と CSS ピクセル値を掛けることができます。代わりに、JavaScript バインディングを作成して、WebView 自体からピクセルサイズを照会することもできます。

詳細については、quirksmode.org をご覧ください。

NARROW_COLUMNS と SINGLE_COLUMN のサポート終了

WebSettings.LayoutAlgorithmNARROW_COLUMNS 値は、新しい WebView ではサポートされていません。

注意: これらの API は、Android 4.4 以降ではサポートされていません。targetSdkVersion を「18」以下に設定した場合でも、これらの API は無効です。

この変更に対し、次の方法で対処できます。

  • アプリケーションのスタイルを変更する。

    HTML と CSS をページで管理している場合は、コンテンツのデザインを変更することが最も信頼性の高い方法である可能性があります。たとえば、ライセンスを引用する画面の場合、テキストを <pre> タグ内にラップすることが可能です。これを行うには、次のスタイルを使用します。

    <pre style="word-wrap: break-word; white-space: pre-wrap;">

    これは、ページのビューポート プロパティを定義していない場合に特に便利です。

  • 新しい TEXT_AUTOSIZING レイアウト アルゴリズムを使用する。

    幅広いデスクトップ サイトをモバイル デバイスで読みやすくする方法として狭い列を使用しており、HTML コンテンツを変更できない場合、新しい TEXT_AUTOSIZING アルゴリズムが NARROW_COLUMNS の適切な代替手段になる可能性があります。

さらに、SINGLE_COLUMN 値(以前にサポート終了)も、新しい WebView でサポートされていません。

JavaScript でのタッチイベントの処理

ウェブページが WebView でタッチイベントを直接処理している場合は、touchcancel イベントも処理していることを確認してください。touchcancel が呼び出されるシナリオはいくつかあります。これを受信しない場合は問題が発生する可能性があります。

  • 要素がタッチされる(touchstarttouchmove が呼び出される)と、ページがスクロールされ、touchcancel がスローされる。
  • 要素はタッチされる(touchstart が呼び出される)が、event.preventDefault() は呼び出されず、早いために touchcancel がスローされる(したがって、WebView は、タッチイベントを使用しないものと想定)。