読者です 読者をやめる 読者になる 読者になる

Droidcon Italy 2015でアプリのパフォーマンスの話をしました

海外向けのAndroidアプリを開発している @rejasupotaro です。 4/12、13にDroidcon ItalyというAndroidのカンファレンスがありました。

Droidcon Italy 2015

私はそのカンファレンスでパフォーマンスの話をしてきました。この記事の前半では、カンファレンスで発表したパフォーマンスの話を、後半に海外のカンファレンスに参加してみてどうだったかということを書きます。

パフォーマンスの話

以下のような3部構成で話をしました。

  • HTTPと通信を行う環境
  • 画像の読み込みと最適化
  • UXを高めるAPI設計

以降の記事はスライドの補足が主になりますので、スライドを一読したあとに読み進めていただくとより実りがあると思います。

HTTP通信を見直す

通信の中身を見る

まず、通信を見ることができるようにします。以前は mitmproxyCharles などのプロキシツールを使っていましたが、今は Stetho を使っています。 USBに繋ぐだけで簡単にネットワークを見ることができるようになるのでAndroidに詳しくない人でも「挙動がおかしいのですが」「リクエストとレスポンス送ってもらえますか?」というやりとりをすることができるようになりました。

Gzipを有効にする

サーバー側でGzipが有効になっているからといってクライアント側で有効になっているとは限りません。Stethoを使ってGzipが有効になっていることを確認しましょう。 もし対応していなければ自分でリクエストヘッダに Accept-Encoding: gzip を付けて、レスポンスヘッダに Content-Encoding: gzip が含まれていたときにデコードする処理を書く必要があります。

OkHttpはデフォルトでGzipが有効になっています。HttpUrlConnectionや、その他のHTTPクライアントを使っている場合は実装を確認してください。 私は以下の理由からAPIや画像のリクエストにOkHttpを使うことをおすすめしています。

  • HTTP/2.0やWebSocketに対応している
  • コネクションをプーリングしたり、バッファをセグメントのリストとして実装して、拡張するときにはセグメントプールからメモリを再取得するなど、パフォーマンスに配慮した設計になっている
  • A Few 'Ok' Libraries (Droidcon MTL 2015) // Speaker Deck
  • Interceptorという仕組みによって柔軟なネットワーク処理を行うことができる
  • たとえば、NetworkInterceptorを使って手元でのレスポンスタイムを計測するモジュールを簡単に作ることができる

Interceptors · square/okhttp Wiki

Cache-Controlを有効にする

OkHttpはCache-Controlをサポートしています。OkHttpClientにCacheクラスのインスタンスをセットするだけで有効にすることができます。

// アプリ側
OkHttpClient client = new OkHttpClient();
Cache cache = new Cache(cacheDir, MAX_CACHE_SIZE);
client.setCache(cache);
# サーバー側
expires_in(1.hour, public: true)
# => Cache-Control: max-age=3600, public

2回目以降の表示を早くすることができるのもそうですが、ローカルにキャッシュを置くことでオフラインでもコンテンツを見れるようになるのが大きなメリットです。

有効にするのは簡単ですが、端末のキャッシュはサーバー側から消すことができないので、適切なポリシーを設定する必要があります。

Reactive Data Store

このアプリはRxJavaをベースに作られています。基本的な方針は以下の記事にある通りです。

RxJavaはデータの取得からスケジューリング、パイプライン処理、ビューへのバインディングまで、Androidフレームワークを補うように使っています。

画像のリクエストを見直す

画像はJSONのレスポンスに比べるとサイズが大きいので、ちょっとした変更で通信量を大きく減らすことができる可能性があります。そのため、アプリの通信量の大半を占める画像の読み込みライブラリは重要な役割を担っています。

パフォーマンスを左右する要素

画像読み込みライブラリでは以下の要素がパフォーマンスを左右します。

  • 内部で使用されるHTTPクライアントの種類と設定
  • キャッシュの管理
  • Executorの設定
  • リクエストのスケジューリング
  • Bitmapの管理

なのでこれらを中心に、どのように画像が読み込まれるかというのを見ていきました。何を使うにしても、キャッシュが有効になっているかと、適切な優先順位が設定しているかは見ると良いと思います。

Facebookが F8Fresco という新しい画像読み込みライブラリを発表しました。I/Oバウンドの処理とCPUバウンドの処理でExecutorを使い分けたり、WebPに対応していない端末にはJPEGにデコードするようにしたりするなど、PicassoやGlideや他の画像読み込みライブラリを研究して作られているので、これから検証しようと思っています。

画像のフォーマットとサイズ

FacebookのiOSアプリはProgressive JPEGを採用している(Faster Photos in Facebook for iOS | Engineering Blog | Facebook Code)ようですが、AndroidはProgressive JPEGには対応していませんでした。しかし、FrescoでProgressive JPEGに対応するようにしたようなので、Facebookは両方プラットフォームともProgressive JPEGに移行するのかもしれません。 私たちはWebPで画像を配信しています。

また、スマートフォンのディスプレイサイズは様々で、安価で小さい端末と、Nexus 10のような大きな端末では表示に必要な画像サイズが違います。 そこで、私たちは動的画像変換サーバーをS3の前に置いて、実際の画面サイズに合わせて変換するようにリクエストをしています。 それに加えて、回線の品質に応じて係数を掛けて画像を縮小するということをしています。

回線の品質を判定するのに、私たちのアプリでは通信の規格を見ています。日本では4GよりWiFiの方が速いのですが、海外では4Gの方が速かったりすることがあります。そのため規格を見るのは厳密な方法ではありませんが、お手軽な方法ではあります。

画像はURLでキャッシュされるので、回線の品質の判定が変わったときにキャッシュにヒットしなくなるということを心に留めて置く必要があります。品質の判定をするときにはスパイクに対応する必要がありますし、そうでなくてもオフライン時に画像が取得できるように、前回の回線の品質判定を保存しています。

係数をいくつに設定するかは、画像がそのサービスにおいてどれほど重要かに依存しています。クックパッドでは過去の調査から、レシピ画像はレシピを決めるときの重要な情報であるということが分かっていたので、画像が荒いと感じられない程度の値に設定しています。

API設計を見直す

レスポンスにBase64でエンコードされた10px四方のサムネイルを含めるようにしています。それによってレスポンスを受け取ったら直ちに画像のようなものを表示することができるようになります。

レスポンスを受け取った直後の状態の比較

ネットワークが不安定だったり遅いところでは、APIをリクエストする回数を減らすのが重要になります。 このアプリでは、レシピを探すという行動は重要な体験になってきます。ユーザーはレシピ間を行ったり来たりするので、そこのストレスをなるべく少なくするために、レシピ検索のAPIでレシピ詳細画面で必要なデータを返すようにして、ユーザーの行動を遮らないようにしています。 この最適化は部分レスポンスによる最適化とのトレードオフですか?という質問がありましたが、必要な情報は取得するようにして、不必要な情報は削るようにするということなので、トレードオフということではありません。

これまで説明してきた最適化は、UXを最大化することを目的で行っています。 どのようにデータを表示するか、どのデータが必要でどのデータが必要でないかは、実際のユースケースに依存するので、ユーザーに一番近いところで開発しているモバイルエンジニアにとって、APIやネットワークのスキルがこれからますます重要になってくるという話で締めました。

海外のカンファレンスに参加する

私のチームは世界各地に分散しており、朝会、チャット、GitHubはどれも英語で行われていますが、私はチームの中でも私が一番英語が苦手だったので、英語の勉強をしにいくつもりで応募しました。

Droidcon Italyについて

参加者数は、去年が400人くらいだったのに対して、今年は800人以上に増えたそうなので、開発者の注目が集まっているということが分かります。 セッションの内容を振り返ると、DIやMV*やリアクティブなどのアーキテクチャの話、EspressoやRobotiumやRobolectricなどのテストの話、プロトタイピングやマテリアルデザインやブランディングなどのデザインの話など、界隈のホットな話を聞くことができました。 イタリアでも「iOSと同じデザインで作ってくれと言われて困っている」と言っていて、日本とあまり変わらないんだなと思って親近感が湧きました。

Keynoteの様子

カンファレンスで発表するためにしたこと

Abstractをちゃんと書く

勢いで書いたら通ったー!という訳ではなく、海外で話すのは今回が初めてだったので慎重に調べて応募しました。

英語論文の書き方やテクニカルライティングの本を読んで勉強して、英語が得意な人に何回か添削してもらいながら、一ヶ月ほど掛けてAbstractを書きました。

後で知ったことですが、トークを採択するときは、その人のTwitterやブログやGitHubや他のカンファレンスの登壇の経験の有無から判断するらしいです。 私はTwitterやブログは日本語でやっているので不利であるということと、採択されたあとも参加者はAbstractを読んでセッションを決めるので重要(なんとか採択されてもAbstractが微妙だと部屋が埋まらなくて寂しい思いをしたり)なので、Abstractは時間を掛けて作る価値があったと思いました。

発表の練習をする

まず、Abstractをもとに原稿を書き起こしました。そのあとにスライドを作り始めると同時に英語の発音の練習も始めましたが、すぐに発音がうまくなるとは思わなかったので、なるべくスライドに文字を入れつつも、文字で埋めすぎないような調整をしました。 喋り以外の方法を使ってでも伝えなければと思って、たとえばキューに入れられたタスクがライフサイクルの変化とともに、どのようにスケジューリングされるのか、の説明でアニメーションを使ったりしています。 そして、スライドが出来たら頭に入るまで繰り返し声に出して練習をしました。

当日は部屋に入りきらないくらいに人が来てくれて、終わったあとに「素晴らしいトークをありがとう」と言ってもらえたので良かったです。

おわりに

細かいパフォーマンスのtipsを共有しました。パフォーマンスチューニングに没頭すると局所最適にハマってしまいがちなのですが、実際にはユーザーが使うときにどう見えるかと考えながら全体の設計をしていくという話をしました。

弊社には海外のカンファレンスでの発表の経験者が多くいる中で、経験の浅い私が知見と称してブログを書くのは気が引けましたが、最初のハードルを越えるのは大変だと思うので、そういう人のためになればと思います。

ちなみに日本では今週末の4/25に DroidKaigi ですね。運営に携わっている人からは日本でもDroidconのようなコミュニティを作っていきたいという話を聞いておりますので、参加して一緒に盛り上げていきましょう。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://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('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/