Android 10対応したので個人的まとめ (RSSリーダーアプリ編)

※ 一部内容を変更して再投稿しました:bow:

# はじめに: ようこそAndroid 10 ＆ ようこそ〆切

今年の9月3日、Android 10がリリースされました！

<blockquote class="twitter-tweet"><p lang="en" dir="ltr"><a href="https://twitter.com/hashtag/Android10?src=hash&amp;ref_src=twsrc%5Etfw">#Android10</a> is here 🎉 and ready to help. Full of new and familiar features, Android is more inclusive, accessible and safer than ever. <a href="https://t.co/uszOlbGm6P">pic.twitter.com/uszOlbGm6P</a></p>&mdash; Android (@Android) <a href="https://twitter.com/Android/status/1168935724655218691?ref_src=twsrc%5Etfw">September 3, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

そして、ちょうど本日、Android 10に関する日本語版blogが更新されました！

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Android 10 をリリースしました。新しいジェスチャー操作やダークテーマなどの新機能に対応するための情報をまとめました。<a href="https://t.co/xLISB9mWM1">https://t.co/xLISB9mWM1</a></p>&mdash; Google Devs Japan (@googledevjp) <a href="https://twitter.com/googledevjp/status/1176678502277382144?ref_src=twsrc%5Etfw">September 25, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

：
と、おめてたい一方で、APIレベル28必須の締切が迫ってきました。

[2019 年のターゲット API レベル要件の拡大について](https://developers-jp.googleblog.com/2019/03/2019-api.html)

>
> - 2019年8月: 新しいアプリで、APIレベル28（Android 9）以上をターゲットとすることが必須になります。
> - 2019年11月: 既存のアプリのアップデートで、APIレベル28以上をターゲットとすることが必須になります。

今手持ちのAndroidアプリのAPIレベル(targetSdkVersion)は26なのですが、
せっかくAndroid 10が出たので、28をジャンプしてAPIレベル29に対応しました。
きっと2020年のオリンピックが終わったころには必須APIレベル29になってAndroidX対応必須となると思います。
手持ちのアプリはRSSリーダーアプリなのでジャンルが近ければ参考になるかもしれないです。


# 資料をそろえた

バージョン対応表は以下となります。

|名称|バージョン|targetSdkVersion|
|:--|:--|:--|
|Android O|8.0|26|
|Android O(MR1)|8.1|27|
|Android P|9.0|28|
|Android 10(Q)|10.0|29|

公式の各バージョン対応のページは以下にまとまっています。

- Android 8.1
 - [Android 8.1 の機能と API](https://developer.android.com/about/versions/oreo/android-8.1?hl=ja)

- Android 9
 - [Behavior changes: apps targeting API 28+](https://developer.android.com/about/versions/pie/android-9.0-changes-28?hl=ja)

- Android 10
 - [Welcoming Android 10!](https://android-developers.googleblog.com/2019/09/welcoming-android-10.html)
 - [ようこそ Android 10! (上記の日本語版)](https://developers-jp.googleblog.com/2019/09/android-10.html)
 - [Behavior changes: apps targeting API 29+](https://developer.android.com/about/versions/10/behavior-changes-10?hl=ja)

対応したことを以下にまとめます。

# http通信の許可(Android 9.0対応)

こちらはAndroid 9.0対応なのですが、デフォルトのネットワークセキュリティ構成がhttps通信のみ許可になりました。
自分のアプリは汎用RSSリーダーアプリなのでhttpの外部サイトに直接アクセスしており、
`usesCleartextTraffic` をtrueに指定しました。

> ネットワークセキュリティ構成
> https://developer.android.com/training/articles/security-config.html?hl=ja

targetSdkVersion:26 のデフォルト設定は以下のようになります。

```xml
    <base-config cleartextTrafficPermitted="true">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>
```

targetSdkVersion:28 のデフォルト設定は以下のようになります。

```xml
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>

```

近年はLet's Encryptの普及もありhttps通信が当たり前になっているので
通信先が決まっていてすべてhttps通信の場合は対応不要です。
一部がhttpの場合はドメイン指定 `network_security_config` でドメイン指定する方法もあります。
汎用的にURLにアクセスするブラウザアプリなどでは以下のように `AndroidManifest.xml` に平文通信使用の指定が必要になります。

```AndroidManifest.xml
    <application
        android:name=".application.MainApplication"
        android:usesCleartextTraffic="true" <!-- ここを追加 -->
        :
```

# プライバシーの変更
Android 10ではプライバシー(外部ストレージ、位置情報取得、バックグラウンドのアクティビティ起動など)周りが大きく変更になります。 自分は対応なしでしたが影響大きそうなので概要書きます。

> Privacy changes in Android 10
> https://developer.android.com/about/versions/10/privacy/changes#scoped-storage

### 外部ストレージ対応

[Manage scoped external storage access](https://developer.android.com/training/data-storage/files/external-scoped?hl=ja)

本対応は、今後targetSdkVersionに関わらずすべてのアプリで対応する必要があります。

Android 10（APIレベル29）以上を対象とするアプリには、デフォルトで外部ストレージデバイスまたはスコープストレージへのスコープアクセスが許可されます。 
以下のAPIを使用している方は今後使用できなくなるようなので注意が必要です。

- [Environment#getExternalStorageDirectory()](https://developer.android.com/reference/android/os/Environment.html#getExternalStorageDirectory)
- [Environment#getExternalStoragePublicDirectory()](https://developer.android.com/reference/android/os/Environment.html#getExternalStoragePublicDirectory)

<img width="640" alt="スクリーンショット 2019-09-27 3.13.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19692/bcd960e1-79da-9164-6956-f05d499c7e0c.png">

推奨対応として、独自データを扱う場合には `Context#getExternalFilesDir()`を、一般的なメディアを取り扱う場合は `MediaStore` 、一般的なファイルを扱う場合は `Storage Access Framework` を使用します。

一時的オプトアウト(不参加)の方法として `android:requestLegacyExternalStorage="true"` を指定する方法がありますがアンインストールまで設定が引き継がれます。

# 折りたたみ端末対応

### 動作確認方法

> Galaxy Fold
> https://www.galaxymobile.jp/galaxy-fold/

2019年9月現在はGalaxy Foldしか存在しないですがAndroid 10では折りたたみ端末に対応しています。
Android Studio 3.5ではデフォルトで7.3インチと8.0インチの仮想折リたたみ端末(Foldable)が用意されています。

<img width="480" alt="スクリーンショット 2019-09-23 16.28.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19692/34b8e13f-fe69-f418-49bd-1bd2221ad416.png">

上記でエミュレータ実行を行うと右下のボタンで折りたたみが行える様になり、動作確認を行うことができます。

<img width="320" alt="スクリーンショット 2019-09-16 11.06.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19692/b48b3bf9-1fcd-76cf-10d7-94f8d4b3dba0.png">
<img width="320" alt="スクリーンショット 2019-09-16 11.05.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19692/a394a59a-7db1-187f-989c-3f4e3599178e.png">

Android 7.0のマルチウィンドウのサポートの拡張版という印象です。
すでにChromebook用などにresizableActivity対応していたら対応が容易だと思います。
Android 8.0対応時に、縦長端末対応のために `android:maxAspectRatio` に対応していましたが
今回は折りたたみで縦長から横長になったときも考慮して `android:minAspectRatio` も設定しました。

マルチウィンドウについては後述します。

### マルチウィンドウ対応

> 折りたたみ式デバイスに対応したアプリの作成
> https://developer.android.com/preview/features/foldables?hl=ja

マルチウィンドウ対応大きく変更になったのは複数起動している時のライフサイクルです。
Android 10上でアプリが稼働する場合の、 `onResume()` メソッドと `onPause()` の通知タイミングが異なります。
アプリを複数起動している場合、再開時にフォーカスしているアプリのみでなくすべてのアクティビティが再開状態になるようです。

このため、新しいライフサイクル `onTopResumedActivityChanged(boolean)` が追加され優先度が最高のアプリを区別できます。

自分のアプリではカメラなどを扱っていないので動作確認のみだったのですが、カメラ・メディア・マイクなど使用しているアプリは注意する必要がありそうです。

# ダークテーマのAndroid 10対応

過去に[ダークモードのあれこれ](https://qiita.com/keidroid/items/920e655fa6823b533ba1)を書いたのですが、今回Android 10ではコントロールセンターからダークモード切り替えができるようになりました。

<img width="320" alt="スクリーンショット 2019-09-16 11.06.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19692/fe7b0a2f-37cf-ab7c-32b1-35fef14e7779.png">

これによりダークモードをシステムに従う`MODE_NIGHT_FOLLOW_SYSTEM` や、バッテリー状態によって切り替える `MODE_NIGHT_AUTO_BATTERY` を使用するのが良さそうです。
`Material Component v1.1.0-alpha03`では時間による切り替えは非推奨になりました。

https://medium.com/androiddevelopers/appcompat-v23-2-daynight-d10f90c83e94

> MODE_NIGHT_NO. Always use the day (light) theme.
> MODE_NIGHT_YES. Always use the night (dark) theme.
> MODE_NIGHT_FOLLOW_SYSTEM (default). 
> MODE_NIGHT_AUTO_BATTERY. ✨New in v1.1.0-alpha03.
> MODE_NIGHT_AUTO_TIME & MODE_NIGHT_AUTO. ⛔ Deprecated in v1.1.0-alpha03.

`MODE_NIGHT_FOLLOW_SYSTEM` に設定しコントロールパネルから切り替えた場合、 `Activity#recreate()` は不要でよろしくダークモード切り替えを行ってくれました。
また、ダークモードに対応する余裕がない方向け(？)に `android:forceDarkAllowed="true"` で強制的にダークモードに対応する機能も提供になりました。

# ジェスチャーナビゲーション

Android 10エミュレータで、初期設定の下部ナビゲーションバーは見慣れた3-buttonだったのですが、 `System > Gesture` で3種類の中から選べます。
Android 10ではジェスチャーナビゲーション推しのようです

<img width="320" alt="スクリーンショット 2019-09-16 11.06.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19692/990c2749-de69-875d-fd3d-334cef4162ce.png">

対応には大きく「全画面対応」「スワイプ処理のコンフリクト対応」が必要となります。
それぞれは以下の公式Mediumで触れられています。

> Gesture Navigation: Going edge-to-edge
> https://medium.com/androiddevelopers/gesture-navigation-going-edge-to-edge-812f62e4e83e

> Gesture Navigation: Handling visual overlaps
> https://medium.com/androiddevelopers/gesture-navigation-handling-visual-overlaps-4aed565c134c

### [断念] 全画面対応

結論から言うと自分は全画面対応を諦めました。
対応方法は以下の動画で詳しく説明していますが対応しなかった理由(言い訳)が二つあります。

> droidcon NYC 2017 - Becoming a master window fitter🔧
> https://youtu.be/_mGDMVRO3iE

<img width="640" alt="スクリーンショット 2019-09-16 11.06.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19692/86478bd6-bd7d-148a-554b-eb249bf4e275.png">


ひとつめは上記の自分のアプリは `DrawerLayout` と `CoordinatorLayout` で構成しており、上記キャプチャの通り対応箇所が多い点です。
ふたつめは安定版最新のAndroidXライブラリ `androidx.core:core:1.1.0` にInsetsを便利に扱うクラス `WindowInsetsCompat` がまだはいっていなかった点です。

というわけでいったん全画面は見送りました。

↓未対応だと画面上と画面下が塗りつぶされてしまうのでこれを透明にする必要があるのですが
あまりに気にならないようにも見えるので..
<img width="240" alt="スクリーンショット 2019-09-16 11.06.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19692/e6131582-4d56-415c-d2fd-1e935389c639.png">

### スワイプ処理のコンフリクト対応

Android 10ではジェスチャーナビゲーションの場合、画面横端のスワイプが `戻る処理` に、画面下端のスワイプが `ホーム処理` になります。

> Dark Theme & Gesture Navigation (Google I/O'19)
> https://youtu.be/OCHEjeLC_UY

上記の動画を見ればスワイプ処理への影響がわかります。
基本的にはAndroidXの最新コンポーネントを使用すればNavigationDrawerなどの処理はよろしくやってくれるそうです。

必須対応として、画面下にあるコンポーネントと衝突しないようにする必要があります。
自分は上記の全画面対応を断念したこともあり、対応不要でした。(動画32:00頃)

<img width="640" alt="スクリーンショット 2019-09-16 11.06.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19692/ebb93d52-e647-9fab-6aa3-c3cde61165cc.png">

また、カルーセルなどの横スクロール処理でスワイプ処理を戻るに持っていかれてしまうのを避けたいのですが、
エッジの除外処理は**しない**ことを推奨とのことです。(動画39:00頃)
若干不安ですが従うことにしました。

<img width="640" alt="スクリーンショット 2019-09-16 11.06.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19692/71201beb-4e22-9ee5-170c-96285f568ee2.png">

# Robolectricのテストがコケる件

targetSdkVerison:29にすると、最新のRobolectric4.3で以下のエラーが出ました。

```
java.lang.IllegalArgumentException: API level 29 is not available
```

リリースノートを見るとAndroid 10にはJava9が必須要件のようです。
https://github.com/robolectric/robolectric/releases

> Robolectric 4.3
> :
> Android SDK 28 (includes support for testing against SDKs from 16 on) and Android Q Beta 2.
> Running tests on Android Q requires Java 9.
> Legacy resources mode will not be supported for Android Q.

また、targetSdkVersion:28以上で動作させると
Looper周りの挙動が変わり、今までのテストが動作しなくなる恐れがあります。


自分の場合は新しいLooperModeに対応するため、対象クラスに `@LooperMode(PAUSED)` アノテーションを付与しました。
また、 Java9対応はしんどいので、 `src/test/resource/robolectric.properties` でSDK指定を行い28で動作させることにしました。

``` robolectric.properties
# sdk29 required Java9
sdk = 28
```

Looper周りの挙動を変えたくない場合は
Robolectric 4.2以下を使用するかsdkを27以下に指定すると今までと同じ挙動になります。

```robolectric.properties
# sdk29 required Java9
# sdk28 required @LooperMode(PAUSED)
sdk = 27
```
詳細については下記Robolectric公式サイトページに載っています。

> Improving Robolectric's Looper simulation
> http://robolectric.org/blog/2019/06/04/paused-looper/

# 感想

今回の対応は、ジェスチャーナビゲーション対応があまり変化ないように見えますが対応がけっこうありそうです。
そもそもジェスチャーナビゲーションありきでアプリを作ると画面の再設計が必要になりそうです。

昔のAndroid端末には画面下にいろんなボタンとくるくる回せるボールがついていて、それはそれは便利だったんじゃよ。

<img width="480" alt="スクリーンショット 2019-09-16 11.06.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19692/82590907-fd15-0c51-4b06-1ccfc4f9a699.jpeg">

おめでとうAndroid 10!
おわり。