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の挙動が比較できる、次のような画面をそれぞれに配置しました。
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 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 |
---|---|
TextViewに関しては、意図したとおり透明度50%の #ff8080
が表示されています。
WebViewに表示したほうは、気持ちいいくらいに無視です。
現在は WebView で無効になっています。
という記述とは矛盾しませんが、
たとえば現在、API レベル 27 以前をターゲットとするアプリにおいては、WebView で
#80ff8080
という色が不透明のライトレッド(#ff8080
)にレンダリングされます。 先頭のコンポーネント(Android ではアルファ コンポーネントとして認識される)は現在、無視されます。
こちらの記述とは矛盾しているような気がします。
Android 9.0エミュレータ
次に、Android 9.0での挙動です。こちらはtargetSdkVersion 28のWebViewで色が付いてくれることを期待します。
targetSdkVersion 27 | targetSdkVersion 28 |
---|---|
問題なく、期待通りの挙動をしました。
気をつけるべきこと
API Level 27以下のデバイスでは、8桁表記のカラーコードがそもそも無視されているような挙動になりました。ドキュメントとは矛盾気がしますが、まあもともと使えなかったと考えれば、保守性の観点では問題がなさそうです。
どちらかというと、今後「RGBAカラーコードが使えるようになったWebView」を使ってハイブリッドアプリを開発していく場合が心配です。WebView向けにはWeb標準に沿ってRGBA表記のカラーコードをデザイナーさんが指定する機会が増えるはずですが、これをAndroidにそのまま指定すると意図しない色になってしまいます。
「Androidはカラーコードの解釈が特殊である」ということをチーム内で共有し、アプリ内に意図しない差異が生まれないよう、気をつけましょう。
余談
3桁と6桁だけとはいえ、カラーコードを``
で囲むと色が付くの、Qiitaさんすごいですね。と思いながら社内のGitLabにも貼ってみたら、あちらはRGBAの8桁にも対応してて、こちらはもっとすごかったです。
すごく読みやすい。