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

2019年8月になったので、[Google Playに新規アプリをリリースする際のtargetSdkVersionを28以上にしないといけないやつ](https://forest.watch.impress.co.jp/docs/news/1171267.html)が始まりました。既存アプリのアップデートも、11月から同様の措置が入るので、そろそろ対策しておきたいですね。

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

* [動作の変更点: API レベル 28+ をターゲットとするアプリ  |  Android Developers](https://developer.android.com/about/versions/pie/android-9.0-changes-28?hl=ja)

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

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

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

> Android 9 以降をターゲットとするアプリでは、4 桁および 8 桁の 16 進数 CSS 色を扱うために、ドラフトの [CSS Color Module Level 4](https://drafts.csswg.org/css-color/#hex-color) で策定された動作を有効にする必要があります。
> CSS Color Module Level 4 は、Chrome 52 からサポートされていますが、現在は [WebView](https://developer.android.com/about/versions/pie/reference/android/webkit/WebView.html?hl=ja) で無効になっています。これは、既存の Android アプリが Android のバイト順（ARGB）で 32 ビットの 16 進数色を含み、レンダリング エラーを引き起こす可能性があると判明したためです。
> たとえば現在、API レベル 27 以前をターゲットとするアプリにおいては、[WebView](https://developer.android.com/about/versions/pie/reference/android/webkit/WebView.html?hl=ja) で `#80ff8080` という色が不透明のライトレッド（`#ff8080`）にレンダリングされます。 先頭のコンポーネント（Android ではアルファ コンポーネントとして認識される）は現在、無視されます。 一方、API レベル 28 以降をターゲットとするアプリの場合、`#80ff8080` は、透明度が 50% のライトグリーン（`#80ff80`）として認識されます。

[CSS Color Module Level 3](https://www.w3.org/TR/2018/REC-css-color-3-20180619/)までは、カラーコードを4桁ないし8桁で表現して、アルファ値を指定する方法がありませんでした。Level 4では `#80ff8080` のようなRGBA表記もサポートされるということですね。

> Android 9 以降をターゲットとするアプリでは、4 桁および 8 桁の 16 進数 CSS 色を扱うために、ドラフトの [CSS Color Module Level 4](https://drafts.csswg.org/css-color/#hex-color) で策定された動作を有効にする必要があります。

上記の通り、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](https://developer.android.com/about/versions/pie/reference/android/webkit/WebView.html?hl=ja) で無効になっています。これは、既存の Android アプリが Android のバイト順（ARGB）で 32 ビットの 16 進数色を含み、レンダリング エラーを引き起こす可能性があると判明したためです。
> たとえば現在、API レベル 27 以前をターゲットとするアプリにおいては、[WebView](https://developer.android.com/about/versions/pie/reference/android/webkit/WebView.html?hl=ja) で `#80ff8080` という色が不透明のライトレッド（`#ff8080`）にレンダリングされます。 先頭のコンポーネント（Android ではアルファ コンポーネントとして認識される）は現在、無視されます。 

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

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

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

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

喜ばしいことですね。

## 検証する

> たとえば現在、API レベル 27 以前をターゲットとするアプリにおいては、[WebView](https://developer.android.com/about/versions/pie/reference/android/webkit/WebView.html?hl=ja) で `#80ff8080` という色が不透明のライトレッド（`#ff8080`）にレンダリングされます。 先頭のコンポーネント（Android ではアルファ コンポーネントとして認識される）は現在、無視されます。 

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

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

```kotlin: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")
    }
}
```

```xml: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276/62636bff-d444-2e2d-7824-b4a41c2e7015.png) | ![スクリーンショット 2019-08-02 12.30.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276/2a12167c-9d8e-6a9d-0104-4f91eada2a7f.png) |

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

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

> 現在は [WebView](https://developer.android.com/about/versions/pie/reference/android/webkit/WebView.html?hl=ja) で無効になっています。

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

> たとえば現在、API レベル 27 以前をターゲットとするアプリにおいては、[WebView](https://developer.android.com/about/versions/pie/reference/android/webkit/WebView.html?hl=ja) で `#80ff8080` という色が不透明のライトレッド（`#ff8080`）にレンダリングされます。 先頭のコンポーネント（Android ではアルファ コンポーネントとして認識される）は現在、無視されます。 

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

### Android 9.0エミュレータ

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

| targetSdkVersion 27 | targetSdkVersion 28 |
|---------------------|---------------------|
| ![スクリーンショット 2019-08-02 12.40.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276/b44182b2-f08b-7798-b509-49f84a4ca650.png) | ![スクリーンショット 2019-08-02 12.38.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276/d6ee8c25-12e5-571e-8f2c-f8351aacdf45.png) |

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

## 気をつけるべきこと

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

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

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

## 余談

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

![スクリーンショット 2019-08-02 14.32.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276/fa6a3c2d-9cba-8dcf-4f8a-58f8d07cae2e.png)

すごく読みやすい。