Edited at

targetSdkVersion 28 + Android 9.0からはWebView内にRGBAのカラーコードが書けるようになった話

2019年8月になったので、Google Playに新規アプリをリリースする際のtargetSdkVersionを28以上にしないといけないやつが始まりました。既存アプリのアップデートも、11月から同様の措置が入るので、そろそろ対策しておきたいですね。

そんなわけで、API Level 28って新しく何が入ったんだっけ、と確認してみました。

フォアグラウンドサービスにパーミッションが必要になったり、Apache HTTPクライアントのサポートが本格的に終了したりと、既存の実装次第ではアレしそうなものもいくつか見当たります。


WebViewでカラーコードの判定が変わる

読んでいく中で、ふと目についたのが「CSS RGBA 16 進値の処理」という項目です。


Android 9 以降をターゲットとするアプリでは、4 桁および 8 桁の 16 進数 CSS 色を扱うために、ドラフトの CSS Color Module Level 4 で策定された動作を有効にする必要があります。

CSS Color Module Level 4 は、Chrome 52 からサポートされていますが、現在は WebView で無効になっています。これは、既存の Android アプリが Android のバイト順(ARGB)で 32 ビットの 16 進数色を含み、レンダリング エラーを引き起こす可能性があると判明したためです。

たとえば現在、API レベル 27 以前をターゲットとするアプリにおいては、WebView#80ff8080 という色が不透明のライトレッド(#ff8080)にレンダリングされます。 先頭のコンポーネント(Android ではアルファ コンポーネントとして認識される)は現在、無視されます。 一方、API レベル 28 以降をターゲットとするアプリの場合、#80ff8080 は、透明度が 50% のライトグリーン(#80ff80)として認識されます。


CSS Color Module Level 3までは、カラーコードを4桁ないし8桁で表現して、アルファ値を指定する方法がありませんでした。Level 4では #80ff8080 のようなRGBA表記もサポートされるということですね。


Android 9 以降をターゲットとするアプリでは、4 桁および 8 桁の 16 進数 CSS 色を扱うために、ドラフトの CSS Color Module Level 4 で策定された動作を有効にする必要があります。


上記の通り、Android 9.0のWebViewでは、CSS Color Module Level 4の挙動でカラーコードが表現できるようになったようです。

さて、それだけなら話はシンプルなのですが、その後ろにごちゃごちゃと書いてある内容が気になります。


Androidのカラーコード判定はARGB

Androidはカラーコードを色指定に利用することができますが、アルファ値を含める場合の表記がARGB(#aarrggbb)になっています。もし textView.setTextColor(Color.parseColor("#80ff8080")) のようなコードでテキストに色を付けた場合、透明度50%の #ff8080 で彩色されます。

この辺の挙動が嫌な感じでAndroid ChromeのLevel 4対応とぶつかったのか、API Level 27以前はRGBA対応が無効化されていたそうです。


CSS Color Module Level 4 は、Chrome 52 からサポートされていますが、現在は WebView で無効になっています。これは、既存の Android アプリが Android のバイト順(ARGB)で 32 ビットの 16 進数色を含み、レンダリング エラーを引き起こす可能性があると判明したためです。

たとえば現在、API レベル 27 以前をターゲットとするアプリにおいては、WebView#80ff8080 という色が不透明のライトレッド(#ff8080)にレンダリングされます。 先頭のコンポーネント(Android ではアルファ コンポーネントとして認識される)は現在、無視されます。


先頭のアルファ値が無視されるという話はちょっと怪しい(後述)のですが、ひとまずアルファ値が付いているカラーコードは意図しない挙動をもたらすということで、無視する方針になったようです。


Web準拠のカラーコードが使えるようになる

API Level 28では、Chromeの間での折衝が済んだのか、WebViewでLevel 4準拠のカラーコードが使えるようになったそうです。


一方、API レベル 28 以降をターゲットとするアプリの場合、#80ff8080 は、透明度が 50% のライトグリーン(#80ff80)として認識されます。


喜ばしいことですね。


検証する


たとえば現在、API レベル 27 以前をターゲットとするアプリにおいては、WebView#80ff8080 という色が不透明のライトレッド(#ff8080)にレンダリングされます。 先頭のコンポーネント(Android ではアルファ コンポーネントとして認識される)は現在、無視されます。


上記の挙動が本当に発生するのか疑わしかったので、実際に動かしてみました。

targetSdkVersionが27と28のappモジュールをそれぞれ作成し、TextViewの挙動とWebViewの挙動が比較できる、次のような画面をそれぞれに配置しました。


MainActivity.kt

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val webView = findViewById<WebView>(R.id.webview)
val html = """
<html>
<body>
<span style="color: #80ff8080;"><b>#80ff8080</b></span>
</body>
</html>
"""
.trimIndent()
webView.loadData(html, "text/html", "utf-8")
}
}



activity_main.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".MainActivity">

<TextView
android:text="API28 or 27"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/textView"
android:textAppearance="@style/TextAppearance.AppCompat.Display1"/>

<TextView
android:text="TextView"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/textView1"
android:textAppearance="@style/TextAppearance.AppCompat.Headline"/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#80ff8080"
android:textColor="#80ff8080"
android:textStyle="bold"/>

<Space android:layout_width="match_parent" android:layout_height="16dp"/>

<TextView
android:text="WebView"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/textView2"
android:textAppearance="@style/TextAppearance.AppCompat.Headline"/>

<WebView android:id="@+id/webview" android:layout_width="match_parent" android:layout_height="64dp"/>

</LinearLayout>


これらをAndroid 8.1(API Level 27)とAndroid 9.0(API Level 28)のエミュレータで、それぞれ動かします。

※あまり影響しなさそうですが、minSdkVersionは27で共通です。


Android 8.1エミュレータ

まずは、Android 8.1での挙動です。ドキュメントに沿うならば、targetSdkVersion 27のほうが「不透明のライトレッド(#ff8080)にレンダリングされ」るはず?です。

targetSdkVersion 27
targetSdkVersion 28

スクリーンショット 2019-08-02 12.30.29.png
スクリーンショット 2019-08-02 12.30.12.png

TextViewに関しては、意図したとおり透明度50%の #ff8080 が表示されています。

WebViewに表示したほうは、気持ちいいくらいに無視です。


現在は WebView で無効になっています。


という記述とは矛盾しませんが、


たとえば現在、API レベル 27 以前をターゲットとするアプリにおいては、WebView#80ff8080 という色が不透明のライトレッド(#ff8080)にレンダリングされます。 先頭のコンポーネント(Android ではアルファ コンポーネントとして認識される)は現在、無視されます。


こちらの記述とは矛盾しているような気がします。


Android 9.0エミュレータ

次に、Android 9.0での挙動です。こちらはtargetSdkVersion 28のWebViewで色が付いてくれることを期待します。

targetSdkVersion 27
targetSdkVersion 28

スクリーンショット 2019-08-02 12.40.36.png
スクリーンショット 2019-08-02 12.38.53.png

問題なく、期待通りの挙動をしました。


気をつけるべきこと

API Level 27以下のデバイスでは、8桁表記のカラーコードがそもそも無視されているような挙動になりました。ドキュメントとは矛盾気がしますが、まあもともと使えなかったと考えれば、保守性の観点では問題がなさそうです。

どちらかというと、今後「RGBAカラーコードが使えるようになったWebView」を使ってハイブリッドアプリを開発していく場合が心配です。WebView向けにはWeb標準に沿ってRGBA表記のカラーコードをデザイナーさんが指定する機会が増えるはずですが、これをAndroidにそのまま指定すると意図しない色になってしまいます。

「Androidはカラーコードの解釈が特殊である」ということをチーム内で共有し、アプリ内に意図しない差異が生まれないよう、気をつけましょう。


余談

3桁と6桁だけとはいえ、カラーコードを``で囲むと色が付くの、Qiitaさんすごいですね。と思いながら社内のGitLabにも貼ってみたら、あちらはRGBAの8桁にも対応してて、こちらはもっとすごかったです。

スクリーンショット 2019-08-02 14.32.58.png

すごく読みやすい。