Android デバイスは、メモリや CPU のパフォーマンスに制限があります。そのため、Android はメモリと CPU リソースを無制限に使用することはできず、メモリの過剰使用はメモリの枯渇(OOM)を引き起こし、CPU リソースの過剰使用はスマートフォンの遅延やプログラムの応答なしの状態(ANR)を引き起こします。そのため、Android プログラムのパフォーマンスの問題は非常に重要です。これは、通常のコーディング中にパフォーマンスの最適化に注意する必要があることを意味します。最適化の前提条件は、どの状況がパフォーマンスの問題を引き起こす可能性があるかを明確に知ることです。以下に、いくつかのパフォーマンス最適化の方法をまとめます。
1. レイアウトの最適化#
レイアウトの最適化のアイデアは非常にシンプルで、レイアウトファイルの階層をできるだけ減らし、レイアウトの階層を減らすことで描画の作業量を減らすことです。レイアウトの最適化を行う方法は次のとおりです。
-
まず、レイアウト内の不要なコントロールと階層を削除します。次に、ViewGroup を選択的に使用します。LinearLayout と RelativeLayout の両方で実現できる場合は、LinearLayout を選択します。RelativeLayout は機能が複雑で、レイアウトのプロセスにより多くの CPU 時間がかかります。また、ネストの削減はよく使用される方法であり、最も直感的な方法です。
-
<include>
タグ、<merge>
タグ、およびViewStub
を使用します。<include>
タグはレイアウトの再利用に使用され、<merge>
タグはネストの削減に使用され、<include>
タグと組み合わせて使用するか、またはカスタムビューで使用します。ViewStub
は必要に応じてロードする機能を提供し、非常に軽量で幅と高さが 0 なので、レイアウトや描画には参加しません。多くのレイアウトは通常表示されず、特定のシナリオでのみ表示されるため、表示する必要がある場合にのみロードすれば、初期化のパフォーマンスが向上します。
2. 描画の最適化#
描画の最適化は、View の onDraw メソッドで大量の操作を避けることを意味します。主に 2 つの側面で表れます。
-
まず、onDraw 内で新しいローカルオブジェクトを作成しないでください。onDraw は頻繁に呼び出されるため、一瞬で大量の一時オブジェクトが生成され、これはメモリを大量に消費するだけでなく、頻繁な GC を引き起こし、プログラムの実行効率を低下させます。
-
次に、onDraw メソッドで時間のかかるタスクを行わないでください。大量のループ処理を実行しないでください。各ループが非常に軽量でも、大量のループは依然として CPU の時間スライスを大量に占有し、View の描画プロセスがスムーズでなくなります。Google の公式パフォーマンス最適化基準では、View の描画フレームレートを 60fps に保つことが最適であり、それには各フレームの描画時間が 16ms(16ms = 1000/60)を超えてはいけないとされています。
3. メモリリークの最適化#
メモリリークは、開発者の経験と開発意識に高い要求を課すため、最もよく犯されるエラーの 1 つでもあります。メモリリークの最適化には、コード内のメモリリークを回避することと、いくつかの分析ツールを使用して潜在的なメモリリークを見つけて解決することの 2 つの側面があります。メモリリークが発生しやすいコーディングの状況をいくつか挙げます。
-
静的変数によるメモリリーク
たとえば、
Context
やView
が静的変数であり、内部で Activity を保持しているため、Activity が閉じられても解放されない場合があります。 -
シングルトンパターンによるメモリリーク
上記と同様に、直接オブジェクトにアクセスすることで Activity が保持され、解放できなくなります。もう 1 つの明らかな例は、登録方法のみであり、登録の解除がない場合もメモリリークが発生します。シングルトンの特徴は、そのライフサイクルが Application と一致するため、メモリリークが発生する可能性があります。
-
プロパティアニメーションによるメモリリーク
プロパティアニメーションは無限ループのアニメーションの一種です。このようなアニメーションを Activity で再生し、onDestory で停止しない場合、アニメーションはずっと再生され続けます。なぜなら、Activity がアニメーション View を保持しているためです。最終的には Activity が解放されなくなります。解決策は、アニメーションを適時停止することです。
メモリリークの分析ツールは多数あります。例えば:
-
Android Studio に組み込まれたプロファイラ
CPU、メモリ、ネットワークの変化を直感的に表示でき、多くのシミュレーション操作も行えますが、メモリリークを明確に表示するのは難しく、MTA と組み合わせて使用する必要があります。
-
MAT
MAT(Eclipse Memory Analyzer)は、強力なメモリリーク分析ツールです。
-
Android LeakCanary
Android LeakCanary は簡単に統合でき、メモリリークを自動的に検出することができます。現在、私たちのプロジェクトでも LeakCanary を使用しています。使用方法についてはドキュメントを参照してください。
これらのツールは、メモリリークの位置を分析および特定するのに役立ちますが、問題が特定された後に問題を解決するために使用する必要があります。
4. 応答速度の最適化#
主な考え方は、メインスレッドでの時間のかかる操作を避けることです。時間のかかる操作がある場合は、それをサブスレッドで実行する必要があります。応答速度が遅い場合、主に Activity の起動速度に現れます。メインスレッドが多くの作業を行うと、画面が真っ暗になったり、ANR が発生する可能性があります。
- 時間のかかる操作をサブスレッドで実行する
- ビジネスの最適化、新しいアルゴリズムの使用、コードのリファクタリング、過去の負債の償還など
- trace.txt ファイルを分析して ANR の原因を特定する
- メインスレッドとサブスレッドが同期ロックを競合する場合、サブスレッドがメインスレッドが必要とするロックを保持している
5. RecyclerView と Bitmap の最適化#
- onBindView 内での時間のかかる操作を避ける
- リストのスクロール状態に応じてタスクの実行頻度を制御する。例えば、リストが高速にスクロールしている場合、大量の非同期タスクを開始するのは適切ではありません。
- Bitmap については、BitmapFactory.Options を使用して必要に応じて画像をサンプリングし、適切なサイズの画像を読み込むことで、Bitmap のサイズを減らすことができます。
6. スレッドの最適化#
スレッドの最適化は、スレッドプールを使用することで、プログラム内に大量のスレッドが存在することを避けることです。スレッドプールは内部のスレッドを再利用するため、スレッドの作成と破棄のコードのパフォーマンスオーバーヘッドを回避できます。また、スレッドプールはスレッドの最大同時実行数を効果的に制御できるため、大量のスレッドがリソースを争ってブロックすることを防ぐことができます。したがって、開発プロセスでは、スレッドを作成する代わりにスレッドプールを使用するように心がけてください。
7. 最適化の提案#
- 過剰なオブジェクトの作成を避ける
- 列挙型を過剰に使用しないでください。列挙型は整数よりも多くのメモリスペースを占有します
- 定数は static final で修飾してください
- Android 固有のデータ構造を使用してください。これらはより良いパフォーマンスを提供します
- 適切にソフト参照と弱参照を使用してください
- メモリキャッシュとディスクキャッシュを使用してください
- 静的内部クラスを使用するようにしてください。これにより、内部クラスによる潜在的なメモリリークが回避されます