クックパッドアプリ(Android)の開発効率化のためにやったこと/やっていること

モバイル基盤部のこやまカニ大好き(id:nein37)です。 モバイル基盤部では、CI環境の改善やアプリのリリースサイクル自動化といった開発・リリースフローの効率化に加え、アプリのビルド速度改善や開発のしやすさを改善する様々な取り組みを行っています。 今回はその中から、クックパッドアプリ(Android)に対して行った開発効率化の取り組みの一部を紹介したいと思います。

あわせて読みたい : Android版クックパッドアプリで採用している技術の現状確認 2018年版

日々のメンテナンス系

不要になったソースコードやリソースの削除

気がつくとどこからも参照されなくなったソースコードやリソースはどうやっても発生するので定期的に消しています。 特にモバイル基盤部のタスクと決まっているわけではないですが、単に綺麗になって嬉しいこととapkサイズが少しでも小さくなれば良いという気持ちで手の空いた時に Android Studio の Analyze ツールや konifar/gradle-unused-resources-remover-plugin で検出されたものをシュッと消しています。

Lint設定の最適化/Lint警告の除去

クックパッドアプリでは以前、朝Lintという取り組みで細かいLint指摘事項への対応を行っていました。 この取り組みは警告も減るしなんとなく成果が出た気持ちになって良いものだったのですが、最終的に修正コストが異常に高かったり本当に対応が必要かと思えるような警告が残って誰も手を付けられなくなり、やがて途絶えてしまいました。

僕もすっかり朝Lintのことを忘れていたのですが、ある時なんとしても令和の朝Lintというプルリクエストを出したいと思い手元で Lint を動かしてみるとおよそ280件の指摘事項があり、うちいくつかはクックパッドアプリでは考慮しなくても良いようなものだとわかりました。 lintOptions を見ると、これは逆に対応したほうが良いと思えるものもいくつかあります。 この lintOptions をクックパッドアプリの現状に合わせたものを令和の朝Lintとしてプルリクエストにしました。

f:id:nein37:20191021143111p:plain

このときのプルリクエストでは各指摘事項の変更に対して、なぜ enable/disable にするのか、どのように修正すれば良いのかをコメントしておいたので、今後 Lint 設定を再度見直すときにも利用できると考えています。

f:id:nein37:20191021143140p:plain

同じくモバイル基盤メンバーの吉田さんが社内ブログでも朝Lint活動について広報してくれていて、朝Lintという習慣がひっそりと復活しつつあります。

※ クックパッドアプリではプルリクエストに含まれる変更に関する Lint や ktlint の指摘事項は Danger によってプルリクエスト中に指摘され修正するようになっており、朝Lintの対象となるコードを増やさないような仕組みづくりも同時に行っています。

画像リソースのWebP化/WebPおじさん化

あるときどうしても apk サイズを小さくしたくなり、画像リソースをまとめてWebPに変換しました。 Android Developers にはWebPはサイズが小さくて最高とか画像リソースをWebPに変換するとビルドが速くなるといった夢のようなことが書いてあり、それを信じて変換しました。 Android Studio には WebP の変換ツールが組み込まれていて、アプリ内の画像を一括で WebP に変換することができます。 また、クックパッドアプリは minSdkVersion21 になっているため、ロスレスや透過といった WebP の機能をフルに活かすことができるのも利点でした。 Lossy WebP への変換では画質が劣化するために以前のバージョンと比較する必要がありますが、Lossless WebPへの変換であれば理論上画質の劣化はありません。 WebP に乗り換えるためにすべての画面のすべての画像の画質チェックをしなくても良いのは便利でした。

f:id:nein37:20191021143203p:plain

6月頃に一括でアプリ内の画像およそ1000個の画像を Lossless WebP に変換し、その結果、apk サイズを1.2MB縮小することが出来ました。 それ以降は主にプルリクエストレビュー時に WebP おじさんとして活動しています。

f:id:nein37:20191021143229p:plain

得られた知見として、 Lossless WebP への変換ではほとんどの場合画像サイズが小さくなりますが、以下の2パターンではうまく縮小できませんでした。

  1. 元画像が十分に小さい Indexed Color のみで縮小されたPNGである場合
  2. 元画像がまったく軽量化されていない巨大な画像である場合

1. はWebPに変換した場合に仕組み的に縮小される余地がないためか、ほぼ誤差範囲ですがPNGよりもWebP画像のほうが大きくなる事があります。 Android Studio の変換ツールに容量が節約できない場合は変換をスキップするという設定があるため、この場合は変換対象から外しています。

2. は Lossless WebP ではなく Lossy WebP で変換すべきものです。正直見た目では Lossless にすべきか Lossy にすべきかわからないので、現状は解像度とファイルサイズをみて判断しています。

将来的にはLossless/Lossy WebPへの変換はDangerに指摘させることができると良いなと思っています。 この記事を書いている途中でプルリクエスト内に png, jpg や大きすぎる WebP が含まれている場合は Danger に指摘させるような修正を入れたのでWebPおじさん業はなくなりました。

Danger により機械化されたWebPおじさんの様子 f:id:nein37:20191021143256p:plain

なお、ビルド速度への影響は計測できないほどわずかでした。

minSdkVersion 21 後の変更

Ripple 対応

これまで background リソースを StateListDrawable で切り替えてタッチフィードバックを実装していたような箇所を Ripple によるタッチフィードバックに置き換えていきました。 API 21から RippleDrawable も使えるようになっていて、この部分は素直に minSdkVersion21 の恩恵を受けられた部分でした。

android:elevation の指定で影をつける

当たり前なんですが古い端末での挙動とか何も考えずに elevation で影が落ちるというのが本当に楽で良いのです。

*-v21 系代替リソースの整理

Style や Theme 系リソースに *-v21 で分岐させていたリソースがあったので、minSdkVersion 21 を期に整理しました。 これまで Material Design のバックポートに関する知識がないと Theme を変更するのが難しかったのですが、minSdkVersion21 になったことで Theme や Style の編集はだいぶ簡単になりました。

ツール導入など

AndroidKTX導入

クックパッドアプリ内の Kotlin 比率が高くなって来たのでAndroidKTXを導入しました。 去年の11月時点ではクックパッドアプリのおよそ20%が Kotlin でしたが、現在ではさらに Kotlin への置き換えが進み半分ほどがKotlinで書かれている状態です。 f:id:nein37:20191021143324p:plain

Firebase Performance Monitoring

去年突然アプリのパフォーマンス監視がしたくなりFirebaes Performance Monitoringを導入しようとしました。 このときはクックパッドアプリが依存していた一部のjarと競合してうまく導入できなかったのですが、今年になり Android Gradle Plugin を更新したりFirebase Performance Plugin 自体が更新されたりした結果、いつのまにか導入できるようになっていました。 現在は社内の主要なアプリでは大体有効になっていて、特にレスポンスが遅い API の特定やアプリ起動時間の測定に使われています。

Stetho から Flipper への乗り換え

もともと Stetho を利用していたのですが、Mirrativ tech blogさんの記事を参考に Flipper を試してみたら良かったので乗り換えました。 アタッチしなくても良いのは本当に素晴らしく、アプリのデータ削除などでプロセスキルを挟んだ場合でもアプリを立ち上げれば自動でログを見られるようになるのはとても便利です。

R8導入

Android Gradle Plugin v3.4 でデフォルト有効になったR8ですが、クックパッドアプリではそれ以前からR8を利用して難読化処理を利用するとビルド時間が倍になるという問題が発覚していました。 BetaやRCでバージョンが上がるたびに試していましたがまったく改善せず、とうとう v3.4 が stable になっても解決しなかったため一時的にR8を無効化していました。 そのままでもアプリのビルドはできるのですが、標準ツールが自分のアプリで利用できないのは悲しいので、定期的に時間をとって調査していました。

やるぞ!という気持ちのissue f:id:nein37:20191021143401p:plain

あるとき annotations.jar という古代のProguard設定の仕組みがビルド時間に影響をしていることに気が付き削除することで無事に有効化することができましたが、普段から最新のビルドツールや設定を試して問題を検出することの重要性を感じました。

マルチモジュール関連

以前マルチモジュールにしていく話をしてから一年以上経ち、クックパッドアプリも25モジュール構成になりました。 最初の頃は StyleTheme などをまとめたUIモジュールやログ用の仕組みをまとめたログモジュールなど、比較的変更の少ない静的なモジュールばかり切り出していましたが、これらのモジュールは切り出した後もほとんど変更が入っておらずビルドキャッシュを最大限利用できた上、位置づけもわかりやすいので早めに切り出しておいてよかったと思います。 そこから各種機能をモジュールに切り出す作業を進めていますが、:legacy に依存しないモジュールで機能を実装できるようになるまではまだもう少し掛かりそうです。

モジュールの命名

最近 Google Developers の日本語ブログにもAndroid のモジュールのパスに関するちょっとしたヒントという記事が出ていましたが、クックパッドアプリでもAndroidビューにおけるモジュール表示がわかりにくいという問題は早い段階で発覚していました。 上記の記事で紹介されていたような projectDir による解決も検討したのですが、結局 features_ のような prefix をモジュールにつけることにしました。 クックパッドアプリでは単純な解決方法を選ぶという方針でこのようにしましたが、このあたりはチームやモジュール分割の方針によって最適解が変わると思います。

モジュール切り替えによるアプリの設定変更

以前のクックパッドアプリはビルドバリアントによって接続先とデバッグ機能の有無を切り替えていました。

  • stage flavor dimension(開発用設定の切り替え)
  • mode flavor dimension(接続先設定の切り替え)
    • internal(社内ステージングサーバ向け、Hyperionなどの開発用機能あり)
    • external(本番サーバ向け、リリース用設定)
  • buildType
    • debug(minify,Proguard なし)
    • billingBeta(決済確認用の特殊なビルド)
    • release(minify, Proguard あり、リリース用証明書)

上記設定の組み合わせによってビルド時に必要な設定を利用していましたが、マルチモジュール構成のプロジェクトではアプリがこれらのビルドバリアントを設定している場合、依存しているライブラリプロジェクトにも同様のビルドバリアントを設定する必要があります。 (ライブラリプロジェクトに同名のビルドバリアントが存在しない場合、 ./gradlew testProdInternalDebugUnitTest のようなテストコマンドでライブラリプロジェクトのテストが実行されなくなる場合があります)

モジュールが増えていくにしたがってこの設定が面倒になり、 Android Studio 上でのビルドバリアントの切り替えも大変になってきたことから、 flavor ではなく依存先モジュールの切り替えによって接続先の切り替えや開発用機能の追加を行うように切り替えました。 変更後の各モジュールの依存は以下のようになっています。(dev flavor は minSdkVersion 21 化したことにより分岐がほぼなくなったので不要になりました)

  • :app_cookpad (本番サーバ向けビルドをリリースするためのモジュール)
    • :settings_external (本番サーバの接続先情報モジュール)
  • :app_cookpad_internal 社内向けアプリをビルドするためのモジュール)
    • :settings_internal (社内サーバの接続先情報モジュール)
    • :features_debug (開発用機能モジュール)
  • :app_cookpad_billingBeta 決済確認用のアプリをビルドするためのモジュール)
    • :settings_internal (社内サーバの接続先情報モジュール)
    • :features_debug (開発用機能モジュール)
    • アプリモジュールとして定義したことにより buildType billingBeta は廃止しました

この変更により、今まで ./gradlew assembleProdExternalRelease という呪文のようだったビルドコマンドが ./gradlew :app_cookpad:assembleRelease だけで良くなります。 AndroidStudio 上でもビルド対象のモジュール(=必要なアプリの種類)とbuildType(=minify,proguard,証明書)だけ意識すればよくなり、GUIでの操作もかなり簡略化されました。 クックパッドアプリはこれまでの長期間の開発で Gradle ファイルがかなり複雑化していたのですが、ビルドバリアントの整理とモジュール分割によってそれぞれ設定を書く場所がわかりやすくなり、多くの部分を共通化してシンプルな構造になっていきました。 この方式を採用するとモジュール数はどうしても増えていくのですが、クックパッドアプリのような大きいプロジェクトでも ./gradlew testDebugUnitTest のような基本的なコマンドが何も考えなくてもちゃんと動くというのは開発のしやすさという点で非常に重要だと思っているので、今年やっておいてよかった変更の一つだと考えています。

おわりに

僕の趣味で比較的地味な変更ばかり紹介してしまいましたが、今年はこの他にも多くの(機能追加以外の)変更が行われています。 モバイル基盤部ではこれからも新機能を簡単に開発し、素早くユーザーに届けるためにモバイルアプリの開発効率化を続けていきます。

興味がある方はぜひ一度クックパッドオフィスに遊びに来てください。 https://info.cookpad.com/careers/