Edisonでは、モバイルアプリケーションの最適化に頻繁に遭遇し、次の2つの問題のいずれかを解決する場合に非常に役立つと考えられる資料を共有したいと考えています。a)アプリケーションのスローダウンを少なくしたい。 b)大量のユーザーのために美しく、柔らかく、滑らかなインターフェースを作りたい。
Udi Cohenによる記事の翻訳の最初の部分に注目してください。これは、Android向けに最適化する若い同僚をトレーニングするためのガイドとして使用しました。
(
最初の部分を読む)
一般的なメモリのヒント
コードを記述するときに使用する簡単なガイドラインを次に示します。
- 列挙はすでにパフォーマンスについての激しい議論の問題です。 これは、リスティングが消費するメモリの量を説明するビデオと、このビデオと潜在的に誤解を招く情報の説明です。 列挙型は通常の定数よりも多くのメモリを使用しますか? 間違いなく。 悪いですか? 必ずしもそうではありません。 ライブラリを作成していて、強力な型安全性が必要な場合は、 @IntDefなどの他のソリューションと比較して、その使用を正当化できます。 一緒にグループ化できる定数の束がある場合、列挙型を使用することはあまり賢明な決定ではありません。 いつものように、決定を下す際に考慮する必要があるトレードオフがあります。
- ラッパーは、プリミティブ型からオブジェクト表現への自動変換です(たとえば、int-> Integer)。 各プリミティブ型はオブジェクト表現に「ラップ」され、新しいオブジェクトが作成されます(衝撃、私は知っています)。 このようなオブジェクトが多数ある場合、ガベージコレクターはより頻繁に呼び出されます。 ターン数に気付かないのは簡単です。なぜなら、プリミティブタイプのオブジェクトに割り当てると自動的に行われるからです。 解決策として、適切なタイプを使用してみてください。 アプリケーションでプリミティブ型を使用する場合は、実際に必要なくプリミティブ型をラップしないようにしてください。 メモリプロファイリングツールを使用して、プリミティブ型を表すオブジェクトを見つけることができます。 Traceviewを使用して、Integer.valueOf()、Long.valueOf()などを検索することもできます。
- HashMap対ArrayMap / Sparse * Array-ラッパーと同様に、HashMapを使用するにはオブジェクトをキーとして使用する必要があります。 アプリケーションでプリミティブなint型を使用する場合、HashMapと対話するときに自動的にIntegerにラップされます。この場合、SparseIntArrayを使用できます。 まだオブジェクトをキーとして使用する場合は、ArrayMapを使用できます。 どちらのオプションもHashMapよりも少ないメモリで済みますが、 動作が異なるため 、速度は低下しますが、メモリをより効率的に使用できます。 どちらの方法もHashMapよりもフットプリントが小さくなりますが、アイテムの取得またはメモリの割り当てにかかる時間はHashMapよりもわずかに長くなります。 1000個を超える要素がない場合、ランタイムの違いはそれほど大きくないため、これら2つのオプションは実行可能です。
- コンテキスト認識 -前述のとおり、メモリリークは比較的簡単に発生します。 アクティビティがAndroidのメモリリークの最も一般的な原因であることを知って驚かないかもしれません(!)。 リークには、UIのすべての階層表現が含まれており、それ自体が多くのスペースを占有するため、非常に高価です。 アクティビティで何が起こっているのかを必ず理解してください。 オブジェクトへのリンクがキャッシュ内にあり、このオブジェクトがアクティビティより長く存続している場合、このリンクをクリアせずにメモリリークが発生します。
- 非静的内部クラスの使用は避けてください 。 非静的内部クラスとそのインスタンスを作成すると、外部クラスへの暗黙的な参照が作成されます。 内部クラスのインスタンスが外部クラスよりも長期間必要な場合、外部クラスは不要になってもメモリ内に残ります。 たとえば、Activityクラス内でAsyncTaskを継承する非静的クラスが作成された後、新しい非同期タスクへの移行が行われ、それが継続している間にアクティビティが完了します。 この非同期タスクが継続している限り、アクティビティは維持されます。 解決策は簡単です。これを行わず、必要に応じて内部静的クラスを宣言します。
GPUプロファイリング
Android Studio 1.4に新しく追加されたのは、GPUレンダリングプロファイリングです。
Androidウィンドウの下の[GPU]タブに移動すると、画面上の各フレームの取得にかかった時間を示すグラフが表示されます。
グラフ上の各パネルは1つのレンダリングされたフレームを表し、色はプロセスのさまざまな段階を表します。
- Draw (青)-ビュー#onDraw()メソッドを表します。 この部分は、DisplayListオブジェクトを構築/更新し、GPUが理解できるOpenGLコマンドに変換します。 レートが高いのは、使用されている複雑な表現が原因であり、表示リストを作成するのにより多くの時間を必要とするか、短時間で多くのビューが無効になります。
- 準備 (紫)-UIのレンダリングを高速化するために、Lollipopに別のスレッドが追加されました。 RenderThreadと呼ばれます。 彼は、ディスプレイリストをOpenGLコマンドに変換し、GPUに送信する責任があります。 これが発生すると、UIスレッドは次のフレームの処理に進むことができます。 UIスレッドがすべての必要なRenderThreadリソースを転送するのにかかる時間は、この段階で反映されます。 表示リストが長い/重い場合は、このステップに時間がかかる場合があります。
- プロセス (赤)-OpenGLコマンドを作成するためのディスプレイリストの実行。 リストが長いか複雑な場合、多くの要素を再描画する必要があるため、このステップには時間がかかる場合があります。 ビューは、無効になるか、ビューを非表示にして移動した後に開かれたため、再描画できます。
- 実行 (黄色)-OpenGLコマンドをGPUに送信します。 CPUがコマンドバッファーをGPUに送信し、次のフレームのクリーンバッファーを取得するため、このステップには時間がかかる場合があります。 バッファの数には制限があり、GPUがビジー状態の場合、プロセッサは最初のバッファが空くまで待機します。 したがって、このステップで高い値が表示された場合、これはGPUがインターフェイスのレンダリングでビジーであったことを意味する可能性があり、短時間で描画するには複雑すぎる可能性があります。
メジャー/レイアウト、入力処理など、より多くのステップを表示するために、より多くの色がマシュマロに追加されました。
編集09/29/2015:GoogleのフレームワークエンジニアであるJohn Reckは、これらの色のいくつかについて次の情報を
追加しました 。
「アニメーションという言葉の正確な定義は、CholebackerにCALLBACK_ANIMATIONとして登録されているすべてです。 これには、choreographer#postFrameCallbackおよびView#postOnAnimationが含まれます。これらは、view.animate()、ObjectAnimator、Transitionsなどで使用されます。そして、これはsystraceの「animation」ラベルと同じです。
「Misc」は、vsyncタイムスタンプと受信時の現在のタイムスタンプの間の遅延です。 Choreographerの「blabla ms skipping blabla ms skip by blablaフレーム」からのログを既に表示している場合は、「misc」として表示されます。 framestatsダンプ(https://developer.android.com/preview/testing/performance.html#timing-info)では、INTENDED_VSYNCとVSYNCに違いがあります。
ただし、この機能の使用を開始する前に、開発者のメニューでGPUレンダリングモードを有効にする必要があります。
これにより、ツールはADBコマンドを使用して必要なすべての情報を取得できるようになります。
adb shell dumpsys gfxinfo <PACKAGE_NAME>
情報を得て、自分でチャートを作成できます。 このコマンドは、階層内の要素の数、すべての表示リストのサイズなどの有用な情報を表示します。 マシュマロでは、より多くの情報が得られます。
アプリケーションのUIのテストを自動化した場合、特定のアクション(リストのスクロール、重いアニメーションなど)の後にこのコマンドを開始するアセンブリを作成し、「ジャンキーフレーム」などの値に時間の変化があるかどうかを確認できます。 これは、コードにいくつかの変更を加えた後のパフォーマンスヒットの判断に役立ち、アプリケーションが市場に出る前に問題を修正できます。
ここに示すように、「framestats」キーワードを使用すると、さらに正確な情報を取得でき
ます 。
しかし、これがそのようなスケジュールを見る唯一の方法ではありません!
開発者のメニュー「GPUレンダリングのプロファイル」で見ることができるように、「画面上にバーとして」オプションもあります。 彼の選択により、画面上の各ウィンドウのグラフと、16 msのしきい値を示す緑色の線が表示されます。
右側の例では、一部のフレームが緑の線を横切っていることがわかります。つまり、描画に16ms以上必要です。 青が支配しているように見えるため、描画する表現が多数あるか、複雑であることがわかります。 この場合、さまざまなタイプのビューをサポートするリボンをスクロールしています。 一部の要素は無効になり、一部は他の要素よりも複雑になります。 おそらく、いくつかのフレームがこの線を越えたのは、表示が難しい要素を捕らえたためでしょう。
階層ビューア
私はこのツールが大好きで、多くの人がこのツールを使用していないことを非常に残念に思っています!
Hierarchy Viewerを使用して、パフォーマンス統計を取得し、画面上で完全なビュー階層を表示し、要素のすべてのプロパティにアクセスできます。 また、すべてのトピックデータをダンプし、各スタイル属性に使用されているすべてのパラメーターを確認できますが、これは、階層ビューアーがAndroidモニターからではなくスタンドアロンで実行されている場合にのみ可能です。 アプリケーションレイアウトを作成し、それらを最適化する場合、このツールを使用します。
中央には、ビューの階層を反映したツリーがあります。 プレゼンテーションの階層は広くなる可能性がありますが、深すぎる(〜10レベル)場合、多大なコストがかかる可能性があります。 ビューがView#onMeasure()で測定されるたびに、またはその子孫がView#onLayout()に配置されるとき、これらのコマンドはこれらのビューの子孫に同じように適用されます。 RelativeLayoutおよび一部のLinearLayout構成など、一部のレイアウトは各ステップを2回実行し、それらがネストされている場合、パスの数は指数関数的に増加します。
右下隅には、レイアウトの「計画」が表示され、各ビューの場所が示されます。 ここで、またはツリーでビューを選択して、左側のすべてのプロパティを確認できます。 レイアウトを設計するとき、特定のビューが終了する場所で終了する理由がわからないことがあります。 このツールを使用すると、ツリーを追跡して選択し、プレビューウィンドウのどこにあるかを確認できます。 画面上の表現の最終的な寸法を見て面白いアニメーションを作成し、この情報を使用して要素を正確に配置できます。 他のビューによって意図せずにブロックされた、失われたビューを見つけることができます。
ビューごとに、レイアウトとそのすべての子孫の測定/作成/レンダリングに時間がかかりました。 色は、ツリー内の他の表現と比較して、これらの表現がどのように実行されるかを反映します;これは、弱いリンクを見つけるのに最適な方法です。 ビューのプレビューも表示されるので、ツリーを調べて、それらを作成する指示に従って、削除できる追加のステップを見つけることができます。 パフォーマンスに影響を与えるものの1つは、オーバードローと呼ばれます。
オーバードロー
GPUプロファイリングセクションでわかるように、GPUが多くの要素を描画する必要がある場合、グラフに黄色で表示される実行フェーズは完了するまでに時間がかかり、各フレームの描画に必要な時間が長くなります。 オーバードローは、赤い背景に黄色のボタンなど、何かの上に何かを描画するときに発生します。 GPUは、最初に赤い背景を描画し、次にその上に黄色のボタンを描画する必要があります。これにより、オーバードローが避けられなくなります。 オーバードローレイヤーが多すぎる場合、これがGPUがより集中的であり、16ミリ秒で十分な時間がない理由です。
開発者のメニューの「GPU Overdrawのデバッグ」設定を使用すると、この領域でのオーバードローの程度を示すために、すべてのオーバードローが着色されます。 オーバードローの程度が1x / 2xであれば、それでも問題ありません。小さな赤い領域でもいいですが、画面上に赤の要素が多すぎる場合は、おそらく問題があります。 いくつかの例を見てみましょう。
左側の例では、緑色のリストがありますが、これは通常は正常ですが、上部に画面を赤くするオーバードローがあり、これはすでに問題になっています。 右側の例では、リスト全体が明るい赤です。 両方の例には、2x / 3x度のオーバードローを持つ不透明なリストがあります。 これらは、ウィンドウ内の画面全体の背景色がアクティビティまたはフラグメントと重複している場合、およびリストビューとその各要素の表示にある場合に発生する可能性があります。
注:デフォルトのテーマは、ウィンドウ全体の背景色を設定します。 画面全体を覆う不透明なレイアウトのアクティビティがある場合、このウィンドウの背景を削除して、オーバードローレイヤーを1つ削除できます。 これは、トピックまたはコードでgetWindow()を呼び出すことで実行できます。onCreate()でSetBackgroundDrawable(null)を呼び出します。
階層ビューアを使用すると、すべての階層レイヤーをPSDファイルにエクスポートして、Photoshopで開くことができます。 さまざまなレイヤーを調査した結果、レイアウト内のすべてのオーバードローが明らかになります。 この情報を使用して、過剰なオーバードローを削除し、緑ではなく青を目指してください!
アルファ
透明度の使用はパフォーマンスに影響する可能性があります。その理由を理解するために、ビューでalphaパラメーターが設定されたときに何が起こるかを見てみましょう。 次の構造を考慮してください。
互いにオーバーラップする3つのImageViewを含むレイアウトが表示されます。 直接実装では、setAlpha()でアルファを設定すると、すべての子孫ビュー(この場合はImageView)に伝搬するコマンドが呼び出されます。 次に、これらのImageViewは、フレームバッファーのalphaパラメーターで描画されます。 結果:
これは私たちが見たいものではありません。
各ImageViewはアルファ値でレンダリングされたため、すべての重複する画像がマージされます。 幸いなことに、OSにはこの問題に対する解決策があります。 レイアウトは別のオフスクリーンバッファにコピーされ、アルファはこのバッファ全体に適用され、結果はフレームバッファにコピーされます。 結果:
しかし...私たちはこれに代価を払います。
フレームバッファーで行われる前にオフスクリーンバッファーでビューを追加レンダリングすると、別の不可視のオーバードローレイヤーが追加されます。 OSは、以前にこのアプローチまたは直接的なアプローチをいつ使用するかを正確に知らないため、デフォルトでは常に1つの複雑なアプローチを使用します。 ただし、アルファを設定し、オフスクリーンバッファを追加する難しさを回避する方法はまだあります。
- TextViews -setAlpha()の代わりにsetTextColor()を使用します。 テキストにアルファチャネルを使用すると、テキストが再描画されます。
- ImageView -setAlpha()の代わりにsetImageAlpha()を使用します。 TextViewと同じ理由。
- カスタムビュー -カスタムビューが重複をサポートしない場合、この完全なソリューションは重要ではありません。 上記の例に示すように、子孫ビューを結合する方法はありません。 hasOverlappingRendering()メソッドをオーバーライドしてfalseを返すことにより、ビューへのパスを直接取得するようOSに通知します。 また、trueを返すようにonSetAlpha()メソッドをオーバーライドすることにより、アルファを設定するときに起こることを手動で処理するオプションもあります。
ハードウェアアクセラレーション
Honeycombにハードウェアアクセラレーションが含まれていたとき、画面にアプリケーションを表示する新しいレンダリングモデルを取得しました。 これには、画像取得を高速化するためのビューレンダリングコマンドを記述するDisplayList構造が含まれます。 しかし、開発者が通常忘れる別の興味深い機能があります-プレゼンテーションレベル。
これらを使用して、オフスクリーンバッファにビューを描画し(アルファチャネルを使用したときに見たように)、必要に応じて操作できます。 複雑なビューをより速くアニメーション化できるため、これはアニメーションに最適です。 レベルがないと、アニメーションプロパティ(x座標、スケール、アルファ値など)を変更した後、プレゼンテーションアニメーションにアクセスできなくなります。 複雑な表現の場合、この機能は子孫の表現にまで及ぶため、子孫は自分自身を再描画しますが、これは高価な操作です。 プレゼンテーションレベルを使用し、ハードウェアに依存して、プレゼンテーションのテクスチャをGPUで作成します。 x / y軸に沿った位置の変更、回転、アルファなど、テクスチャを置き換える必要なくテクスチャに適用できる操作がいくつかあります。 これは、アニメーション中にそれらを置き換えることなく、画面上の複雑なビューをアニメーション化できることを意味します! これにより、アニメーションがスムーズになります。 これを行う方法を示すサンプルコードを次に示します。
ちょうどいい?
はい。ただし、ハードウェアレベルを使用する際に留意すべき点がいくつかあります。
- ビューの実行後にクリーンアップを実行します-ハードウェアレイヤーは、GPUの限られたメモリスペースのスペースを使用します。 アニメーションなどで必要な場合にのみ使用し、その後クリーニングを実行してください。 上記のObjectAnimatorの例では、withLayers()メソッドを使用しました。このメソッドは、最初にレイヤーを自動的に作成し、アニメーションが終了すると削除します。
- ハードウェアレベルを使用した後にビューを変更すると、不適切になり、オフスクリーンバッファでビューを再生する必要があります。 これは、ハードウェアレベルに対して最適化されていないプロパティが変更されたときに発生します(現在最適化されている:回転、スケーリング、x / y軸に沿った移動、アルファ)。 たとえば、ハードウェアレベルを使用してビューをアニメーション化し、背景色を更新しながら画面上でビューを移動すると、ハードウェアレベルで絶えず更新されます。 これには、その使用を不当にする可能性のある追加費用が必要です。
2番目のケースでは、この更新を視覚化する方法があります。 開発者の設定を使用して、「ハードウェアレイヤーの更新を表示」を有効にできます。
その後、ハードウェアレベルで更新すると、ビューが緑色で強調表示されます。 しばらく前に、期待したスムージングでページを反転させないViewPagerがあったときにこれを使用しました。 このオプションをオンにした後、私は戻ってViewPagerをめくってみました。これは私が見たものです:
どちらのページもスクロール全体で緑色でした!
これは、ハードウェアレベルがページの最適化に使用され、ViewPagerをめくるとページが使用できなくなったことを意味します。 ページを更新し、バックグラウンドで視差効果を使用してページをめくると、ページ上の要素を徐々にアニメーション化しました。 私がしなかったことは、ViewPagerページのハードウェアレイヤーを作成しなかったことです。 ViewPagerコードを調べたところ、ユーザーがスクロールを開始すると、両方のページがハードウェアレベルで作成され、スクロールの終了後に削除されることがわかりました。
ハードウェアレベルを使用してページをスクロールすることは理にかなっていますが、私の場合、これは適合しませんでした。 通常、ViewPagerをスクロールしてもページは変更されません。また、ページは非常に複雑になる可能性があるため、ハードウェアレベルにより、レンダリングがはるかに高速になります。 私が取り組んでいたアプリケーションでは、そうではなかったので、
自分で書いた小さな
ハックを使用してこのハードウェア層を削除しました
。ハードウェア層は特効薬ではありません。 それがどのように機能するかを理解し、適切に使用することが重要です。さもないと、大きな問題にぶつかる可能性があります。
自分でやる
ここに示すすべての例の準備として、これらの状況をシミュレートするために多くのコードを書きました。 これらは
、Githubリポジトリと
Google Playで見つけることができます。 さまざまなアクティビティのスクリプトを分割し、アクティビティを使用しているときに発生する可能性のある問題の種類を理解できるように、できるだけスクリプトを文書化しようとしました。 アクティベーションドキュメントを読み、ツールを開いて、アプリケーションを操作します。
詳細情報
Android OSの進化に伴い、アプリケーションを最適化する方法があります。 Android SDKに新しいツールが導入され、OSに新しい機能が追加されました(ハードウェアレベルなど)。 何かを変えることを決める前に、新しいことを学び、妥協を学ぶことが非常に重要です。
Android Performance Patternsと呼ばれる素晴らしいYouTubeプレイリストがあります。パフォーマンスに関するさまざまなことを説明するGoogleの短いビデオがたくさんあります。 異なるデータ構造(HashMapとArrayMap)の比較、ビットマップの最適化、さらにはネットワーク要求の最適化についても見つけることができます。 それらすべてを見ることを強くお勧めします。
Android Performance Patterns Google+コミュニティに参加して、Googleの従業員を含む他のメンバーとパフォーマンスについて話し合い、アイデア、記事、質問を共有してください。
他の興味深いリンク
- Androidのグラフィックアーキテクチャの仕組みをご覧ください。 AndroidでのUIのレンダリングについて知っておく必要があるすべてのものがあり、SurfaceFlingerなどのさまざまなシステムコンポーネントと、それらが相互にどのように相互作用するかについて説明します。
- Google IO 2012のトークでは 、レンダリングモデルの仕組みと、UIがレンダリングされるときにガベージを取得する方法と理由を示しています。
- Devoxx 2013のAndroid Performance Workshopトークでは、レンダリングモデルでAndroid 4.4で行われた最適化の一部を示し、パフォーマンスを最適化するためのさまざまなツール(Systrace、Overdrawなど)を紹介します。
- Preventive Optimizationに関する素晴らしい投稿と、Premature Optimizationとの違い。 多くの開発者は、この変更はマイナーだと考えているため、コードを最適化しません。 あなたが覚えておくべき一つの考えは、全部でこれがすべて大きな問題になるということです。 コードのごく一部のみを最適化する機会がある場合、これは取るに足らないように見えるかもしれませんが、私はそれを除外しません。
- Androidのメモリ管理は、Google IO 2011の古いビデオであり、今でも関連しています。 Androidがアプリケーションのメモリを管理する方法、およびEclipse MATなどのツールを使用して問題を特定する方法を示します。
- 人気のあるTwitterクライアントを最適化するためのGoogleエンジニアのRomain Guyによるケーススタディ 。 この研究では、Romainがアプリケーションのパフォーマンスの問題をどのように発見したか、そしてそれらを修正するために何をすることを推奨するかを示しています。 再設計後のその他の問題を示す次の投稿です。
今日、アプリケーションの最適化を開始するのに十分な情報と自信があることを願っています!
トレースを開始するか、他の利用可能な開発者オプションのいくつかを有効にして、それらから始めます。 コメントであなたの考えを共有することをお勧めします。