その他

成功するPhoneGapアプリを開発するための高速化&UXテクニック

興味深いブログ記事が海外で掲載されていました。拙訳で恐縮ですが紹介したいと思います。

内容はPhoneGapアプリを高速化するための手法の解説で、具体的な事例とともに、いくつかのテクニックの紹介が行われています。少し長い記述になりますが、是非PhoneGapやMonacaを用いた開発の参考にしてください。


成功するPhoneGapアプリを開発するための高速化&UXテクニック

Performance & UX Considerations For Successful PhoneGap Apps

PhoneGapアプリを開発する方から、下記のような質問をよく尋ねられます。

  • ・アプリを高速化する方法は?
  • ・どうやってネイティブアプリのような質感を出せるか?
  • ・プラットフォームに違和感のないアプリを作るためのテクニックは?
  • ・OSのルック&フィールとマッチさせるためには?

この記事では、素晴らしいPhoneGapを開発するためのテクニックを紹介しつつ、「interweb(Webを取り込んだアプリ)」にまつわる疑問や迷信を明らかにしたいと考えています。

アプリ速度とは、エンドユーザーが感じるパフォーマンスであり、アプリの反応性を意味します。PhoneGapアプリはHTMLとWebViewで構成されているため、各OSプラットフォームが持つWebViewの速度に依存することになります。これは決してHTMLベースのアプリが本質的に遅いという意味ではありません。事実、HTMLベースのUIで成功したアプリは数多くあります。また、WebView自体を高速化することは困難ですが、HTML実行を高速化することは可能です。

一方で、もしWebViewが十分に高速でないと感じる場合は、その部分だけネイティブで組み合わせることが可能です。こうすることで、ネイティブUIを用いながら、必要に応じてPhoneGapによるHTMLとJavaScriptのUIを組み合わせることが可能です。

HTMLとWebViewのパフォーマンスについて

まずは、モバイルにおけるHTMLとWebのパフォーマンスについて紹介します。ここには多くのテクニックが蓄積されており、そのなかには大きく効果を発揮する内容が多くあります。

ハードウェアアクセラレーション

多くの記事で、可能な限りハードウェアアクセラレーションを強制することを推奨する記述があります。その具体的な方法は、下記のようにCSSのtransformのスタイルにtranslate3dを用いるというものです。

transform: translate3d(0, 0, 0);

こうすることでHTML DOMの描画にGPUが用いられるようになります。一方で、この設定がアプリケーションにどの程度のパフォーマンス向上を与えるか、理解しておく必要があります。アプリケーションの速度が大きく向上することもあるでしょう。しかしながら、時としてパフォーマンスに問題が発生し、その原因を突き止めるのに非常に苦労する場合もあります。そのため、あまり考えずに本設定をすべてのHTML DOM要素に対して適用しないことが重要です。

translate3dを用いた場合、GPU上のメモリーに描画内容が格納されます。過度にHTML DOM要素の描画にGPUを用いると、GPU上の利用可能メモリーが埋まってしまう可能性があります。こうなると、突然アプリケーションがエラーメッセージも表示せずクラッシュしてしまうか、GPUのメモリースワップが端末メモリーや記憶媒体に対して発生してしまいます。いずれの場合も、GPUを用いない場合と比べて大幅にパフォーマンスが低下してしまいます。

また、translate3dを用いたtransform操作を適用するたびに、HTML DOM要素の描画データがGPUに転送されます。通常この処理にかかる時間は微々たるもので、気にする必要はありません。しかし、大きく複雑なHTML DOM要素に対してtranslate3dを使用した場合は、最新の端末とOSを使った場合においても、体感できる程度の遅延が発生することになります。HTML DOM要素が複雑になるほど、この遅延は大きくなります。その結果、転送中はWebViewのUIスレッドは完全にロック状態となります。これまで私が経験したなかで1秒を超えるケースはありませんが、500ミリ秒のUIロックでも深刻なマイナスの印象を与えてしまいます。さらに、DOM要素に変更を加えた場合、その部分が再度GPUに転送されることになります。

translate3dを用いる場合には、DOM要素をネストさせることも控えましょう。たとえば、

要素内に大量のDOM要素が配置されており、先頭の

要素を含めたすべての要素がtranslate3dを用いてGPU描画を有効にしている例を考えます。この場合、先頭の

要素を描画するためには、その子供の要素が描画される必要があり、さらにその子供が描画され・・・という具合に連鎖していきます。要するに、親要素を描画する前に、すべての子要素の内容がGPUにアップロードされる必要があります。その結果として、描画に時間がかかるだけでなく、GPUメモリー使用量の肥大化や最悪アプリケーションをクラッシュさせる原因となってしまいます。

また、GPUの最大テクスチャーサイズも考慮する必要があります。もしDOM要素がGPUのサポートする最大テクスチャーサイズ(横幅・縦幅のいずれか)よりも大きい場合、パフォーマンスの低下や品質の低下が発生します。translate3dを用いたアニメーションやスクロールの際に、HTML DOM要素の描画がちらついた経験はありませんか?これはDOM要素の縦横サイズがGPUのサポートしているテクスチャーサイズよりも大きいことが原因です。多くのモバイル端末の最大テクスチャーサイズは1024x1024ですが、OSにより異なります。iPad 2の場合は2048x2048で、iPad 3/4の場合は4096x4096となります。もし最大テクスチャーサイズが1024ピクセルで、DOM要素の高さが1025ピクセルだった場合、画面がちらついたり速度低下が発生したりします。

参考: OSによっては、ブラウザーの実装が原因で描画がちらつくことがあります。この場合、backface-visibilityというCSSプロパティーを設定することで解決する場合があります。詳しくはこちらの記事(英語)を参考にしてください。

最後に、モバイル端末の特徴を再確認することをお勧めします。一般的に、デスクトップPCと比べて、より低速なCPU、バス転送速度、そしてメモリー容量に制限されています。デスクトップPCでtranslate3dが問題なく動作しても、モバイル端末では同じように動作するとは限りません。また、プラットフォームによってハードウェアアクセラレーションの対応度に違いがあり、そのパフォーマンス結果はまちまちです。

translate3dを効果的に用いると、必ずパフォーマンス向上にとってプラスとなるでしょう。一方、使い方によってはマイナス要因となる可能性もあります。上手に活用し、必ず・必ず・必ず、実機でのテストを行いましょう。

コンテンツのリフローが与える影響

「リフロー」という言葉を初めて聞いた方は、本項目に注目してください。リフローとはブラウザーエンジンの処理内容の一つで、HTML DOM要素の位置や座標を計算する処理となります。たとえば、各DOM要素の横幅・縦幅の計算、テキストの改行処理、相対的な位置計算などがリフロー処理の例となります。

リフローの計算は負担が大きいものなので、効果的にパフォーマンス向上を実現するためには、リフロー処理の頻度を最小限に止めることが重要です。HTML DOM要素の横幅を変更するようなアニメーションを作成し、フレームレートが5fps程度となってしまうのは、このリフロー処理が原因と考えられます。

リフロー処理は、DOMの内容を変更する、DOM要素をリサイズする、CSSによる位置変更や余白の変更を行う、といった場合に実行されます。デスクトップPCでは気にならない程度の処理時間ですが、モバイル端末においてはパフォーマンス低下の大きな要因となります。しかし、静的なページでない限り、動的に変更する際のリフロー処理を完全に無くすことはできません。そのため、リフロー処理の負担を軽減する方法について紹介します。

リフロー処理に関する詳細は、下記の記事を参照するといいでしょう。

PhoneGapアプリ開発のテクニックを紹介する多くの記事で、「DOM要素を最小限にとどめること」や「深くネストしたHTMLを避ける」こと、CSSアニメーションや画像のプリロードを行うこと、などが推奨されています。これらはすべて、リフロー処理を最小限にとどめるためのテクニックとなります。

・DOM要素の数を減らす

DOM要素の数が減るほど、リフロー処理において測定・計算する対象要素が減ります。

・深くネストされたHTML DOM構造を避ける

HTML構造が深くなるほど、リフロー処理はより複雑になり、計算量が増大します。さらに、末端の要素における変更がすべての親の階層に至るリフロー処理を発生させ、より多くの計算量が必要になります。

・CSS transformを用いる

先述のハードウェアアクセラレーションに関するテクニックに加え、CSS3のtransformを用いるとリフロー処理を行わずにHTML DOM要素を変更することができます。たとえば、X・Y・Z軸に対する変換や拡大・縮小処理、回転処理などです。

・CSS AnimationとCSS Transitionを用いる

CSS AnimationとTransitionを用いると、一気に高速化されます。しかし、すべての場合で有効である訳ではありません。リフロー処理を引き起こすCSS操作を行った場合(たとえばwidthやheightプロパティーの変更など)パフォーマンスの低下が発生します。こういう場合は、上述のCSS transformを用いることで、リフロー処理を抑制できます。

・DOM要素に対して固定の幅と高さを設定する

コンテンツのサイズを変更しない場合には、リフロー処理は実行されません。これは<div>要素だけでなく、画像を読み込む際にも該当します。画像サイズが固定されず、画像の読み込みが行われた場合は、読み込み完了のタイミングでリフロー処理が実行されてしまいます。複数の画像が用いられている場合は、その都度リフロー処理が実行されます。

・CSSスタイルで用いられる画像を事前に読み込む

これには2つのメリットがあります。まず、画像が必要になった際に既に利用可能となります。これにより、表示遅延やちらつきを抑えることができます。そして、事前に画像などを読み込んでおくと、画像などが読み込まれた後に実行される2回目のリフロー処理(1回目はDOMが最初に計算された際、2回目は読み込みの際に発生します)を回避できます。

・HTML DOM要素を賢く使う

たとえばJavaScriptの配列で格納されたデータに対して、<table>要素を作成することを考えてみます。事前に%lt;table>要素を設置し、毎回のループで既存のDOMに各行を追加する処理は、非常にコストの高い処理となります。こういった場合は、まずはJavaScriptの配列から、%lt;table>要素内のHTML DOM要素を作成してしまいます。次に、そのループ完了後に%lt;table>要素を既存のHTML DOMに追加します。こうすることで、リフロー処理を最小限に抑えることができます。

このように、DOM要素のレイアウト計算や位置計算の処理を減らすことが、パフォーマンス向上につながります。

グラフィックはシンプルに

デザイナーの方はすばらしい見栄えのモックアップを作成しますが、それらのデザインに忠実に従ったアプリはパフォーマンス低下の原因となります。CSSシャドゥやCSSグラデーションを使いすぎると、プラットフォームやブラウザーによっては速度低下が発生します。たとえば、これらの効果はiOS端末と比較して、Android端末のパフォーマンス低下が顕著です。

タッチ操作

「マウスイベントは遅くタッチイベントは速い」という内容を聞いたことがあるでしょう。これは事実で、「mousedown」「mousemove」「mouseup」もしくは「click」イベントを使わず、「touchstart」「touchmove」そして「touchend」イベントを利用しましょう。

モバイル端末では、OSレベルでマウスイベントの低下が発生します。OSはジェスチャーが発生したかどうかを識別します。そしてジェスチャーが発生していない場合、マウスイベントとしてそのイベントをWebViewに渡します。タッチイベントを用いると、OS側の遅延なくWebViewにイベントが伝わります。

ただし「click」に相当するタッチイベントが欠如しています。自作することも可能ですし、「taps」というイベントを発生させるライブラリーを用いることも一考です。下記のようなライブラリーが対応しています:ZeptoFastClickHammer.jsiScroll

JavaScriptの最適化

本質的に効率的なコードを記述すると、UIスレッドをブロックすることはありません。JavaScriptの最適化に関する記事をお読みいただくといいでしょう。

アプリを実装する際は、いろいろな最適化手法を考慮する必要があります。友達のプログラマー仲間にコードレビューを依頼するなどして、より良いコードになるよう努力してください。

ネイティブのパフォーマンス

ネイティブUIを用いたアプリ開発を行いたいけれども、HTMLでその内容を記述したいとしましょう。実は、PhoneGapがネイティブアプリのサブビューとして利用できます。

この手法はCordovaViewをネイティブアプリのサブビューとして使うものですが、ネイティブコードの開発経験が要求されます。これによりPhoneGapやHTMLのUIを活用しつつ、ネイティブコンポーネントを利用することもできます。

HTMLを用いたカスタムUIの実装はとても簡単です。CordovaViewを用いる方法は、各ネイティブとHTMLの強みを組み合わせることができます。

UIとUXに関して

開発者は優れたユーザー体験を提供することに努力したいと思っているはずです。そのなかで、よくある質問に対する私の回答を紹介します。その質問とは「私のアプリをどうやって、ネイティブアプリのルック&フィールと同一のものにできますか?」というものです。

私のこれに対する回答は、同一にしない、です。

これは、特定のプラットフォームに対して「近い動きをする」アプリを開発してはいけないという意味ではありません。私が伝えたいのは、すべての細かい挙動までOSに合わせる努力をしない方がいいということです。これは、1)とても困難であるため2)OS側での変更が発生すると、あなたのアプリケーションの不一致がより大きなものとなるため、という理由からです。

「不気味の谷」現象という言葉をご存知でしょうか。PhoneGapアプリを開発する際、ネイティブアプリと同一の挙動を作りこむほど、細かい差異が「何か違う」「何かがおかしい」と思うようになってしまいます。これがユーザーにとってマイナスのイメージにつながります。

そのため、アプリが固有のUIを実装することを推奨します(App Storeのガイドラインに適合する範囲である必要があります)。たとえば、ルック&フィール、ボタンのスタイル、ナビゲーションなどが該当します。すべての側面からネイティブUXを目指すのではなく、あなたのアプリが提供する固有のブランドをUXとして提供しましょう。利用者はあなたのアプリに対して唯一の評価とし、ネイティブOSとの比較は行わないでしょう。

一方で、もしネイティブのルック&フィールを実現する場合は、CSSスタイルを用いることで完全に再現することができます。

実機でテストする

非常に重要なことです。常に、実機でテストを行いましょう。必ず、です。私は旧機種のテストを行います。それは、旧機種で速度が出せた場合、新機種ではより高速に動作するためです。対象となるすべての端末に対して、テストを行いましょう。私の場合、iOSではiPhone 4(4Sではありません)とiPad 2を対象としています。AndroidではMotorola Atrix、Nexus 7タブレット、Kindle Fire(第一世代)、そしてSamsung Galaxy 10.1(第一世代)でテストを行います。デバイスを借りてほかのプラットフォームに対するテストを行うこともあります。さらに、店舗に出向き展示端末にアプリをインストールして、アプリの見た目を確認することもあります。


翻訳した感想

日本語におけるPhoneGapアプリ開発のノウハウが、英語での情報と比べてまだまだ少ないと実感しています。今後、目にとまったテクニックを随時紹介していきたいと感じました。

記事に触れられている「ネイティブUIの活用」ですが、弊社が提供するPhoneGap開発環境であるMonacaでは実装されています。興味のある方は、こちらのドキュメントを是非ご覧ください。

前の記事へ

次の記事へ

一覧へ戻る

「その他」カテゴリの最新記事

PAGE TOP