sky’s 雑記

主にAndroidとサーバーサイドの技術について記事を書きます

Github Actionsでワンショットタスクを実行する

最近はサーバーサイドの仕事が増えているので、 記事もサーバーサイドよりの技術が増えるかもしれません。

ECS Fargateにアプリケーションをデプロイする際、 あわせてDBスキーマの更新マイグレーションを行いたいユースケースについてです。

結論

steps:
      - uses: actions/checkout@v2

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{ secrets.AWS_IAM_ROLE_ARN }}
          aws-region: ap-northeast-1
          role-session-name: GithubActionDeployment

      - name: Install AWS CLI
        shell: bash
        run: |
          if ! [ -x "$(command -v aws)" ]; then
            curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
            unzip awscliv2.zip
            sudo ./aws/install --update
            aws --version
          fi

      - name: ECS run migration task
        env:
          task_definition: # task definition arn
          cluster: # cluster
          vpc_configuration: # vpn conf
          container_overrides: '{"containerOverrides": [{"name": "web", "command": ["php", "artisan", "migrate"] }]}'
        run: |
          task_arn=$(aws ecs run-task \
            --region ap-northeast-1 \
            --launch-type FARGATE \
            --cluster '${{ env.cluster }}' \
            --network-configuration 'awsvpcConfiguration=${{ env.vpc_configuration }}' \
            --task-definition '${{ env.task_definition}}' \
            --overrides '${{ env.container_overrides }}' \
            --query tasks[0].taskArn --output text)

背景

ワンショットタスク

DBマイグレーションのようなコマンド実行後に正常終了するECSタスクのことを指しています。ECSタスクを構成するessentialなコンテナが全て終了するとECSタスクも終了するため、起動後正常終了するDockerコンテナと考えて良いです。 通常のECSタスクを構成するコンテナはCMD ["nginx", "-g", "daemon off;"] のようにフォアグラウンドで起動し続けるため意識することがありませんでしたが、バックグラウンドで起動するプロセスや起動後終了するプロセスの場合CMD実行後コンテナは正常終了します。 よってGithub ActionsでDBマイグレーションのようなワンショットタスクを実行したい場合コンテナのCMDにマイグレーションコマンドを指定したECSタスクを作成すれば良いことになります。

方法

run-task

aws cliのrun-taskコマンド[1]のoverridesオプションでCMDなどコンテナの定義を上書きすることができるのでこれを採用します。

{
  "containerOverrides": [
    {
      "name": "string",
      "command": ["string", ...],
      "environment": [
        {
          "name": "string",
          "value": "string"
        }
        ...
      ],
      "environmentFiles": [
        {
          "value": "string",
          "type": "s3"
        }
        ...
      ],
      "cpu": integer,
      "memory": integer,
      "memoryReservation": integer,
      "resourceRequirements": [
        {
          "value": "string",
          "type": "GPU"|"InferenceAccelerator"
        }
        ...
      ]
    }
    ...
  ],
  "cpu": "string",
  "inferenceAcceleratorOverrides": [
    {
      "deviceName": "string",
      "deviceType": "string"
    }
    ...
  ],
  "executionRoleArn": "string",
  "memory": "string",
  "taskRoleArn": "string",
  "ephemeralStorage": {
    "sizeInGiB": integer
  }
}

例としてLaravelのmigrationをワンショットで実行することを考えるとコマンドは以下のようになります。

aws ecs run-task \
            --region ap-northeast-1 \
            --launch-type FARGATE \
            --cluster '${{ env.cluster }}' \
            --network-configuration 'awsvpcConfiguration=${{ env.vpc_configuration }}' \
            --task-definition '${{ env.task_definition}}' \
            --overrides '{"containerOverrides": [{"name": "web", "command": ["php", "artisan", "migrate"] }]}' \
            --query tasks[0].taskArn --output text)

以上の内容をGithub Actionsに落とすと冒頭のようなworkflowになります。

その他検討した方法

専用のtaskを定義する

CMDのみが異なるサーバーを起動しているタスクとほぼ同一のタスクを定義する必要があり冗長なので却下しました。

ENTRYPOINT

CMDではなくENTRYPOINTであればコンテナ立ち上げ時に複数命令を実行可能。ただフォアグラウンド命令の前にDBマイグレーションの実行を挟むことになるので、ワンショットにはならず柔軟性に欠けるため却下しました。

execute-command

execute-command[2]でecsタスクに対してコマンドを実行できるようになったのでそれを利用する方法です。やっていることはCMDをoverrideしてrun-taskすることと変わらないはずなので今のところ要件は満たせると思っています。

aws ecs execute-command \
--cluster '${{ env.cluster }}'  \
--task '${{ env.task_definition}}' \
--container web \
--interactive \
--command "php artisan migrate"

amazon-ecs-deploy-task-definition

通常のタスクのデプロイに使っているaws公式のworkflowを利用する方法。現在はone-offタスクの実行は未サポートらしい[3]、 上述の他の方法で対応可能なので今後もサポートはされない気がしています。

ref

[1] run-task — AWS CLI 1.25.58 Command Reference

[2] デバッグ用にAmazon ECS Exec を使用 - Amazon ECS

[3] run one-off task (e.g. migrations) · Issue #54 · aws-actions/amazon-ecs-deploy-task-definition · GitHub

英語の勉強を始める

新年度から転職して英語を使う環境で働き始めた、 仕事で不自由があるといけないので本腰入れて勉強しようと思う。

ちなみに英語力は10年以上前にTOEIC800ちょっと取った程度です。

スピーキング

当初の想定どおりではあるが全然話せない。課題意識としては3つある。 - アウトプットする速度 アウトプットする過程に頭の中で文章を組み立てる作業があるので、あらかじめ喋る内容を決めていないものについてはなかなか言葉が出てこない。使いやすいフレーズを徐々に増やしていくしかないのかなと思って暗記ゲーに取り組もうとしているが正しいのか不明。 - 文法 主語が抜けるとか5W1Hが適切でないとか時制が正しくないとか冠詞が抜けるとかそういう類のもの。これについては転職して2日である程度改善している気がするので様子見。 - 苦手意識 喋るのを躊躇ってしまう現象の原因。バイネームで話を振られたら無理やり喋るんだけどそうでない場合はどうしても躊躇ってしまう。場数を踏んで慣れるしかないかなと思っている。

リスニング

リスニングはそこまで苦手意識はないつもりだったんだが、厳し目に振り返ってみると日常会話だと7割くらい、オンボーディングのセッションだと5割くらいしか理解できていない気がする。特にインド英語のリスニングが壊滅的で3割くらいしか理解できない。ネイティブの人もたまにsorry?と聞き返しているので自分にはなおさらきついんだろうけどインド人比率が高いのでどうにかしたいところではある。インド英語最適はしたくないのでリスニング力の底上げで対応したいところではあるが悩ましい。

以上、まとまりのない感想記事でした。

Androidや研究の話もちょっと落ち着いたら再開します。

LiveDataのこれまで

Coroutinesのアップデートが盛んに行われており、昨今のAndroid界隈でLiveDataからFlowへ乗り換える機運が高まっているように感じます。今一度Flowへの乗り換えをスムーズに行うためにもLiveDataが発表された経緯など歴史を振り返りたいと思います。

Architecture components - introduction (Google I/O '17)

Google I/O 17ではArchitecture Componentsが発表された。LifecycleOwner/LiveData/ViewModelあたりのComponentはこの発表が初出。onStartやonStopなどActivity/Fragmentが持つ複雑なライフサイクルに対応すること、複数のライフサイクルでリソースをシェアすることを目的にLifecycle AwareなLiveDataが発表された。 www.youtube.com

Fun with LiveData (Android Dev Summit '18)

LiveDataについてIt's desgined for a very specific use case と言及されている。LiveDataを使わないケースとして以下3つがあげられていた。

1. 多くのオペレータやストリームが必要な場合 => Rx
2. データの操作にUIやlifecycleが絡まない場合 => callback
3. データの更新がないone-shotな操作 => coroutines

Google I/O 17で言及されていた通りでAndroid固有の複雑なライフサイクルに対応するという狭い目的でLiveDataが発表されたことが強調されていた。ストリーム処理の用途でLiveDataを利用したいという要望をよく見かけるがそもそも用途が異なるということがわかる。

またmap/switchMap/MediatorLiveDataあたりはこの発表が初出。

www.youtube.com

Android Jetpack: What's new in Architecture Components (Google I/O '18)

Android Jetpackが発表された。LiveDataのDataBindingはこの発表が初出。

www.youtube.com

LiveData with Coroutines and Flow (Android Dev Summit '19)

LiveDataとCoroutineの連携について発表された。主題はFragment,Activity,ViewModel,Applicationそれぞれが持つスコープでいかに操作をキャンセルするかであり、Coroutineのスコープについてがメイントピックになっていた。LiveDataはリアクティブストリームビルダーとして設計されたわけではないとここでも強調されており、 実装についてはViewModel,Repository,DataSourceの3層でLiveData,Flowそれぞれを利用した場合のPro/Conが述べられている。特にデータの操作が複雑になりやすいRepository層DataSource層でCoroutine Flowが有効であり、ViewModel層では画面ロード時に1度だけ呼び出すようなone-shotな操作にはLiveData Builderを用い、複数回データが更新されるような処理の場合はRepository層ではFlowを採用したほうが良いと言われていた。

www.youtube.com

まとめ

  • LiveDataの用途は限定的であり、特徴はLifecycle Awareである
  • 複雑なストリーム処理用途ではないためRxにあるようなオペレータは実装されていない
  • RxがオーバースペックなアプリではLiveDataで仕様を実現できたが本来Rxを代替するものではない
  • Coroutine FlowはRxを代替するものだがLiveDataを代替するものではない

kotlinx-coroutines-testのrunBlockingTestについて

前回に引き続きCoroutinesの単体テストの話です。

kotlinx-coroutines-testのDispatchers.setMainについて - sky’s 雑記

今回はrunBlockingTest[1]について取り扱いたいと思います。

⚠理解が曖昧な状態で記述している可能性があります、間違いがあれば訂正お願いします。

環境

implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.20"

testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.9"

runBlockingTestとは

kotlinx-coroutines-testに含まれるAPIでrunBlocking同様に現在のスレッドをブロックしてコルーチンを起動することが可能。

上記例ではrunBlockingに渡しているブロックで2度コルーチンが起動されておりそれぞれで10secディレイを入れているため以下のような出力になる。

次にrunBlockingをrunBlockingTestに変更した場合

delayが即時実行され処理が完了していることがわかる。

runBlockingTestの用途

runBlockingTestが導入された背景はTesting Coroutines on Android (Android Dev Summit '19)[2]とKotlinConf 2019: Testing with Coroutines by Sean McQuillan[3]が詳しい。Android Dev Summitでは良い単体テストは速く(fast)、信頼性があり(reliable)、独立している(isolated)と言われており、runBlockingTestは特に速さと信頼性の観点で導入されたように思う。

ではrunBlockingTestを利用したいのはどういう場合だろうか。

普段ViewModelが呼び出すRepositoryのsuspend関数は実行結果をmockすることが多くあまりピンと来なかったが、KotlinConfのプレゼンテーションではsuspend関数のタイムアウト処理をテストするケースが例として挙げられていた。例えば以下のようなタイムアウトが設定されたsuspend関数があった場合、これをrunBlockingで実行すると5秒待つ必要がある。

一方同じ処理をrunBlockingTestで書くと以下のようになり即座にテストが通る、またDelayControllerによりCoroutineDispatcherのvirtual timeを変更することでtimeOutが絡むテストが容易に記述できる。

補足

ここまでrunBlockingTestの用途について書いてみたが現状ExperimentalCoroutinesApiであることに加えて、いくつかクリティカルに思えるissueが上がっていたため導入については要検討という印象である。続報に期待したい。

AbstractMethodError when use withTimeout

AbstractMethodError when use withTimeout · Issue #2307 · Kotlin/kotlinx.coroutines · GitHub

2021/01/18時点で最新版1.4.2のkotlinx-coroutines-testを利用すると発生するエラー

Provided example test for withTimeout fails

Provided example test for withTimeout fails · Issue #1390 · Kotlin/kotlinx.coroutines · GitHub

ReadMeのExample[5]をそのまま書くと発生するエラー

Replace TimeoutCancellationException with TimeoutException

上述のsuspend fun foo でTimeoutExceptionをそのままthrowせずにtry/catchしてFooExceptionを投げ直している理由。TimeoutCancellationException is CancellationException, thus is never reported. とのこと。

Replace TimeoutCancellationException with TimeoutException · Issue #1374 · Kotlin/kotlinx.coroutines · GitHub

Use Kotlin Coroutines in your Android App

Use Kotlin Coroutines in your Android App  |  Android Developers

runBlockingTest is experimental, and currently has a bug that makes it fail the test if a coroutine switches to a dispatcher that executes a coroutine on another thread. The final stable is not expected to have this bug.

参考

[1] runBlockingTest - kotlinx-coroutines-test

[2]

www.youtube.com

[3]

www.youtube.com

[4] kotlinx.coroutines/kotlinx-coroutines-test at master · Kotlin/kotlinx.coroutines · GitHub

kotlinx-coroutines-testのDispatchers.setMainについて

Coroutinesの単体テストの話です、マルチスレッドな関数の単体テストを書いていると理解が曖昧なところがまだあるので改めて調べることにします。趣旨としてはCoroutineのテストを書くときに@BeforeでDispatchers.setMainを何故やる必要があるのかと何が行われているのかです。長くなりそうなので一旦記事にします。

TL;DL

  • Androidコンテキストを排除してテストをするためにsetMainでDispatcherを差し替える必要がある
  • 利用するMainDispatcherはloadPriorityで決まる

環境

testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2"

背景

CoroutineのDispatchers.Mainは内部的にgetMainLooper()を呼び出している[1]。Looperはandroid.osパッケージに含まれており、UIスレッドで起動することを考えれば当たり前ではあるのだが、つまるところ単体テストではDispatchers.Mainは使えない。なのでviewModelScopelaunch(context = Dispatchers.Main)のようにCoroutineを起動する処理のテストでは何らかの方法でDispatchers.Mainを差し替える必要がある。この問題に対処するためにkotlinx-coroutines-testではDispatchers.Mainの中身を差し替えられるようになっている。

setMain

setMainで行う処理は以下のようになっておりシンプル

TestDispatchers.kt[2]

@ExperimentalCoroutinesApi
public fun Dispatchers.setMain(dispatcher: CoroutineDispatcher) {
    require(dispatcher !is TestMainDispatcher) { "Dispatchers.setMain(Dispatchers.Main) is prohibited, probably Dispatchers.resetMain() should be used instead" }
    val mainDispatcher = Dispatchers.Main
    require(mainDispatcher is TestMainDispatcher) { "TestMainDispatcher is not set as main dispatcher, have $mainDispatcher instead." }
    mainDispatcher.setDispatcher(dispatcher)
}

mainDispatcherに対してTestMainDispatcherの型チェックが入っている、なのでテストではTestDispatcherを使っていることになるのだが、mainDispatcherの型がMainCoroutineDispatcherにしか見えないのでまずはここを追いかけることにする。

Dispatchers.kt[3]

 @JvmStatic
    public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher

MainDispatcherLoader.kt[4]

 val factories = if (FAST_SERVICE_LOADER_ENABLED) {
                FastServiceLoader.loadMainDispatcherFactory()
            } else {
                // We are explicitly using the
                // `ServiceLoader.load(MyClass::class.java, MyClass::class.java.classLoader).iterator()`
                // form of the ServiceLoader call to enable R8 optimization when compiled on Android.
                ServiceLoader.load(
                        MainDispatcherFactory::class.java,
                        MainDispatcherFactory::class.java.classLoader
                ).iterator().asSequence().toList()
            }

FastServiceLoader.kt[5]

private fun <S> load(service: Class<S>, loader: ClassLoader): List<S> {
        return try {
            loadProviders(service, loader)
        } catch (e: Throwable) {
            // Fallback to default service loader
            ServiceLoader.load(service, loader).toList()
        }
    }

MainDispatcherLoader.kt[4]

            factories.maxBy { it.loadPriority }?.tryCreateDispatcher(factories)

ということでloadPriorityが最大のMainDispatcherFactoryでMainCoroutineDispatcherを生成することになる。factoriesの中身はFastServiceLoaderで設定されていてTestMainDispatcherFactoryAndroidDispatcherFactory である。それぞれのpriorityは

TestMainDispatcherFactory[6]

internal class TestMainDispatcherFactory : MainDispatcherFactory {
    override val loadPriority: Int
        get() = Int.MAX_VALUE
}

AndroidDispatcherFactory[1]

internal class AndroidDispatcherFactory : MainDispatcherFactory {
    override val loadPriority: Int
        get() = Int.MAX_VALUE / 2
}

となっておりテスト時にはTestMainDispatcherが使用される。

参考

[1] HandlerDispatcher

[2] TestDispatchers

[3] Dispatchers

[4] MainDispatcherLoader

[5] FastServiceLoader

[6] TestMainDispatcherFactory

Kotlin 1.4.20-M2でDeprecatedとなったKotlin Android Extensionsを弔う

表題の通りKotlin 1.4.20-M2でKotlin Android ExtensionsがDeprecatedとなりました、個人的には好きな技術だったので弔いがてら記事にしたいと思います。

概要

Kotlin Android Extensions[1]はKotlin用のView参照機構で、findViewByIdによるViewの参照の問題を解決するために導入された。手軽に導入できシンタックスもシンプルなので採用しているプロジェクトも多いように思うが、後述するいくつかの問題によりAndroid公式からはBest Practiceとはされていなかった。

このような背景からView参照機構として長らくfindViewById,ButterKnife,Kotlin Android Extensionsといった技術が存在する状況が続いてたが(DataBindingは除く)、2019年のGoogle I/OにてViewBinding[2]が発表されView参照機構のBest Practice問題は一様の収束に至った。

これを受けて昨日リリースされたKotlin 1.4.20-M2[3]では遂にKT-42121 Deprecate Kotlin Android Extensions compiler plugin となり、今後多くのAndroidプロジェクトのView参照機構はViewBindingへmigrateされていくことになると思う。

Why kotlinx synthetic is no longer a recommended practice

redditにてなぜkotlinx syntheticはrecommended practiceではないのか、というスレッドで議論がされておりその中に以下の回答がされている。[4]

We’ve been shifting away from them (e.g. we don’t teach them in the Udacity course) because they expose a global namespace of ids that’s unrelated to the layout that’s actually inflated with no checks against invalid lookups, are Kotlin only, and don't expose nullability when views are only present in some configuration. All together, these issues cause the API to increase number of crashes for Android apps.

これによるとKotlin Android Extensionsの問題としてglobal namespace of ids, Kotlin only, don't expose nullabilityがあげられている。

global namespace of ids

これはKotlin Android Extensionsを使ったことがある人なら一度は経験したことはあると思う。Kotlin Android Extensionsのlayout id定義はグローバルに存在しlayout idのViewをimportして参照したとしてもコンパイルエラーで気がつくことができない。

Kotlin only

当然Kotlinでしか利用できないためJavaで記述されたActivityなどでは引き続きButterKnifeやfindViewByIdを使う必要があった。

don't expose nullability

when views are only present in some configuration、例えば以下のようにportrait,landscapeで異なるlayoutを定義していた場合を考えると

activity_kotlin_android_extensions.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".KotlinAndroidExtensionsActivity">

</FrameLayout>

land/activity_kotlin_android_extensions.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".KotlinAndroidExtensionsActivity">

    <TextView
        android:id="@+id/label_kotlin_android_extensions_land"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</FrameLayout>

この状態で例えばActivityでTextViewを参照しようとするとwarningが発生するもののクラッシュするまで問題に気づくことはできない。

...
label_kotlin_android_extensions.text = "bar" // warning: The resource is missing in some of layout versions
...

一方ViewBindingを利用して参照する場合Viewはnullableとなるためコンパイルエラーとなる。

...
binding.labelKotlinAndroidExtensions.text // only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type TextView?
...

終わりに

以上Kotlin Android ExtensionsがDeprecatedになった経緯を眺めた。ViewBindingはAndroid Studio 3.6 Canary 11以降から使用可能でActivity/Fragment単位で段階的な導入も可能にみえる。自分が携わっているプロダクトでは主にDataBindingを使用しているため移行する必要はあまりなさそうだが、一部ButterKnifeによる記述が残っているのでそのあたりから移行を進めていこうと思う。

※ proandroiddevのほうでもKotlin Android ExtensionsのDeprecatedの件に触れていた[5]

参考

[1] Android KTX  |  Android Developers

[2] ビュー バインディング  |  Android デベロッパー  |  Android Developers

[3] Release Kotlin 1.4.20-M2 · JetBrains/kotlin · GitHub

[4] Why kotlinx synthetic is no longer a recommended practice : androiddev

[5] Migrating the deprecated Kotlin Android Extensions compiler plugin to ViewBinding | by Ahmad El-Melegy | Oct, 2020 | ProAndroidDev

AndroidにおけるJSR-310実装

ちょっとツイッターに流れてきた情報を見て気になったので改めて調べてみました。

TL;DL

  • ThreeTenABPでJSR-310を利用する場合は環境によらず同じtz databaseを参照する(はず
  • desugaringでJSR-310を利用する場合は実行環境により異なるtz databaseを参照する(はず

java.util.Date と JSR-310

java.util.Dateが使い勝手が悪いことは良く知られており、Java SE 8ではjava.util.Dateを置き換えるDate and Time APIが追加された。このAPIはJSR-310として標準化されており、インターフェースがデベロッパーフレンドリーであるだけでなくインスタンスがイミュータブル、スレッドセーフといった機能的な優位性を持つ。[1]

AndroidでのJSR-310実装方法の変遷

前述の通りJSR-310はJava SE 8からの機能である。Android Gradle plugin4.0.0以降でJSR-310のdesugaring対応がされたが[2]、それまではThreeTenBPやThreeTenABP[3]のようなJava6,7向けにJSR-310をバックポートしたライブラリを利用する必要があった。またThreeTenBPはJSR-310と完全に同じ振る舞いをするわけではないという点にも注意が必要でタイムゾーン(tz database)は独自に実装されておりJREではなくThreeTenBP自体にパッケージされたtz databaseを参照するため、tz databaseの更新にはThreeTenBPの更新が必要であった。[4][5]

tz databaseについて

世界各地域の標準時(time zone、タイムゾーン)情報をまとめたデータ群のことである、以下のように標準時からの差をタイムゾーンとオフセットにより定義する。厄介なのが世界各国でこの定義の更新は割と頻繁に行われているようで、年に数回はtz databaseの更新が行われていることだ。[6]

# Zone  NAME            GMTOFF  RULES   FORMAT  [UNTIL]
Zone    Asia/Tokyo      9:18:59 -       LMT     1887 Dec 31 15:00u
                        9:00    -       JST     1896
                        9:00    -       CJT     1938
                        9:00    Japan   J%sT

ZonedDateTime: 2020-08-01 00:00:00 (Asia/Tokyo) OffsetDateTime: 2020-08-01T00:00:00+09:00

Androidにおけるtz database

Android Gradle plugin4.0.0以降でJSR-310のdesugaring対応がされたと述べたが、これまでThreeTenABPで利用していたJSR-310に見えるものとdesugaringで利用可能となるJSR-310はtz databaseの取り扱いなど細かい部分で実は挙動が異なる。ThreeTenABPが参照するtz databaseはパッケージングされたものであるのに対してdesugaringしたJSR-310が参照するのはJava実行環境のtz databaseとなる。Android10以降の端末でいうと/system/apex/com.google.android.tzdata.apex に含まれているtz databaseを参照する。

またAndroid10以降でAPEXベースのモジュール更新メカニズム[7]が採用されたようでtz databaseの更新方法が変わっているようだが、Android9以前のOEMがAPKベースのメカニズムでtz databaseを端末にプッシュしていた方法と比べて今回の件について本質的な差異はないように思う。

まとめ

JSR-310で実装するといってもThreeTenABPを使うかdesugaringを使うかで参照するtz databaseは異なる。今後はdesguaringでJSR-310を利用するほうに流れていくと思うが、その場合実行環境によって異なるバージョンのtz databaseを参照することは避けられないのではないだろうか。

参考

[1] 必修! Date and Time API──Java SE 8の新日時APIの基本を学ぶ - builder by ZDNet Japan

[2] Use Java 8 language features and APIs  |  Android Developers

[3] GitHub - JakeWharton/ThreeTenABP: An adaptation of the JSR-310 backport for Android.

[4] ThreeTen Extra と ThreeTen Backport (ja) - notepad

[5] TzdbZoneRulesProvider (ThreeTen backport 1.4.5 API)

[6] Timezone Data Versions in the JRE Software

[7] Time Zone Data  |  Android オープンソース プロジェクト  |  Android Open Source Project