nashcft's blog

時々何か書く。

CircleCI to GitHub Actions 移行日記

追記 (2020-06-13)

この件その後ログを精査してみたら、メモリの問題じゃなくて実行しているテストの1つにあった非同期処理の実装がまずくて処理が迷子になったまま帰ってこないのが原因だった。

追記終わり

職場の Android アプリのCIに CircleCI を使っていたのだけど、古いプランのままでコンテナのRAMが4GBの medium しか使えないのと、アプリの規模が大きくなりつつあるために色々対策をしてもOOMで落ちることがままあり、幾らかお話をした結果として現状簡素なCIしか無いし欲しいのは十分なRAMくらいなので CircleCI のプランを現行の performance plan にするくらいなら GitHub Actions の方がコスパが良いということで移行をしている。

先にビルドがより不安定な project から移行をして、そちらは問題なく移行できたのだけど、最も安定していた project を移行した際に gradle task の実行中に特に中断されるでもなく応答が返ってこなくなるという挙動に遭遇した。よくあるCI用の実行オプションを足しても特に改善される様子がなかったので debug log を出力させてみたところ、 task の実行途中で空き容量を超えた量のメモリ確保の要求がきて、解放しようとしたが殆どもしくは全く空けられず、延々と request が飛んでくるだけといった状況になってることがわかった。

関連する log は以下のような感じ:

[DEBUG] [org.gradle.process.internal.health.memory.DefaultMemoryManager] 728290099 memory requested, 429182976 free
[DEBUG] [org.gradle.workers.internal.WorkerDaemonExpiration] Will attempt to release 694 of memory
[DEBUG] [org.gradle.process.internal.health.memory.DefaultMemoryManager] 728290099 memory requested, 0 released, 429182976 free
[DEBUG] [org.gradle.cache.internal.DefaultFileLockManager] Waiting to acquire shared lock on daemon addresses registry.
[DEBUG] [org.gradle.cache.internal.DefaultFileLockManager] Lock acquired on daemon addresses registry.
[DEBUG] [org.gradle.cache.internal.DefaultFileLockManager] Releasing lock on daemon addresses registry.
[DEBUG] [org.gradle.cache.internal.DefaultFileLockManager] Waiting to acquire shared lock on daemon addresses registry.
[DEBUG] [org.gradle.cache.internal.DefaultFileLockManager] Lock acquired on daemon addresses registry.
[DEBUG] [org.gradle.cache.internal.DefaultFileLockManager] Releasing lock on daemon addresses registry.
[DEBUG] [org.gradle.process.internal.health.memory.DefaultMemoryManager] 728290099 memory requested, 429182976 free
... (以下繰り返し)

Gradle task 実行時のオプションは以下の通り:

 -Dorg.gradle.jvmargs="-Xmx5120m -XX:+HeapDumpOnOutOfMemoryError" -Dorg.gradle.workers.max=2 -Dorg.gradle.daemon=false -Dkotlin.incremental=false

要求されるメモリの量は常に同じなのでずっと同じ要求が繰り返されているんだろうなと思うのだけど、何のために700MB程度確保しようとしているのか、何故できてもほんの少ししか解放できないのかなどわからないことばかりだし、じゃあアプリの規模に対して -Xmx が小さすぎるのかというと見ての通り少なくない、というかむしろかなり多い方だし、大きくなりつつあるとはいえ特別大規模なアプリではないのでカツカツということはないと思っているので、今の自分の gradle に対する知識ではお手上げだなあとなっている。あとこの gradle task が進まなくなる挙動は必ず発生するわけではなく時々完走するので、何か変更を入れたときにたまたまビルドが通るとそこに原因を求めてしまいがちで調査がなかなか進まずもどかしい。

Groupie に View Binding サポートが追加された

先月出してた groupie-viewbinding を追加する pull request が今日 merge された。

github.com

早速これの入った v2.8.0 のリリースもされた模様。

Release v2.8.0 · lisawray/groupie · GitHub

ものとしては groupie-databinding ほとんどそのままという感じで、型パラメータが ViewBinding になっているのと、View Binding には DataBindingUtil#bind みたいな任意の binding class のインスタンスを生成する方法がないのでそれについては BindableItem#initializeViewBinding を override して自分で書いてね、というところがAPI上の差異になる。

簡単な例としては以下のような感じ:

class MyItem(private val data: MyData) : BindableItem<MyItemBinding>() {

  override fun getLayout(): Int = R.layout.my_item

  override fun initializeViewBinding(view: View): MyItemBinding =
      MyItemBinding.bind(view)

  override fun bind(viewBinding: MyItemBinding, position: Int) {
    // ...
  }
}

また、プロジェクトのAGPが3.6.0以上だと、 以前ブログで書いた通り ViewDataBindingViewBinding を implement するようになったので Data Binding も扱うことができ、 Data Binding と View Binding 両方使う場合は groupie-viewbinding だけ依存を追加すればよくなっている。

ただこれまでに groupie-databinding を使ったことがある人には groupie-viewbinding で Data Binding を扱う時にちょっと注意点があって、groupie-databindingBindableItem は ViewHolder が bind される時に内部で ViewDataBiding#executePendingBindings を実行してくれていたのだけど、 groupie-viewbinding のはそれがなくなっている。なので binding class に対応する layout が持つ変数や observable object を変更することで view の更新を行う場合には、 override する bind の実装の中で自分で executePendingBindings を実行するのを忘れないようにしなければならない。 groupie-databinding から groupie-viewbinding に移行する場合は既存の BindableItem を継承した class 達にはすべて initializeViewBinding の override をして回る必要があるので、その時に確認を忘れないようにしてほしい。

developer.android.com

stackoverflow.com

あと、今回の groupie-viewbinding の追加で groupie-databinding が deprecated になった。近い内にメンテも止める予定だそうだ。まあ確かに groupie-viewbinding が Data Binding も扱えるのでそっちに寄せてしまおうというのはわかるのだけど、新規はともかく groupie-databinding からの移行となるとユーザにはやや不便を強いることになり、その点については心残りがある。

まとめ

  • View Binding サポートの groupie-viewbinding が追加された。Data Binding とも使える
  • groupie-databinding が deprecated になった。メンテも止まる予定
  • (groupie-databinding から移行するユーザ向け) groupie-viewbinding で Data Binding を扱う場合の変更ポイント:
    • Binding class のインスタンス生成のために initializeViewBinding が必要
    • bind の実装の中で layout に持たせた変数や observable objects を経由して view の更新を行っている場合は、最後に executePendingBindings を実行する

Android: Data Binding 3.6.0 から ViewDataBinding の定義が変わった

今日 Android Studio 3.6.0 stable がリリースされた。

android-developers.googleblog.com

3.6 から導入された機能の一つに view binding という、data binding の layout 中の要素にアクセスする機能だけを抜き出したようなものがある。

developer.android.com

機能の詳細については公式ドキュメントを見てもらうとして、この機能で data binding の時のように生成される Binding クラスは ViewBinding という interface を実装している。

/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.viewbinding;

import android.view.View;
import androidx.annotation.NonNull;

/** A type which binds the views in a layout XML to fields. */
public interface ViewBinding {
    /**
     * Returns the outermost {@link View} in the associated layout file. If this binding is for a
     * {@code <merge>} layout, this will return the first view inside of the merge tag.
     */
    @NonNull
    View getRoot();
}

(from: AndroidX Tech: Source Code for ViewBinding.java)

この ViewBinding だが、3.6.0 から data binding で生成される Binding クラスの親クラスである ViewDataBinding もこれを実装するようになっている。

3.5 まで (3.5.3): AndroidX Tech: Source Code for ViewDataBinding.java

public abstract class ViewDataBinding extends BaseObservable

3.6 から (3.6.0): AndroidX Tech: Source Code for ViewDataBinding.java

public abstract class ViewDataBinding extends BaseObservable implements ViewBinding

つまり、 ViewBindingViewDataBinding, それと data binding や view binding で生成された Binding クラスの関係は以下のようになる。

ViewBinding
 |
 + - < View binding の機能で生成された Binding クラス >
 |
 + - ViewDataBinding
        |
        + - < Data binding の機能で生成された Binding クラス >

それが何か、というと、 <T extends ViewBinding> (Kotlin だと <T: ViewBinding>) みたいな型パラメータを定義すると view binding で生成されたクラスだけではなく data binding で生成されたクラスも適用可能になるということで、view binding と data binding は1つのプロジェクトに混在させることができるので、ViewBinding に関する実装を書いた時はそれに data binding の生成クラスを放り込んでも問題ないか考慮しておいた方が良いよ、という話。

追記

DroidKaigi 2020 アプリ開発 contribute の思い出

私が Android アプリ開発に携わるようになったのが2017年後半からで、それから迎えた DroidKaigi は 2018, 2019, 2020 と今年で3度目になる。今年は残念ながら中止になってしまった*1けど、自分にとっては公式アプリに初めて contribute した年でもあるので、そのことについての振り返りを残しておこうと思う。

DroidKaigi 2020 公式アプリ

github.com

DroidKaigi では Play Store を見る限り2016からイベントのスケジュールアプリを毎年開発していて、大体開催1ヶ月前くらいにソースコードGitHub で公開され、オープンソースプロジェクトとして開発に参加できるようになる。2018, 2019の時はコードを眺めるくらいしかできなかったのだけど、今年はそろそろ contribute してみたいなと思っていたところに偶然いい感じの issue が流れてきたので contribute することができた。

送った pull request

今回送ったのは5つ。

github.com

github.com

github.com

github.com

github.com

以下それぞれの pull request について簡単にまとめる。

Automate Dagger injection & Use Fragment LayoutId constructor #618

最初に取り組んだもの。Activity や Fragment に対する injection を DaggerAppCompatActivityDaggerFragment を使うのではなく、architecture-components-samples にある以下の実装を使った方法に切り替えて、そのついでに Fragment の View 生成を androidx.fragment の 1.1.0 から導入された LayoutId constructor を使ってコンストラクタに layout の recource ID を渡す方法に変えたよ、という変更。

github.com

AppInjector は、大雑把にいうと Activity や Fragment に injection 対象として指定した interface (ここでは HasAndroidInjectorInjectable) が実装されていたら injection を行う、という実装をした LifecycleCallbacks を Application や 各 Activity (の FragmentManager) に仕込むというもの。利用する側は AppInjector#initialize でApplication に対して ActivityLifecycleCallbacks を登録するだけで良く、 FragmentLifecycleCallbacks の登録は ActivityLifecycleCallbacks の中で行ってくれる。

実は会社で触っているアプリで AppInjector を使っていて、使い方や機構などをそれなりに把握していたので、Dagger 関連の変更とはいえ私にとってはそこまで難しいものではなくラッキーなタスクだった。これのおかげで今回 contribute ができたといってもいいかもしれない。Fragment の対応は、issue を見つけたときの twitter 上での会話とか過去のコミットを見た感じこれを通して本当にやりたかったことはこっちかなって思ってついでに変更した。これは pull request 分けても良かったなーと思っている。

余談だが最近 DaggerAppCompatActivityDaggerFragment でもコンストラクタに layout ID を与えて View の生成をできるようにする pull request が merge されたので、次のバージョンで AppInjector を導入しなくても各 Activity/Fragment で injection の処理を記述せず、かつ LayoutId constructor の機能を使えるようになるはず。

github.com

そういえば AppInjector にライセンス表記とかのコメントを足すのを忘れている...

Groupie 2.7.2 #677

Groupie のバージョンを上げること自体は実際は手段の方で、目的は 2.6.0 で導入された Item#hasSameContentAs を使って同値判定の処理を簡素化する、という変更。

Groupie は内部で DiffUtil を使っていて、 DiffUtil.Callback#areContentsTheSame の判定に2.5.xまでは equals を使っていたので、同一の Item で中身も一緒の場合には再描画をさせたくない時は equals の override が必要になっていた。これが Item の実装によっては割と悩ましい問題となることもあって、その辺については以下の記事が参考になる。

qiita.com

DroidKaigi アプリでは元々この equals の実装のために EqualableContentsProvider という interface を用意していたが、Item#hasSameContentAs の登場によって equals の override をしなくても DiffUtil における同値判定を行えるようになったので、EqualableContentsProvider まわりの実装を全てそちらに移した、という内容になる。ちなみに、 Item#hasSameContentAs のデフォルト実装は equals を呼び出すだけなので、2.5.x 以前から 2.6.0 以降にアップデートする時に影響が出ないようになっている*2

Groupie は2018年末頃から半年強アップデートがなく死んだかと思われたが、昨年8月にメンテナが増え、それ以降はそこそこ活発に開発が進むようになった (最近はまた落ち着き気味だけど)。

github.com

Adjust line spacing of the description in about page #703

テキストの行間幅の設定が漏れてたので修正したもの。

Make pull request CI failed when test tasks failed #715

Pull request のCIで使ってた CircleCI のジョブが失敗してるし、失敗してるのに status check が passed で返ってきてしまうので、失敗した時は失敗と返すようにして、併せて失敗している部分を修正する、という pull request。ジョブの失敗の内、依存ライブラリを前もってダウンロードできてなかった方は結局 offline mode を外すことで対応したのだけど、これは依存を全てダウンロードするタスクを作って解決できたらよかったのだけどなあという思いが残っている。何か方法がないかGradle 力の高い人の意見を聞いてみたい。

Add style Theme.DroidKaigi.ActionBar for dark theme #732

OSSライセンスの一覧表示を Google が提供している oss-licenses-plugin で実装していて、そちらで別の問題があって issue が作られていたのだけど、そこで一覧が真っ白になってると報告されてそれを修正した pull request。Dark theme 向けの Theme.DroidKaigi.ActionBar の定義がなかったので、dark theme になってテキストの色などが白系になったのに背景などに light theme の時と同じ白色が当てられてしまったため。

おわりに

最初の pull request を出す前は何か簡単なものでも1つくらいできればいいなあ程度に思っていたが、最終的に5個送っていた。1つ目が merge されたり awesome label を頂いたりして楽しくなったのもあるが、DroidKaigi それ自体だけでなくこの公式アプリ開発も期間中は多くの人が活動していて一種のイベント感があり、開発に参加しやすい雰囲気があると感じたのも一因だったように思う。これは Live 配信された opening talk でも話があったように、DroidKaigi アプリ開発で初めて OSS に contribute したという人が増えたというところからも感じられた。

これからの1年は DroidKaigi 界隈にとって難しい状況になると思うのであまり暢気なことを言うべきではないかもしれないが、またこうやって来年もアプリ開発に contribute できたらいいなあと思っている。

*1:8/22 に Kotlin Fest と一緒に DroidKaigi 2020 Lite として開催される https://youtu.be/IwHw7vrFwSE?t=4260

*2:ただし別の箇所で breaking change が発生しているので注意

bitrise.io における workflow の差分管理

昨年末に Bitrise Advent Calendar 2019 を眺めてたら、以下の記事で bitrise.io 上で bitrise.yml の差分管理ができるって書いてあって、気になって調べてみた話。

qiita.com

記事には差分管理の機構について紹介がなかったので、"bitrise yml diff" とか "bitrise yml version control" とかそんな感じで検索してみたら、Bitrise 公式ページの Features > Workflow editor に以下の記述があり、どうやらビルド単位で workflow のスナップショットをもっているっぽいことがわかった。

Bitrise saves each build’s configuration state and if changed you can check out a diff between the older and the new versions. Something seems off? Click restore to roll back to an earlier version.

www.bitrise.io

それからもう少し調べてみたところ、bitrise.io 上での bitrise.yml の管理について書かれたドキュメントに行き着いたので完全理解した。Bitrise はコアの部分のドキュメントはちゃんとしているので公式ドキュメントにたどり着いたら実質ゴールである。この調子で各 step でできることのドキュメントも充実させてほしい。Step の振る舞いを理解するのに毎回レポジトリを探してコードを読むのは何か間違っている気がするので。

devcenter.bitrise.io

ドキュメントによると、できることは以下の3つとのこと:

  • 各ビルドの実行で使われた bitrise.yml のスナップショットの確認
  • 「現在の」 bitrise.yml との差分の表示
  • そのスナップショットへのロールバック

あくまで「変更履歴の管理」じゃなくて「現在との差分管理」なんだなあと思わずにはいられないが、CIの workflow にそんなリッチな変更管理はいらないのかもしれない。でもたくさんのビルドが間断なく実行されるような開発環境で変更前のビルドを掘り出すみたいなケースを想像すると、やっぱり「いつ・どんな」変更をしたかをビルドとは独立して記録しておいた方が便利じゃないかなあって思った。

最初は調べてみて良い感じだったらレポジトリ管理から乗り換えようかと考えていたけど、自分の求めているような機能ではなかったのと、レポジトリで bitrise.yml を管理する理由って、開発の並列度の関係で新しい workflow の実験中は変更の適用範囲を限定したいとか、レポジトリの状態 (ツールのバージョンとか build script の変更とか) と常に同期させたいとかも含まれてて、変更管理だけではないなあということに気がついたので、結局これからもレポジトリで bitrise.yml を管理し続けるだろうという結論になった。おわり。

2019年振り返り

記録として

転職した

nashcft.hatenablog.com

Twitter でみるような人たちと面白おかしく Android アプリ開発をやっている。
一つだけ悩みを挙げるとするなら、前職では途中からフルフレックス制になって、電車の混雑を避けるために11~12時出勤みたいなことをずっとしていたので、それに慣れてしまった身体には今の10時出勤は大変厳しいというのがある。

オフィス移転した

面談の時から話に聞いていたのだけど入社して1ヶ月でオフィス移転したことになる。
駅から遠くなってしかもセンター街を通り抜けなければならないので人を呼ぶにはしんどいところだけど電車通勤じゃない身としてはむしろ通勤が平和になって良かった。

入籍した

話がまとまってからいろいろ都合が良かったのが4/1だったので入籍日はそのようになった。

旅行行った

伊勢と伊東に行った。また温泉行きたい。

読書

転職してから読書ペースがガタ落ちして、前職にいた2月までの読了数と3月以降の読了数がほとんど一緒という結果に。積読タワーが大変なことになってきたのでなんとか読む時間を確保したい...

今年読了した中で特に面白かったのは、仕事関係では "Joel on Software" で、もはや古典と言える程度には内容の具体的な部分は古くなってしまっているのだけど、主張や考え方は今でも通じるものがあると思うし、あと単純にエッセイとして面白い。
趣味方面だと人に勧められて読んだコンラッドの『闇の奥』が面白かった。

2020年にやりたいこと

引っ越したい。