Android版クックパッドアプリで採用している技術の現状確認 2018年版

目次

はじめに

技術部の門田( @_litmon_ )です。

Android版クックパッドアプリで採用している技術の現状確認 2015年版 から3年、Androidアプリ開発を取り巻く環境も大きく変わってきました。 本エントリでは、以前のエントリからこれまでにAndroid版クックパッドアプリにあった技術選択の推移や、現在の状況を記していきます。

技術選択に関する基本的な方針などは変わっていないので、前回のエントリ( Android版クックパッドアプリで採用している技術の現状確認 2015年版 )を参照ください。

技術選択の各論

開発環境

現在のクックパッドアプリの開発環境は以下のようになっています。

  • Android Gradle Plugin 3.2.0
  • Gradle 4.10
  • targetSdkVersion 28
  • compileSdkVersion 28
  • minSdkVersion 21
  • support library version 28.0.0 (AndroidX未対応)
  • 使用言語: Java, Kotlin 1.2.60

ここ一年で大きく変化したことといえば、やはり targetSdkVersion と minSdkVersion 、そしてKotlinの採用でしょう。 それぞれに関して1つずつ振り返っていきたいと思います。

targetSdkVersion

targetSdkVersionに関しては、Googleが昨年12月頃にtargetSdkVersion 26以下のアプリは今後リリースもしくはアップデートできなくなるという発表をしたことで、無理矢理にでも上げざるを得ない状況が各アプリにあったと思います。 クックパッドアプリも例に漏れず、今年の頭はまだtargetSdkVersion 23だったアプリが、今年の9月にようやく26を満たすことが出来るようになりました。

targetSdkVersionをこれだけ長い時間かけて上げていったのには理由がありました。

まず1つが、targetSdkVersion 24で入ったIntent, Bundleに含められるデータサイズ制限です。 クックパッドアプリは大量のデータをActivityのIntentに載せて画面遷移を行っていたり、savedInstanceStateに保持していたため、targetSdkVersionを24に上げた状態でAPI 24以上の端末でアプリを使用すると、画面遷移を行った瞬間にクラッシュするという事態が発生していました。 ここを直すために色々と手を尽くして、最終的に以下の対応をすることで大きな問題を起こすことなくアップデートすることが出来ました。(実際にはいくつか作業ミスもありバグ報告も来ていたが、すぐに収束した)

  • Activity遷移の際のデータ保持は可能な限り小さくなるよう、大きな箇所を洗い出して修正
  • savedInstanceStateのデータ保持はAndroid Architecture Component ViewModelを使うように変更

そしてもう1つが、targetSdkVersion 26で入ったバックグラウンド制限です。 クックパッドアプリでは、料理きろくという機能の中でJobSchedulerを使ってバックグラウンド処理を行っており、その他には特にバックグラウンド制限に引っかかるようなものはない、と作業当初は思っていたのですが、実はGoogle Cloud Messagingの実装が古く、BroadcastReceiver内でServiceを起動しており、見事にバックグラウンド制限に引っかかっていました。 また、Google Cloud Messagingも2019年4月までにはFirebase Cloud Messagingに置き換える必要があったため、これを機にFirebase Cloud Messagingへの移行を行いました。

ここでもまた、アプリ上で使っているFirebaseプロジェクトが現在GCMで使っているプロジェクトと一致していない、という問題もあったのですが、GCMは2019年4月以降プッシュ通知を送れない状況であることと、そのプロジェクトはGCM以外の用途では使っていなかったこと、またプッシュ通知を送信する基盤実装への修正も容易であったことを踏まえて、廃止されるまでは両方のプロジェクトにプッシュ通知を送信する、という対応で事なきを得ました。

さらに、ローカルプッシュ通知の実装も、AlarmManagerからBroadcastReceiverを経由してServiceを起動する流れになっており、こちらもバックグラウンド制限に引っかかっていました。こちらはJobSchedulerを使った実装に変更することで対応を行い、これらの作業によって無事にtargetSdkVersion 26に上げることが出来ました。

targetSdkVersion 27, 28に関しては、上で挙げたような大きな障害はなく、すんなりと上がって今は最新の環境で開発が行えている状況です。

minSdkVersion

minSdkVersionに関しては、以前ブログでも紹介があった通り、現在は21となっています。詳細は以下の記事を参照ください。 Androidアプリ の minSdkVersion を21にした話

これによって、開発環境のアップデートに対する作業がぐっとやりやすくなりました。 正直、この1年でここまでのアップデートを行えたのはこの取り組みがあったおかげだと思っています。

Kotlinの導入

Kotlinの導入に関しては、2017年のGoogle I/Oで公式にサポートすると発表した直後から導入したいというのは話していたのですが、クックパッドアプリに関しては導入するのにだいぶ時間がかかってしまいました。

その頃のクックパッドアプリの最大の問題として「どこになにを書けばいいかが分からない」というものがあり、長い間続いているプロジェクトでアーキテクチャなどもうまく導入できておらず、無法地帯なコードベースがあったため、この状態のままKotlinを導入しても問題が増えるだけだと感じていました。 そのため、どこからKotlinを導入するか、どういう風に進めるかを基盤チームで議論した結果、VIPERアーキテクチャを導入し各画面の実装をアーキテクチャに対応させながらKotlinを導入していくことが決まりました。

その間も、他のプロジェクトや社内で使うライブラリにはKotlinを使うことに特に制限はしていなかったため、Kotlin 100%のプロジェクトも中には存在します。 また、導入の際にはStyleGuideを定めたり、社内でKotlin勉強会を開いたりして知見を共有し合ったりしていました。

方針が決まってからしばらく別の施策によって手が止まっていたのですが、クックパッドアプリでも今年の3月頃から導入を開始していて、今ではプロジェクトの約20%のコードがKotlinに置き換わっています。

f:id:litmon:20181115172928p:plain f:id:litmon:20181115172946p:plain

Swiftに比べてまだまだ手を付けられていない状況ですが、手を付けられるところから少しずつKotlinコードへの変換とアーキテクチャの適用を行っています。

HTTP Client

以前の記事時点では、Cookpad APIとの通信にはVolleyを使用していました。 ですが、以前の記事でも述べていたとおり、Volley内部で使用されているApache HTTP ClientがDeprecatedになっていたため、昨年メインとなるCookpad APIとの通信層に使用するコアライブラリをOkHttp3に置き換えました。

Retrofitなどのラッパーライブラリを使うかは検討しましたが、Cookpad APIはGarageで作られているため、Garageのリクエスト方式に沿ったものがあったほうが良いだろうという判断をして、社内ライブラリとしてgarage-client-androidを作成して使っています。

以降の展望としては、API側の変化を伴うものになっていくのではないかなと予想しています。 現在の問題点として、Kotlin, SwiftなどのNull安全な型制約を持った言語とRESTFullなAPIは相性が悪く、返ってくる値が Nullable であるかどうかが分かりにくく、開発効率も下がるしバグが発生しやすいというものがあります。 また、モバイルアプリでは、一画面で複数のリソースが必要になることが多く、RESTFullなAPIだと複数のAPI通信を行う必要がある場合が発生し、扱いが難しいところがあります。

これを解決するために、社内の一部新規サービスではGraphQLを使用したAPI通信を始めていたり、gRPCやSwaggerなどの型制約を解決できるような仕組みを使えないか検討し始めていたりします。

Dependency Injection

以前の記事時点ではRoboGuiceを使用していました。 ですが、RoboGuiceは2016年8月の時点でサポートが終了し、別のDIライブラリへの移行を余儀なくされる形となっていました。 前回の記事でも言及していたとおり、社内ではDIライブラリを使うべきかどうかという議論がずっと繰り返されており、Kotlinを導入した際に同時に導入したVIPERアーキテクチャではDIライブラリを使用しない形で進めていこうという話もしていました。

しかし、現段階で使っている箇所に関しては簡単に剥がすことも出来ないため、2018年4月からは実装も薄くてRoboGuiceからの移行が比較的簡単だったToothpickというDIライブラリを導入しています。 導入しようとしていた4月時点ではまだproguardへの対応が行われている最中だったため、少し時期尚早だったかなと思っていたのですが、いまのところ大きな問題もなく運用出来ています。

ちなみに、クックパッドで最近リリースされたcookpadTVアプリに関しては、DIライブラリにはDagger2を使用しています。このあたりの技術選定は基本的に各サービスごとのエンジニアに任せるようにしています。

cookpadTV -クッキングLIVEアプリ-

Image Loader

画像読み込みには、以前はPicassoを使用していましたが、こちらも長らく開発が滞っていたため、昨年Glideに移行しました。 導入段階ではFacebook製のfrescoも検討していましたが、こちらは画像読み込みの際のインターフェースがPicassoと大きく離れていたり、画像のサイズを予め知っておく必要があったりと、移行作業に難ありだったため見送りました。

PicassoからGlideへの移行に関しては、インターフェースも非常に似通っているためほとんど技術的な問題は起きませんでしたが、リリース後「画像が読み込めない」というお問い合わせが多数寄せられ、とても頭を悩ませたことは記憶に新しいです。(結果的に、通信レイヤーで使用していたOkHttp3のバグだったことが判明し、OkHttp3のアップデートを行うことで解決しました)

Debugging

デバッグツールには、StethoHyperion-Androidを導入しています。

Sthethoは、通信層のコアライブラリをOkHttp3に置き換えたことにより、プロキシを介さずにアプリの通信履歴を見ることが出来て、非常に重宝しています。

また、Hyperionは、SharedPreferencesに保存されているデータの中身を見たり、View構造を探索したりと、非常に多機能で優秀な上に、拡張してデバッグ用のメニューを追加することも出来ます。 今までは、クックパッドアプリのサイドメニュー下にデバッグ版のみ表示されるツールメニューを用意していたのですが、Hyperion導入後はHyperionのメニューとしてデバッグメニューをまとめることが出来るようになり、デバッグ用の機能を管理するのがとても楽になりました。

Android Emulator on Jenkins

Jenkins CI上でのAndroidエミュレータの扱いに関してもここ数年で大きく動きがありました。 弊社のCI環境は、Amazon EC2インスタンス上に構築されたJenkinsを使用しており、以前はその上にARMエミュレータを起動してCI上でのInstrumentation Testの実行に使用していました。 しかし、やはり起動時間や実行時間がネックとなり、それらの改善を行うためにこれまで様々な取り組みをしてきました。各取り組みに関してはそれぞれブログがまとまっているので、そちらを御覧ください。

現在では、AndroidエミュレータはGenymotion Cloud(旧Genymotion On Demand)を使用していますが、上記記事内でも言及している通りGenymotion CloudではGoogle Playの機能を使うことが出来ません。 これに対して、クックパッドアプリでは新たにいくつかの方法を検討中です。例えば、Firebase TestLabを使ったInstrumentation Testの実行や、上記記事でも挙げたようなグローバルチームでいち早く導入されているAWS Bare Metalインスタンスを使用したAndroidエミュレータの構築、またはFirebase, AWSなどのデバイスファームの使用などが挙げられます。

CI環境に関してもその時々で要求されるものは移り変わっていくので、色々な選択肢を試しながら技術選択を行っています。

コードレビューbot

社内では、GitHub Enterprise上でのPullRequest駆動での開発が盛んですが、その中でもコードレビューを行う際にLintやfindbugsなどの静的解析ツールによる指摘を自動化しています。

以前は、社内で開発していたdokumiというツールを使っていたのですが、 dangerというオープンソースのツールに乗り換えました。 dokumiの設定はdokumi本体に含める必要があり、各プロジェクトの設定がツールに含まれる形になってしまい、ツールとプロジェクトの関係が密になりすぎるという問題がありました。

その点、dangerは各プロジェクトごとにRubyで設定ファイルを記述することで動作するし、プラグインを作成するのも容易だったため、複数のプロジェクトに簡単に導入できるという点が乗り換えたポイントでした。また個人的に、dokumiと違ってdangerのコメントは上書きされていくので、PullRequest上に指摘コメントが積み重なっていき読みづらくなることが減るところが気に入っています。

danger導入時に関する話は以下のブログ記事で詳細に書かれているため、よろしければ読んでみてください。 Android開発のコードレビューbotを乗り換えた話

リリースエンジニアリング

以前から、リリースに伴う作業を手作業ではなく機械によって自動化するために、fastlane/supplyを使ってリリース関連の作業を自動化しています。

弊社のCI環境はJenkinsなので、Jenkinsのgoogle-play-android-publisher-pluginを使用する選択や、Gradleプラグインのgradle-play-publisherなどを使用する選択も取れたのですが、その中でもfastlaneを使用している理由は以下の点が大きいかなと思っています。

  • fastlaneはiOSアプリでも積極的に使用されていて、内製のプラグインも開発されているなど、iOSとの仕組み共通化のためにも使える
  • Gradleプラグインとして採用するとビルドに影響が出てしまうので、ビルドとリリースフローは分離したい
  • Ruby製のツールなので、社内の開発リソースと一致しやすい(※個人的な意見です)
  • Groovy書きたくない(※あくまでも個人的な意見です)

最近では、fastlane/supplyを利用してアプリの自動リリースを行い、リリースフローの機械化・自動化も進めています。こちらに関しては、iOSでの取り組みが先行しているため、以下のブログ記事を参照ください。

クックパッドアプリはみんなが寝ている間にサブミットされる

また、来たる2019年2月のDroidKaigi 2019にて、「Google Play Consoleのリリーストラックを有効活用してリリースフローの最適化を行った話」というセッションが採択されたため、そこで詳細に話す予定です。ご興味のある方は足を運んでいただけると幸いです。

おわりに

いかがでしたでしょうか。思いつく限りの最近のクックパッドアプリの開発事情について書き記してみました。

近年のクックパッドアプリは、目新しいものを導入したというよりは今まで使っていたものがどんどんと使えなくなっていったためアップデートしていったという話が主になっていることがわかります。 とはいえ、新しい技術を試していないわけではなく、使えるものは積極的に取り入れていくし、色々な技術を試すための環境は充分に用意されています。

また、今回はクックパッドアプリの開発事情について書きましたが、最近は新規サービスもどんどんと生まれてきており、複数のアプリに対する仕組みの共通化なども積極的に行っています。

これからもいろんな技術を駆使してユーザーさんにすばやく価値を届けられるように改善を続けていくので、一緒に高まっていきたい人はぜひぜひお声がけください!

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/