Androidアプリを最適化するためのヒントとツール
公開: 2022-03-11Androidデバイスには多くのコアがあるので、スムーズなアプリを作成することは誰にとっても簡単な作業ですよね? 違う。 Androidのすべてはさまざまな方法で実行できるため、最適なオプションを選択するのは難しい場合があります。 最も効率的な方法を選択したい場合は、内部で何が起こっているのかを知る必要があります。 幸いなことに、何が起こっているのかを測定して説明することでボトルネックを見つけるのに役立つツールがたくさんあるので、自分の気持ちや匂いに頼る必要はありません。 適切に最適化されたスムーズなアプリは、ユーザーエクスペリエンスを大幅に向上させ、バッテリーの消耗も少なくなります。
最適化が実際にどれほど重要であるかを検討するために、最初にいくつかの数値を見てみましょう。 Nimbledroidの投稿によると、ユーザーの86%(私を含む)は、パフォーマンスの低下により、アプリを1回だけ使用した後にアンインストールしました。 一部のコンテンツを読み込んでいる場合、ユーザーに表示するのに11秒もかかりません。 3人に1人のユーザーだけがあなたにもっと時間を与えるでしょう。 また、それが原因でGooglePlayで多くの悪いレビューを受け取る可能性があります。
すべてのユーザーが最初に何度も気付くのは、アプリの起動時間です。 別のNimbledroidの投稿によると、上位100のアプリのうち、40は2秒未満で起動し、70は3秒未満で起動します。 したがって、可能であれば、通常、コンテンツをできるだけ早く表示し、身元調査と更新を少し遅らせる必要があります。
時期尚早の最適化はすべての悪の根源であることを常に忘れないでください。 また、マイクロ最適化で多くの時間を無駄にしないでください。 頻繁に実行されるコードを最適化することの最大の利点がわかります。 たとえば、これにはonDraw()
関数が含まれます。この関数は、すべてのフレームを1秒間に60回実行するのが理想的です。 描画は世の中で最も遅い操作なので、必要なものだけを再描画してみてください。 これについては後で詳しく説明します。
パフォーマンスのヒント
十分な理論ですが、パフォーマンスが重要な場合に考慮すべきいくつかの事項のリストを以下に示します。
1.文字列とStringBuilder
文字列があり、何らかの理由でさらに文字列を1万回追加したいとします。 コードは次のようになります。
String string = "hello"; for (int i = 0; i < 10000; i++) { string += " world"; }
Android Studioモニターで、文字列の連結がいかに非効率的であるかを確認できます。 たくさんのガベージコレクション(GC)が進行中です。
この操作は、Android5.1.1を搭載したかなり優れたデバイスで約8秒かかります。 同じ目標を達成するためのより効率的な方法は、このようなStringBuilderを使用することです。
StringBuilder sb = new StringBuilder("hello"); for (int i = 0; i < 10000; i++) { sb.append(" world"); } String string = sb.toString();
同じデバイスで、これは5ミリ秒未満でほぼ瞬時に発生します。 CPUとメモリの視覚化はほぼ完全にフラットであるため、この改善がどれほど大きいかを想像できます。 ただし、この違いを実現するには、1万個の文字列を追加する必要がありますが、これはおそらく頻繁には行わないことに注意してください。 したがって、2つの文字列を一度だけ追加する場合、改善は見られません。 ちなみに、あなたがそうするなら:
String string = "hello" + " world";
内部でStringBuilderに変換されるため、正常に機能します。
不思議に思うかもしれませんが、文字列の連結が最初の方法で非常に遅いのはなぜですか? 文字列は不変であるため、一度作成すると変更できません。 文字列の値を変更していると思っていても、実際には新しい値で新しい文字列を作成しています。 次のような例では:
String myString = "hello"; myString += " world";
メモリに格納されるのは、1文字列の「helloworld」ではなく、実際には2文字列です。 ご想像のとおり、文字列myStringには「helloworld」が含まれます。 ただし、値が「hello」の元の文字列は、それを参照せずにまだ存続しており、ガベージコレクションを待機しています。 これは、パスワードを文字列ではなくchar配列に格納する必要がある理由でもあります。 パスワードを文字列として保存すると、予測できない期間、次のGCまで人間が読める形式でメモリに残ります。 上記の不変性に戻ると、文字列は、使用後に別の値を割り当てても、メモリに残ります。 ただし、パスワードを使用した後でchar配列を空にすると、どこからでも消えてしまいます。
2.正しいデータ型を選択する
コードの記述を開始する前に、コレクションに使用するデータ型を決定する必要があります。 たとえば、 Vector
またはArrayList
を使用する必要がありますか? まあ、それはあなたのユースケースに依存します。 一度に1つのスレッドのみを処理できるスレッドセーフなコレクションが必要な場合は、同期されているため、 Vector
を選択する必要があります。 他の場合では、ベクトルを使用する特別な理由が本当にない限り、おそらくArrayList
に固執する必要があります。
ユニークなオブジェクトを含むコレクションが必要な場合はどうでしょうか。 さて、あなたはおそらくSet
を選ぶべきです。 設計上、重複を含めることはできないため、自分で処理する必要はありません。 セットには複数の種類があるため、ユースケースに合ったものを選択してください。 一意のアイテムの単純なグループには、 HashSet
を使用できます。 挿入されたアイテムの順序を保持する場合は、 LinkedHashSet
を選択します。 TreeSet
はアイテムを自動的に並べ替えるため、並べ替えメソッドを呼び出す必要はありません。 また、並べ替えアルゴリズムを考えることなく、アイテムを効率的に並べ替える必要があります。
—ロブパイクのプログラミングの5つのルール
整数または文字列の並べ替えは非常に簡単です。 ただし、クラスをプロパティで並べ替える場合はどうでしょうか。 あなたが食べる食事のリストを書いていて、それらの名前とタイムスタンプを保存しているとしましょう。 タイムスタンプで食事を最低から最高にどのように並べ替えますか? 幸いなことに、それは非常に簡単です。 Meal
クラスにComparable
インターフェースを実装し、 compareTo()
関数をオーバーライドするだけで十分です。 食事を最低のタイムスタンプから最高のタイムスタンプで並べ替えるには、次のように記述します。
@Override public int compareTo(Object object) { Meal meal = (Meal) object; if (this.timestamp < meal.getTimestamp()) { return -1; } else if (this.timestamp > meal.getTimestamp()) { return 1; } return 0; }
3.場所の更新
ユーザーの位置情報を収集するアプリはたくさんあります。 そのためには、多くの便利な機能を含むGoogle LocationServicesAPIを使用する必要があります。 使用方法については別の記事がありますので、繰り返しません。
パフォーマンスの観点からいくつかの重要なポイントを強調したいと思います。
まず、必要に応じて最も正確な場所のみを使用します。 たとえば、天気予報をしている場合、最も正確な場所は必要ありません。 ネットワークに基づいて非常に大まかな領域を取得する方が高速で、バッテリー効率が高くなります。 これは、優先度をLocationRequest.PRIORITY_LOW_POWER
に設定することで実現できます。
setSmallestDisplacement()
と呼ばれるLocationRequest
の関数を使用することもできます。 これをメートル単位で設定すると、指定された値よりも小さい場合、アプリは場所の変更について通知されません。 たとえば、近くにレストランがある地図があり、最小変位を20メートルに設定した場合、ユーザーが部屋の中を歩き回っているだけであれば、アプリはレストランの確認を要求しません。 とにかく近くに新しいレストランがないので、リクエストは役に立たないでしょう。
2番目のルールは、必要な頻度でのみ位置情報の更新を要求することです。 これは非常に自明です。 あなたが本当にその天気予報アプリを構築しているなら、あなたはおそらくそのような正確な予報を持っていないので、あなたは数秒ごとに場所を要求する必要はありません(もしそうなら私に連絡してください)。 setInterval()
関数を使用して、デバイスが場所についてアプリを更新するために必要な間隔を設定できます。 複数のアプリがユーザーの場所を要求し続ける場合、 setInterval()
が高く設定されていても、新しい場所が更新されるたびにすべてのアプリに通知されます。 アプリに頻繁に通知されないようにするには、必ずsetFastestInterval()
を使用して最速の更新間隔を設定してください。
そして最後に、3番目のルールは、必要な場合にのみ位置情報の更新を要求することです。 x秒ごとに地図上に近くのオブジェクトを表示していて、アプリがバックグラウンドで動作する場合は、新しい場所を知る必要はありません。 ユーザーがマップを表示できない場合は、マップを更新する理由はありません。 必要に応じて、できればonPause()
で、位置情報の更新のリッスンを停止してください。 その後、 onResume()
で更新を再開できます。
4.ネットワークリクエスト
アプリがデータのダウンロードまたはアップロードにインターネットを使用している可能性が高いです。 そうである場合、ネットワーク要求の処理に注意を払う理由はいくつかあります。 そのうちの1つはモバイルデータです。これは非常に多くの人に限定されているため、無駄にしないでください。
2つ目はバッテリーです。 WiFiネットワークとモバイルネットワークはどちらも、使いすぎるとかなりの量を消費する可能性があります。 1kbをダウンロードしたいとします。 ネットワークリクエストを行うには、携帯電話またはWiFi無線をウェイクアップする必要があります。その後、データをダウンロードできます。 ただし、操作直後はラジオがスリープ状態になることはありません。 デバイスと携帯通信会社によって異なりますが、さらに約20〜40秒間かなりアクティブな状態になります。
それで、あなたはそれについて何ができますか? バッチ。 数秒ごとにラジオをウェイクアップしないようにするには、ユーザーが数分以内に必要になる可能性のあるものをプリフェッチします。 バッチ処理の適切な方法はアプリによって非常に動的ですが、可能であれば、ユーザーが必要とする可能性のあるデータを3〜4分以内にダウンロードする必要があります。 ユーザーのインターネットタイプまたは充電状態に基づいてバッチパラメータを編集することもできます。 たとえば、ユーザーが充電中にWiFiを使用している場合、ユーザーがバッテリーの少ないモバイルインターネットを使用している場合よりも多くのデータをプリフェッチできます。 これらすべての変数を考慮に入れることは難しいことであり、それはごく少数の人々だけが行うでしょう。 幸いなことに、GCMネットワークマネージャーが助けになります!
GCM Network Managerは、カスタマイズ可能な属性がたくさんある本当に便利なクラスです。 繰り返しタスクと1回限りのタスクの両方を簡単にスケジュールできます。 繰り返しタスクでは、最小の繰り返し間隔と最大の繰り返し間隔を設定できます。 これにより、リクエストだけでなく、他のアプリからのリクエストもバッチ処理できるようになります。 ラジオは、ある期間に1回だけウェイクアップする必要があります。起動している間、キュー内のすべてのアプリは、想定されているものをダウンロードしてアップロードします。 このマネージャーは、デバイスのネットワークタイプと充電状態も認識しているため、それに応じて調整できます。 この記事で詳細とサンプルを見つけることができます。ぜひチェックしてください。 タスクの例は次のようになります。
Task task = new OneoffTask.Builder() .setService(CustomService.class) .setExecutionWindow(0, 30) .setTag(LogService.TAG_TASK_ONEOFF_LOG) .setUpdateCurrent(false) .setRequiredNetwork(Task.NETWORK_STATE_CONNECTED) .setRequiresCharging(false) .build();
ちなみに、Android 3.0以降、メインスレッドでネットワークリクエストを行うと、 NetworkOnMainThreadException
が発生します。 それは間違いなくあなたにそれを二度としないように警告するでしょう。
5.リフレクション
リフレクションとは、クラスとオブジェクトが独自のコンストラクター、フィールド、メソッドなどを調べる機能です。 これは通常、特定のOSバージョンで特定のメソッドが使用可能かどうかを確認するための下位互換性のために使用されます。 その目的でリフレクションを使用する必要がある場合は、リフレクションの使用が非常に遅いため、必ず応答をキャッシュしてください。 依存性注入用のRoboguiceのように、広く使用されているライブラリの中にはReflectionも使用しているものがあります。 それがダガー2を好む理由です。リフレクションの詳細については、別の投稿を確認してください。
6.オートボクシング
オートボクシングとアンボクシングは、プリミティブ型をオブジェクト型に、またはその逆に変換するプロセスです。 実際には、intを整数に変換することを意味します。 これを実現するために、コンパイラはInteger.valueOf()
関数を内部的に使用します。 変換は遅いだけでなく、オブジェクトはそれらのプリミティブな同等物よりもはるかに多くのメモリを消費します。 いくつかのコードを見てみましょう。
Integer total = 0; for (int i = 0; i < 1000000; i++) { total += i; }
これには平均500ミリ秒かかりますが、オートボクシングを回避するために書き直すと、大幅に高速化されます。
int total = 0; for (int i = 0; i < 1000000; i++) { total += i; }
このソリューションは約2msで実行され、25倍高速です。 あなたが私を信じていないなら、それを試してみてください。 数値はデバイスごとに明らかに異なりますが、それでもはるかに高速であるはずです。 また、最適化するのも非常に簡単です。
さて、あなたはおそらくこのような整数型の変数を頻繁に作成しないでしょう。 しかし、回避するのがより難しい場合はどうでしょうか? マップのように、 Map<Integer, Integer>
のようなオブジェクトを使用する必要がありますか? 多くの人が使用するソリューションを見てください。
Map<Integer, Integer> myMap = new HashMap<>(); for (int i = 0; i < 100000; i++) { myMap.put(i, random.nextInt()); }
マップに100kのランダムなintを挿入すると、実行に約250ミリ秒かかります。 次に、SparseIntArrayを使用したソリューションを見てください。
SparseIntArray myArray = new SparseIntArray(); for (int i = 0; i < 100000; i++) { myArray.put(i, random.nextInt()); }
これははるかに少なく、およそ50msかかります。 また、複雑なことを行う必要がなく、コードも読みやすいため、パフォーマンスを向上させるためのより簡単な方法の1つです。 最初のソリューションでクリアアプリを実行すると13MBのメモリが必要でしたが、プリミティブintを使用すると7 MB未満になるため、その半分にすぎません。
SparseIntArrayは、オートボクシングを回避するのに役立つクールなコレクションの1つにすぎません。 マップの値はLong
型であるためMap<Integer, Long>
のようなマップはSparseLongArray
に置き換えることができます。 SparseLongArray
のソースコードを見ると、かなり興味深いものがあります。 内部的には、基本的には1対のアレイです。 同様にSparseBooleanArray
を使用することもできます。
ソースコードを読んだら、 SparseIntArray
がHashMap
よりも遅くなる可能性があるというメモに気づいたかもしれません。 私は多くの実験を行ってきましたが、私にとってSparseIntArray
は、メモリとパフォーマンスの両方の点で常に優れていました。 どちらを選択するかはまだあなた次第だと思います。ユースケースを試して、どれが最も適しているかを確認してください。 マップを使用するときは、間違いなくSparseArrays
を頭の中に入れてください。
7. OnDraw
上で述べたように、パフォーマンスを最適化する場合、頻繁に実行されるコードを最適化することでおそらく最もメリットがあります。 多く実行される関数の1つはonDraw()
です。 それが画面上にビューを描画する責任があることは驚くことではないかもしれません。 デバイスは通常60fpsで実行されるため、関数は1秒間に60回実行されます。 すべてのフレームには、準備と描画を含めて完全に処理される16ミリ秒があるため、遅い機能は避けてください。 画面に描画できるのはメインスレッドのみなので、コストのかかる操作は避けてください。 メインスレッドを数秒間フリーズすると、悪名高いApplication Not Responding(ANR)ダイアログが表示される場合があります。 画像のサイズ変更やデータベース作業などには、バックグラウンドスレッドを使用します。
コードを短くしようとしている人がいて、そうすればもっと効率的になると思っている人もいます。 コードが短いからといってコードが速くなるわけではないので、それは間違いなく進むべき道ではありません。 いかなる状況でも、行数でコードの品質を測定することはできません。

onDraw()
で避けるべきことの1つは、Paintのようなオブジェクトを割り当てることです。 コンストラクターですべてを準備して、描画する準備ができているようにします。 onDraw()
を最適化した場合でも、必要な頻度でのみ呼び出す必要があります。 最適化された関数を呼び出すよりも良いことは何ですか? ええと、関数をまったく呼び出さないでください。 テキストを描画したい場合は、 drawText()
と呼ばれる非常に優れたヘルパー関数があり、テキスト、座標、テキストの色などを指定できます。
8.ViewHolders
あなたはおそらくこれを知っていますが、私はそれをスキップすることはできません。 ビューホルダーのデザインパターンは、リストのスクロールをスムーズにする方法です。 これは一種のビューキャッシングであり、 findViewById()
の呼び出しを大幅に減らし、ビューを保存することでビューを膨らませることができます。 このように見えることがあります。
static class ViewHolder { TextView title; TextView text; public ViewHolder(View view) { title = (TextView) view.findViewById(R.id.title); text = (TextView) view.findViewById(R.id.text); } }
次に、アダプタのgetView()
関数内で、使用可能なビューがあるかどうかを確認できます。 そうでない場合は、作成します。
ViewHolder viewHolder; if (convertView == null) { convertView = inflater.inflate(R.layout.list_item, viewGroup, false); viewHolder = new ViewHolder(convertView); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.title.setText("Hello World");
あなたはインターネットの周りでこのパターンについて多くの有用な情報を見つけることができます。 また、一部のセクションヘッダーなど、リストビューに複数の異なるタイプの要素が含まれている場合にも使用できます。
9.画像のサイズ変更
おそらく、アプリにはいくつかの画像が含まれます。 WebからいくつかのJPGをダウンロードしている場合、それらは非常に大きな解像度を持つ可能性があります。 ただし、それらが表示されるデバイスははるかに小さくなります。 デバイスのカメラで写真を撮る場合でも、写真の解像度はディスプレイの解像度よりもはるかに大きいため、表示する前にサイズを小さくする必要があります。 画像を表示する前にサイズを変更することは非常に重要です。 それらをフル解像度で表示しようとすると、すぐにメモリが不足します。 Androidドキュメントにはビットマップを効率的に表示することについて書かれたものがたくさんあります。それを要約してみます。
つまり、ビットマップはありますが、それについては何も知りません。 サービスにはinJustDecodeBoundsと呼ばれるビットマップの便利なフラグがあります。これにより、ビットマップの解像度を確認できます。 ビットマップが1024x768であり、それを表示するために使用されるImageViewが400x300であると仮定します。 指定されたImageViewよりも大きくなるまで、ビットマップの解像度を2で割り続ける必要があります。 これを行うと、ビットマップが2倍ダウンサンプリングされ、512x384のビットマップが得られます。 ダウンサンプリングされたビットマップは4分の1のメモリを使用します。これは、有名なOutOfMemoryエラーを回避するのに大いに役立ちます。
あなたはそれを行う方法を知ったので、あなたはそれをすべきではありません。 …少なくとも、アプリが画像に大きく依存している場合はそうではなく、1〜2枚の画像だけではありません。 画像のサイズ変更や手動でのリサイクルなどは絶対に避けてください。そのためにサードパーティのライブラリを使用してください。 最も人気のあるものは、Picasso by Square、Universal Image Loader、Fresco by Facebook、または私のお気に入りのGlideです。 その周りには開発者の巨大な活発なコミュニティがあるので、GitHubの問題セクションでも多くの役立つ人々を見つけることができます。
10.厳密モード
厳密モードは、多くの人が知らない非常に便利な開発者ツールです。 これは通常、メインスレッドからのネットワーク要求またはディスクアクセスを検出するために使用されます。 厳密モードで検索する問題と、トリガーするペナルティを設定できます。 Googleのサンプルは次のようになります。
public void onCreate() { if (DEVELOPER_MODE) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork() .penaltyLog() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() .penaltyLog() .penaltyDeath() .build()); } super.onCreate(); }
厳密モードで検出できるすべての問題を検出する場合は、 detectAll()
を使用することもできます。 多くのパフォーマンスのヒントと同様に、厳密モードのレポートをすべてやみくもに修正しようとしないでください。 調査するだけで、問題がないと確信できる場合は、そのままにしておきます。 また、デバッグにのみ厳密モードを使用し、本番ビルドでは常に無効にしてください。
デバッグパフォーマンス:プロの方法
ここで、ボトルネックを見つけるのに役立つ、または少なくとも何かが間違っていることを示すのに役立ついくつかのツールを見てみましょう。
1.Androidモニター
これはAndroidStudioに組み込まれているツールです。 デフォルトでは、Androidモニターは左下隅にあり、そこで2つのタブを切り替えることができます。 Logcatとモニター。 [モニター]セクションには、4つの異なるグラフが含まれています。 ネットワーク、CPU、GPU、およびメモリ。 それらはかなり自明なので、すぐに説明します。 これは、ダウンロード時にJSONを解析しているときに取得したグラフのスクリーンショットです。
ネットワーク部分には、着信トラフィックと発信トラフィックがKB/秒で表示されます。 CPU部分は、CPU使用率をパーセントで表示します。 GPUモニターは、UIウィンドウのフレームをレンダリングするのにかかる時間を表示します。 これは、これら4つの中で最も詳細なモニターであるため、詳細が必要な場合は、こちらをお読みください。
最後に、おそらく最もよく使用するメモリモニターがあります。 デフォルトでは、現在の空きメモリと割り当て済みメモリの量が表示されます。 ガベージコレクションを強制的に使用して、使用済みメモリの量が減少するかどうかをテストすることもできます。 これには、ダンプJavaヒープと呼ばれる便利な機能があり、HPROFビューアーとアナライザーで開くことができるHPROFファイルを作成します。 これにより、割り当てたオブジェクトの数、何によってどのくらいのメモリが使用されているか、そしておそらくどのオブジェクトがメモリリークを引き起こしているかを確認できます。 このアナライザーの使用方法を学ぶことは、世の中で最も簡単な作業ではありませんが、それだけの価値があります。 メモリモニターで次にできることは、時間指定の割り当て追跡を実行することです。これは、必要に応じて開始および停止できます。 これは、デバイスをスクロールしたり回転させたりする場合など、多くの場合に役立ちます。
2.GPUオーバードロー
これはシンプルなヘルパーツールであり、開発者モードを有効にすると、開発者向けオプションでアクティブ化できます。 [GPUオーバードローのデバッグ]、[オーバードロー領域の表示]を選択すると、画面に奇妙な色が表示されます。 それは大丈夫です、それが起こることになっていることです。 色は、特定の領域が何回オーバードローされたかを意味します。 トゥルーカラーは、オーバードローがなかったことを意味します。これは、あなたが目指すべきものです。 青は1回のオーバードロー、緑は2回、ピンクは3回、赤は4回を意味します。
トゥルーカラーを表示するのが最適ですが、特にテキスト、ナビゲーションドロワー、ダイアログなどの周囲には、常にオーバードローが表示されます。 したがって、それを完全に取り除くことを試みないでください。 アプリが青みがかったまたは緑がかった場合は、おそらく問題ありません。 ただし、一部の単純な画面で赤が多すぎる場合は、何が起こっているのかを調査する必要があります。 置き換えるのではなく追加し続けると、互いに積み重ねられたフラグメントが多すぎる可能性があります。 上で述べたように、描画はアプリの中で最も遅い部分であるため、3つ以上のレイヤーが描画される場合、何かを描画する意味はありません。 お気に入りのアプリをお気軽にチェックしてください。 ダウンロード数が10億を超えるアプリでも赤い領域があることがわかります。最適化するときは、気楽に行ってください。
3.GPUレンダリング
これは、プロファイルGPUレンダリングと呼ばれる開発者向けオプションの別のツールです。 それを選択したら、「画面上でバーとして」を選択します。 画面に色付きのバーが表示されます。 すべてのアプリケーションには個別のバーがあるため、奇妙なことに、ステータスバーには独自のバーがあり、ソフトウェアナビゲーションボタンがある場合は、独自のバーもあります。 とにかく、画面を操作するとバーが更新されます。
バーは3〜4色で構成されており、Androidドキュメントによると、そのサイズは確かに重要です。 小さいほど良いです。 下部には青色が表示されます。これは、ビューの表示リストの作成と更新に使用された時間を表します。 この部分が高すぎる場合は、カスタムビューの描画が多いか、 onDraw()
関数で多くの作業が行われていることを意味します。 Android 4.0以降を使用している場合は、青いバーの上に紫色のバーが表示されます。 これは、リソースをレンダリングスレッドに転送するために費やされた時間を表します。 次に赤い部分があります。これは、Androidの2DレンダラーがOpenGLにコマンドを発行してディスプレイリストを描画および再描画するのに費やした時間を表します。 上部にあるオレンジ色のバーは、CPUがGPUの作業の終了を待機している時間を表します。 高すぎる場合、アプリはGPUで多くの作業を行っています。
あなたが十分に良ければ、オレンジの上にもう1つの色があります。 これは、16ミリ秒のしきい値を表す緑色の線です。 アプリを60fpsで実行することが目標であるため、すべてのフレームを描画するのに16ミリ秒かかります。 そうしないと、一部のフレームがスキップされたり、アプリがぎくしゃくしたりする可能性があり、ユーザーは間違いなく気付くでしょう。 アニメーションとスクロールに特に注意してください。ここで、滑らかさが最も重要になります。 このツールを使用してスキップされたフレームを検出することはできますが、問題がどこにあるのかを正確に把握するのには役立ちません。
4.階層ビューア
これは本当に強力なので、私のお気に入りのツールの1つです。 AndroidStudioから[ツール]->[Android]->[Androidデバイスモニター]を使用して起動できます。または、sdk/toolsフォルダーに「モニター」としてあります。 スタンドアロンのhierarachyviewer実行可能ファイルもそこにありますが、非推奨になっているため、モニターを開く必要があります。 ただし、Androidデバイスモニターを開いたら、階層ビューアーパースペクティブに切り替えます。 デバイスに割り当てられている実行中のアプリが表示されない場合は、修正するためにいくつかの方法があります。 また、この問題のスレッドをチェックしてみてください。あらゆる種類の問題とあらゆる種類の解決策を持つ人々がいます。 何かがあなたのためにも働くはずです。
Hierarchy Viewerを使用すると、ビュー階層の非常にわかりやすい概要を取得できます(明らかに)。 すべてのレイアウトを個別のXMLで表示すると、役に立たないビューを簡単に見つけることができます。 ただし、レイアウトを組み合わせ続けると、混乱しやすくなります。 このようなツールを使用すると、たとえば、子が1つしかないRelativeLayoutと別のRelativeLayoutを簡単に見つけることができます。 これにより、そのうちの1つが取り外し可能になります。
各ビューの大きさを確認するために、ビュー階層全体をトラバースするため、 requestLayout()
を呼び出さないでください。 測定値との競合がある場合、階層が複数回トラバースされる可能性があります。これがアニメーション中に発生した場合、一部のフレームが確実にスキップされます。 Androidがどのようにビューを描画するかについて詳しく知りたい場合は、これを読むことができます。 HierarchyViewerで見た1つのビューを見てみましょう。
右上隅には、スタンドアロンウィンドウで特定のビューのプレビューを最大化するためのボタンが含まれています。 その下には、アプリのビューの実際のプレビューも表示されます。 次の項目は、ビュー自体を含め、特定のビューに含まれる子の数を表す数値です。 ノード(できればルートノード)を選択して[レイアウト時間の取得](3つの色付きの円)を押すと、さらに3つの値が入力され、メジャー、レイアウト、および描画のラベルが付いた色付きの円が表示されます。 測定フェーズが特定のビューの測定にかかった時間を表すことは、ショックではないかもしれません。 レイアウトフェーズはレンダリング時間に関するものですが、描画は実際の描画操作です。 これらの値と色は相互に関連しています。 緑の1つは、ビューがツリー内のすべてのビューの上位50%にレンダリングされることを意味します。 黄色は、ツリー内のすべてのビューの50%遅いレンダリングを意味し、赤は、指定されたビューが最も遅いビューの1つであることを意味します。 これらの値は相対的なものであるため、常に赤い値があります。 あなたは単にそれらを避けることはできません。
値の下には、「TextView」などのクラス名、オブジェクトの内部ビューID、およびXMLファイルで設定したビューのandroid:idがあります。 コードでIDを参照していなくても、すべてのビューにIDを追加する習慣を身に付けることをお勧めします。 これにより、Hierarchy Viewerでのビューの識別が非常に簡単になり、プロジェクトで自動テストを行っている場合は、要素のターゲティングがはるかに高速になります。 それはあなたとあなたの同僚がそれらを書くための時間を節約するでしょう。 XMLファイルに追加された要素にIDを追加するのは非常に簡単です。 しかし、動的に追加された要素はどうですか? まあ、それも本当に簡単であることがわかりました。 値フォルダー内にids.xmlファイルを作成し、必須フィールドに入力するだけです。 これは次のようになります。
<resources> <item name="item_title" type="id"/> <item name="item_body" type="id"/> </resources>
次に、コードでsetId(R.id.item_title)
を使用できます。 これ以上簡単なことはありません。
UIを最適化する際に注意すべき点がさらにいくつかあります。 一般に、深い階層は避け、浅い、おそらく広い階層を優先する必要があります。 不要なレイアウトは使用しないでください。 たとえば、ネストされたTableLayout
のグループをRelativeLayout
またはLinearLayouts
のいずれかに置き換えることができます。 常にLinearLayout
とRelativeLayout
を使用するのではなく、さまざまなレイアウトを自由に試してみてください。 また、必要に応じてカスタムビューを作成してみてください。うまくいけば、パフォーマンスが大幅に向上します。 たとえば、Instagramがコメントの表示にTextViewsを使用していないことをご存知ですか?
Android Developersサイトで、Pixel Perfectツールなどを使用して、さまざまなペインの説明を含むHierarchy Viewerに関する詳細情報を見つけることができます。もう1つ指摘するのは、ビューを.psdファイルにキャプチャすることです。 「ウィンドウレイヤーをキャプチャする」ボタン。 すべてのビューは別々のレイヤーにあるため、PhotoshopまたはGIMPで非表示にしたり変更したりするのは非常に簡単です。 ああ、それはあなたができるすべてのビューにIDを追加するもう1つの理由です。 これにより、レイヤーに実際に意味のある名前が付けられます。
開発者向けオプションにはさらに多くのデバッグツールが含まれているので、それらをアクティブにして、それらが何をしているのかを確認することをお勧めします。 何がうまくいかない可能性がありますか?
Android開発者サイトには、パフォーマンスに関する一連のベストプラクティスが含まれています。 それらは、私が実際には話していないメモリ管理を含む、多くの異なる領域をカバーしています。 メモリの処理とメモリリークの追跡はまったく別の話なので、私は黙ってそれを無視しました。 画像を効率的に表示するためにサードパーティのライブラリを使用すると非常に役立ちますが、それでもメモリの問題がある場合は、Square製のリークカナリアを確認するか、これを読んでください。
まとめ
ですから、これは朗報でした。 悪い新機能は、Androidアプリの最適化がはるかに複雑であることです。 すべてを行う方法はたくさんあるので、それらの長所と短所に精通している必要があります。 通常、メリットしかない特効薬はありません。 舞台裏で何が起こっているかを理解することによってのみ、あなたはあなたに最適な解決策を選ぶことができます。 お気に入りの開発者が何か良いことを言ったからといって、それが必ずしもあなたにとって最良の解決策であるとは限りません。 議論する領域はもっとたくさんあり、より高度なプロファイリングツールもたくさんあるので、次回はそれらにたどり着くかもしれません。
あなたがトップの開発者とトップの会社から学ぶことを確認してください。 このリンクには、数百のエンジニアリングブログがあります。 これは明らかにAndroid関連のものだけではないため、Androidのみに関心がある場合は、特定のブログをフィルタリングする必要があります。 FacebookやInstagramのブログを強くお勧めします。 AndroidのInstagramUIには疑問がありますが、彼らのエンジニアリングブログには本当にすばらしい記事がいくつかあります。 私にとって、毎日何億人ものユーザーを扱っている企業で物事がどのように行われているのかを簡単に確認できるのは素晴らしいことです。そのため、ブログを読まないのはおかしなことに思えます。 世界は非常に急速に変化しているため、常に改善し、他の人から学び、新しいツールを使用しようとしないと、取り残されてしまいます。 マーク・トウェインが言ったように、読まない人は読めない人に勝る利点はありません。