目次
- はじめに
- 技術選択の基本的な方針
- 技術選択の各論
- HTTP Client
- Dependency Injection
- View Injection
- Asynchronous Control Flow
- Object Relation Mapper
- Logging
- Fragment
はじめに
技術部の id:gfxです。
Android版クックパッドアプリのリニューアル*1から約1年たちました。現在はリリースごとに5人程度がコミットし、2週間に1度リリースを行う開発体制となっています。プログラミング言語はJavaで、コメントも含めたアプリのソースコードの行数は約15万行です。
本エントリでは、Android版クックパッドアプリで使っている技術、具体的にはライブラリやフレームワークについて紹介します。また、そのための技術選択のアプローチについても概説します。
技術選択の基本的な方針
まず技術選択の方針についてですが、これはサービスの性質に依存すると考えています。クックパッドはすでに15年以上の歴史をもつサービスで、その歴史において ColdFusionからRailsへの置き換えを行ったり Rails 3.2から4.1へのアップグレードを行ったりしており、最新の技術を使って開発できるようにしています。
クックパッドのウェブサービス同様に、モバイルアプリの開発もこの先10年以上続くでしょう。したがって技術選択の基本的な指針においては、この先10年間継続してメンテナンスおよび更新していくことができるかどうかが重要です。Androidに関して言えば、まずGoogleの推奨する標準的な開発環境とサポートライブラリをベースにし、その上で適切な技術を選択していくということも必要です。
技術選択の各論
さて、前置きはこのくらいにして、実際にアプリで選択しているライブラリやフレームワークについて、選択の理由や所感などを紹介していきます。
HTTP Client
Cookpad APIの通信を担うHTTP clientは、Apache HTTP client をバックエンドにしたVolleyを使っています。Volleyは単一のリクエストキューを持ったHTTP clientの実装であり、いくつかの認証を組み合わせて使っているCookpad APIとの通信に適していると考えられたからです。リクエストキューがあると、認証が完了するまで他のリクエストを停止するということが簡単にできます。
しかし、Volleyの採用は間違いでした。VolleyはGoogle I/O 2013で大々的に紹介されたにも関わらず、Gradleから利用しやすいアーティファクトの形でリリースはされず、リポジトリの公開のみにとどまっています。また依存しているApache HTTP clientはAndroid API level 22でdeprecatedになりました。Volleyはバックエンドとなる通信用HTTP clientをカスタマイズできるのですが、その通信部にHttpURLConnectionを採用したHurlStackもApache HTTP clientに依存しています。この状況を踏まえると、Volleyの未来は明るくなさそうです。
また、Volleyはカスタマイズ性や安定性にも難があります。Volleyを利用して実装したアプリ用Cookpad API clientではVolleyを大きく拡張しているのですが、結果的にVolleyのインターフェイスにしたがっているだけでVolley自体のコードはあまり使っていません。これは、Volley自体のロジックではアプリの要求を満たせなかったり、エラーハンドリングに問題があったためです。
なお、最近はOkHttpも補助的に導入しはじめており、Cookpad API clientもいずれOkHttpで置きかえるでしょう。Android 4.4からはHttpURLConnectionのバックエンドがOkHttpになっていますが、これはこれでキャッシュまわりにクラッシュするバグがあるので、最新のOkHttpを直接使うほうがいいでしょう。
Volleyについての反省点は、新しい技術に飛びついて痛い目を見たということです。Volleyの実装で参考にできる点は多々ありますが、今となっては使用はおすすめできません。
Dependency Injection
DIはRoboGuiceを採用しています。導入時には RoboGuice / Square Dagger (Dagger1) / Proton / Transfuseが比較検討され、RoboGuiuceが採用されました。RoboGuiceを採用した理由は次の通りです。
- Android用の機能が豊富 - Google GuiceをベースにAndroid用のカスタマイズがされており、たとえば Context Singoletonなどがある
- 2009年から継続して開発されている
- 歴史が長く採用事例も多いので知見がたまっている
一方でRoboGuiceは、重厚でバグを追いにくいこと、動作が遅いことなどの欠点もあります。
現在はよりパフォーマンスのよい Google Dagger (Dagger2)への移行が議論されていますが、依存関係の記述方法が異なるため移行コストが高く、あまり進捗はありません。
DIは概念が複雑で使うのが難しいフレームワークなので、そもそもDIを使うべきか否かという議論も行っています。いまのところは、DIでコードを劇的にシンプルにできるという利点は大きいと判断し、DIを使おうというところに落ち着いています。
View Injection
Viewの各要素をJava objectにバインドする仕組み(View Injection)は、開発初期はRoboGuiceの @InjectView
のみを使っていました。しかし今年に入ってから ButterKnifeを導入して、こちらも併用しています。ButterKnifeの導入が遅かったのは、RoboGuiceの @InjectView
と ButterKnife の @InjectView
が混ざると混乱するのではないかという危惧のためでしたが、やはり ButterKnife がないと ViewHolderパターンの記述が煩雑であるということで導入しました。
ところで、 Google I/O 2015 で Data Bindingという新しいライブラリが発表されました。これはいままでの View Injection ライブラリを置き換える強力なものだったため、さっそく導入して使い始めています。
Data Binding導入にあたっての利点には次のようなものがあります。
- ButterKnife+ViewHolderパターンの記述量を大きく減らせる
- 導入にあたっての学習コストが低い
- 最悪使わない事になったとしても、元に戻す作業が複雑でない
なお現在リリースされている dataBinding:1.0-rc0
ではいくつかバグがあるので、一般的には次の安定版を待つのが無難でしょう。
Asynchronous Control Flow
非同期リクエストのフロー制御用ライブラリとしては、RxJavaを採用しました。それ以外のライブラリではFacebook Boltsなどを検討したのですが、使い方が難しいわりに効果があまり見込めないということで採用はしませんでした。
RxJava以前はコールバックベースのインターフェイスとAsyncTaskをCountDownLatchを駆使して制御していました。まだRxJavaを使っていなコードもかなりありますが、徐々に書き換えは進んでいます。
RxJavaの導入については以下のエントリで詳しく紹介しています。
RxJavaの用途としては連続したHTTPリクエストのネストを重ねることなく記述できるプロミス的な使い方が典型的です。またそれだけでなく、非同期リクエストの待ち合わせも rx.Observable.combineLatest()
で行っており、RxJavaを使わないコードと比べるとシンプルで読みやすくなりました。
一方でRxJavaはAndroidコンポーネントのライフサイクルに従った unsubscribe()
が難しく*2、何度もバグを出しています。このあたりをカバーするのが RxAndroidなはずですが、RxAndroidの方向性について若干モメているようで、まだ枯れているとは言いがたい技術です。
また、RxJava自体の欠点もあります。たとえば、無限リストと単一のリクエストを同じ rx.Observable
で表現するのですが、これらは使い方が異なるため、使う際は注意が必要です。様々なデータのストリームをすべて rx.Observable
という一つの型で表現するというRxJavaの特徴が、かえって使いにくくしているという結果になっているのです。
このようにRxJavaの導入は利点も欠点もありますが、RxJavaの根底にあるReactiveX自体の歴史は長く十分に枯れており*3、RxJavaの開発も活発なので、総合的に見て導入のメリットはあると考えています。
Object Relation Mapper
ORMについてはActiveAndroidを採用しており、以下のエントリで詳しく述べています。
クックパッドアプリはその性質上マスターデータがサーバーサイドにあり、アプリのデータはそのほとんどキャッシュです。そこで簡単に使えるということを重視してActiveAndroidを使っています。しかしActiveAndroidの開発は停滞しており、issueやpull-requestは放置されていますから、ActiveAndroidの未来は明るくなさそうです。
将来的にローカルのデータベースを使った機能を拡充したいことを考えると、ORMについてはそろそろ刷新する必要があると感じています。
Logging
クリックイベントなどのメトリクスのログ収集のためのロギングライブラリは、自社で開発したものを使っています。
Pureeは継続して開発しており、現在のv3ではAPIはよりシンプルになり、パフォーマンスも改善しています。必要に応じて自社でライブラリを開発し、メンテナンスしていくという選択が有効なこともあります。
Fragment
FragmentはAndroid組み込みのandroid.app.Fragment
ではなくサポートライブラリのandroid.support.v4.app.Fragment
を使っています。これは、サポートライブラリのFragmentは最新の機能をすべて使えるのに対して、組み込みのFragmentで使える機能はアプリのminSdkVersionに依存するからです。
また、安定性という意味でもサポートライブラリのFragmentを選択する理由があります。組み込みのFragmentとサポートライブラリのFragmentは共に継続して開発されていますが、組み込みのFragmentのリビジョンはAndroid OSのリビジョンに依存します。つまり、組み込みのFragmentを使うかぎり、Fragmentの実装のリビジョンを指定できません。それならば、バージョンを指定して使えるサポートライブラリのFragmentのほうが確実に挙動を予測できます。
まとめ
本エントリでは、Android版クックパッドで採用している技術について解説しました。個々の技術は成功したり失敗したりしていますが、たとえ失敗したとしても反省をいかしつつ次の10年を気持ちよく開発できるような技術選択を心がけています。
クックパッドでは、次の10年の技術を見極めたいエンジニアを募集しています!