Perfettoの詳細な分析

1. ペルフェットの基本

1. Perfetto の概要

Perfetto は、パフォーマンス計測とトレース分析を改善するための運用グレードのオープンソース スタックです。Systrace とは異なり、protobuf でエンコードされたバイナリ ストリームの形式で任意の長さのトレースを記録できるデータ ソースのスーパーセットを提供します。Perfetto は、systrace のアップグレードされたバージョンとして理解でき、更新されたプラットフォームと新しいチャートで使用され、より多くの情報を表示します。これは、開発者が主要な Android サブシステム (SurfaceFlinger/SystemServer/Input/Display や​​その他のフレームワークの主要なモジュール、サービス、View システムなど) の実行情報を収集するのに役立ち、それによって開発者がシステムのボトルネックをより直観的に分析し、パフォーマンスを向上させるのに役立ちます。


その基本機能には次の部分が含まれます。

Perfetto の正式な導入と使い方については、こちらをご覧ください: perfetto 公式 Web サイト

2.Perfetto利用の流れ

Perfetto を使用する前に、各プラットフォームでの Perfetto の使用方法を理解する必要があります。基本的なプロセスは次のとおりです。

  • 携帯電話はクロールするインターフェイスの準備ができています。
  • クリックしてクロールを開始します (コマンドラインを使用している場合は、コマンドの実行が開始されます)
  • 携帯電話で操作を開始します(あまり時間をかけすぎないでください。ファイルが大きすぎるとファイルがスタックしてしまい、問題を特定するのが難しくなります)。
  • 設定時間が経過すると、xxxtrace.html ファイルが生成されますので、以下の Web サイトからPerfetto UIを開きます

一般的にキャプチャされる Perfetto ファイルは次のとおりです。

3. トレースをキャプチャするいくつかの方法

3.1. コマンドキャプチャ perfetto-traces メソッド

コマンド ライン フォームはより柔軟で高速です。関連する TAG を一度設定すると、将来再度使用するときに結果がすぐに取得されます。また、関連するコマンドをスクリプトに書き込むこともでき、次回の使用がより便利になります。 。

コマンドに関連するパラメーターはたくさんありますが、ツールを使用する場合は、それほど多くのことを考慮する必要はなく、該当する項目にチェックを入れるだけでよく、コマンドラインを使用する場合は手動でパラメーターを追加するだけです。

一般的によく使われるのは、

  1. -o: 出力ファイルのパスと名前を示します。
  2. -t: 取得時間 (最新バージョンを指定する必要はありません。終了するには Enter キーを押してください)
  3. -b: バッファ サイズを指定します (通常はデフォルトの Buffer で十分です。長い Trae をキャプチャする場合は Buffer を増やすことをお勧めします)
  4. -a: アプリのパッケージ名を指定します (カスタム トレース ポイントをデバッグする場合は、これを忘れずに追加してください)

Android R より前のバージョンでは、デフォルトでオフになっています。最初にコマンドを実行してオンにする必要があります。

adb シェル setproppersist.traced.enable 1

perfetto-traces をキャプチャしてエクスポートする基本的なコマンドは次のとおりです。他のタグや関連コマンドが必要な場合は、自分で追加できます。

perfetton を取得します: 
adb shell perfetto -o /data/misc/perfetto-traces/trace_file.perfetto-trace -t 10s sched freq idle am wm gfx view binding_driver hal dalvik カメラ入力解像度メモリ
エクスポート トレース ファイル: 
adb pull /data/misc / perfetto-traces/trace_file.perfetto-trace

3.2 atraceコマンドを使用してsystraceを取得する 具体的な方法は以下の通りです。

atrace コマンドを取得します。
adb shell atrace -z -b 40000 am wm view res ss gfx view halbionic pm sched freq idle disk load sync binding_driver bider_lock memreclaim dalvik input -t 10 > /data/local/tmp/trace_output.atrace pull export トレース ファイルを取得します
adb pull /data/local/tmp/trace_output.atrace

3.3 systrace スクリプトを使用して systrace をキャプチャする 具体的な方法は次のとおりです。

1. Python 環境を設定する必要があります。

2. systrace.zip を解凍します。

解凍した systrace ディレクトリに移動し、ディレクトリ内に systrace.py ファイルがあるので、次のコマンドを実行します。

python systrace.py am wm view res ss gfx rs hal bionic pm sched freq idle ディスクバインダー_ドライバーバインダー_ロック memreclaim dalvik 入力データベース -t 10 -o tracelog\systrace.html

systrace ディレクトリの下の tracelog ディレクトリで生成された systrace.html ファイルを見つけます。

3.4携帯電話でのシステム トレースによるキャプチャ

1. [開発者向けオプション] を開き、[設定] -> [システム] -> [開発者向けオプション] -> [システム トレース] -> [クイック設定の表示] タイル をクリックし、システム トレースのショートカット アイコンを追加します。 

[設定] - [システム] - [開発者向けオプション] - [システム追跡] で記録するカテゴリ (カテゴリ) とバッファ サイズ (バッファ サイズ) を設定します (ソフトウェアによってパスが異なる場合があります)。

[トレース記録]を開いて記録を開始します

通知がポップアップ表示されたら、問題の再現を開始したり、特定の操作を実行したりできます。

完了後、通知バーの[タップしてトレースを停止]をクリックして停止し、保存します。

2. テストする前に、ドロップダウン ステータス バーの [トレースの記録]アイコンをクリックして perfetto-trace の取得を開始し、テストを開始します。

3. テストが完了したら、[ステータス バーのレコード トレース アイコンをプルダウンする] または [ステータス バーのフェッチ通知をプルダウンする] をクリックして、systrace のフェッチを停止します。

4. 「トレースが保存されました」通知がポップアップ表示されたら、キャプチャされた perfetto-trace ファイルを携帯電話のディレクトリ/data/local/tracesで取得できます。

5. adb pull /data/local/tracesコマンドを使用して、対応する systrace をローカルに保存します。

4. スレッドのステータスを確認する

Perfetto は、さまざまなスレッド状態を識別するためにさまざまな色を使用します。スレッドの現在の状態を識別するために、各メソッドに対応するスレッド状態があります。スレッドの状態を見ることで、現在のボトルネック (CPU の遅さ) を知ることができます。バインダーの呼び出し、IO 操作、または CPU タイム スライスの取得の失敗が原因ですか?

スレッドのステータスには主に次のものが含まれます。

4.1 緑: 実行中

この状態のスレッドのみが CPU 上で実行できます。同時に複数のスレッドが実行可能状態になる可能性があり、これらのスレッドの task_struct 構造体は、対応する CPU の実行可能キューに入れられます (スレッドは最大でも 1 つの CPU の実行可能キューにのみ存在できます)。スケジューラのタスクは、各 CPU の実行可能キューから、その CPU 上で実行するスレッドを選択することです。

機能: スレッドの実行ステータス、実行時間を確認し、競合製品と比較し、速度が速いまたは遅い理由を分析することがよくあります。

  1. 頻度が足りないのでしょうか?
  2. 小さなコアで実行されていますか?
  3. 実行中と実行可能を頻繁に切り替えますか? なぜ?
  4. ランニングとスリープを頻繁に切り替えますか? なぜ?
  5. 実行すべきではないコアで実行されていますか? たとえば、重要でないスレッドが大きなコアを占有します。

4.2 青: 実行可能

スレッドは実行できますが、現在スケジュールされておらず、CPU スケジュールを待っています。

機能: 実行可能状態のスレッド状態が長く続くほど、CPU がビジーになり、タスクが時間内に処理されなくなります。

  1. バックグラウンドで実行されているタスクが多すぎませんか?
  2. 周波数が低すぎて処理が間に合わないのでしょうか?
  3. 特定のcpusetに限定されているのにcpuがいっぱいなので処理が間に合わないのでしょうか?
  4. 現時点でのRunningのタスクは何ですか? なぜ?

4.3 白: 眠っている

スレッドには実行する作業がありません。スレッドがミューテックス ロックでブロックされているか、スレッドが戻るのを待っている可能性があります。誰がスレッドを起動したかを確認して、どのスレッドを待っているかを確認できます。

機能: これは通常、イベントが駆動するのを待機します。

4.4 オレンジ: 無中断スリープ - IO ブロック

スレッドは I/O でブロックされているか、ディスク操作の完了を待っています。通常、最後の行はこの時点でのコールサイトを識別します: wait_on_page_locked_killable

機能: これは通常、IO 操作が遅いことを示します。オレンジ色の中断不可能なスリープ状態が多数ある場合は、通常、メモリ不足状態に入っていることが原因です。メモリを適用するときに PageFault がトリガーされます。これはページ キャッシュに表示されることがあります。 Linux システムのリンク リスト。まだ準備ができていない (つまり、ディスクの内容が完全に読み取られていない) ページがあり、この時点でユーザーがこのページにアクセスすると、wait_on_page_locked_killable がブロックされます。システムは IO 操作、各トランザクションでビジー状態です。すべての IO 操作をキューに入れる必要がある場合、非常に簡単に発生し、ブロック時間が比較的長くなることがよくあります。

4.5 ブラウン: 無中断スリープ (非 IO)

スレッドは別のカーネル操作 (通常はメモリ管理) でブロックされています。

機能: 一般的にカーネル状態に陥りますが、正常な場合もあれば異常な場合もあり、状況に応じて解析する必要があります。

5. タスク起床情報分析

Perfetto は、スレッド呼び出し待機の分析に役立つ非常に役立つ情報を特定します。

多くの場合、スレッドが起動されているという情報の方が重要です。スレッドが誰によって起動されたかがわかれば、それらの間の通話待機関係もわかります。スレッドが比較的長いスリープ状態にあり、その後起動された場合、次のことが可能です。このスレッドを起こした人を確認するには、起こした人の情報を確認し、起こした人がなぜこんなに遅く起きたのかを確認することができます。

一般的な状況は次のとおりです。アプリケーションのメイン スレッドは SystemServer の AMS と通信するために Binder を使用しますが、AMS のこの関数がたまたまロックの解放を待っている (または関数自体の実行に時間がかかる) ため、アプリケーションのメイン スレッドは次のことを行う必要があります。比較的長い時間待機すると、応答の遅さやラグなどのパフォーマンスの問題が発生します。これが、バックグラウンドで多数のプロセスが実行されたり、バックグラウンドで実行された後にマシン全体のパフォーマンスが低下したりする主な理由の 1 つです。走っている猿。

もう 1 つのシナリオは、アプリケーションのメイン スレッドがアプリケーションの他のスレッドの実行結果を待っているときです。このとき、スレッド ウェイクアップ情報を使用して、メイン スレッドがブロックされているスレッドを分析できます (図を参照)。次のシナリオ:

この間のスリープ状態を確認することができ、一番後ろのRunnig状態をクリックするとどのCPUで動作しているのかが確認でき、クリックするとどのスレッドが起動したのか、異常がないか確認することができます。

6. 情報領域データ解析

6.1 CPUアーキテクチャ

簡単に言えば、現在の携帯電話の CPU は、コア数とアーキテクチャに応じて次の 3 つのカテゴリに分類できます。

  1. 大小のコア アーキテクチャではない (通常なのでコアは同じ)
  2. 大規模および小規模コア アーキテクチャ (通常は 0 ~ 3 つの小規模コア、4 ~ 7 つの大きなコア)
  3. 大、中、小コア アーキテクチャ (通常 0 ~ 3 個の小型コア、4 ~ 6 個の中型コア、7 個の超大型コア)

6.2 CPU情報情報

通常、Pefetto の CPU 情報情報は一番上にあり、そこでよく使用される情報には次のものが含まれます。

  1. CPU周波数の変更
  2. タスクの実行ステータス
  3. 大小のコアのスケジューリング
  4. CPUブーストのスケジュールステータス

一般に、Systrace のカーネル CPU 情報は、タスクのスケジューリング情報を参照して、頻度またはスケジューリングが現在のタスクのパフォーマンス上の問題を引き起こしているかどうかを確認します。例は次のとおりです。

  1. 特定のシーンのタスクの実行が比較的遅い場合、タスクが小さいコアにスケジュールされているかどうかを確認できます。
  2. 特定のシーンのタスクの実行が比較的遅いのですが、現在の CPU 周波数ではこのタスクを実行するには不十分ですか?
  3. 私のタスクは非常に特殊です。このタスクをビッグ コアで実行できますか?
  4. 私のシーンには CPU に対する高い要件があります。シーンの実行中に最小 CPU 周波数を制限するようにリクエストできますか?

6.3 現在の選択情報の分析:

6.4 CPUによるスレッド情報解析

7. ショートカットキーの使用

ショートカット キーを使用すると、Perfetto の表示を高速化できます。よく使用されるショートカット キーの一部を次に示します。

W : Perfetto をズームイン、ズームインすると局所的な詳細がよくわかります
S : Perfetto がズームアウト、ズームアウトすると全体が表示されます
A : 左に移動します
D : 右に移動します
M : 上下に見やすくするために期間の範囲を選択します

pefetto に付属の検索ボックスには 4 文字以上が必要です。検索されるデータはより詳細になり、Ctrl+F を使用できます。場合によっては、Ctrl+F で検索ボックスに付属のデータを検索できないことがあります。

8. リフレッシュ レートとは何ですか?

  1. 60 fps は、画像が 1 秒あたり 60 回更新されることを意味します。これはソフトウェア用です。
  2. この 60 件の更新は均等に更新する必要があり、一方が速く、他方が遅いというわけではなく、そうでないと視覚的に滑らかに感じられません。
  3. 1 秒あたり 60 回、つまり 1 回更新するには 1/60 ~= 16.67 ミリ秒
  4. ここで説明する画面のリフレッシュ レートはハードウェアに固有です。現在、ほとんどの携帯電話画面のリフレッシュ レートは 60 Hz に維持されています。モバイル デバイスは一般に 60 Hz を使用します。これは、モバイル デバイスの消費電力要件が高く、携帯電話の性能が向上しているためです。携帯電話の場合、リフレッシュレートは周波数の増加に伴ってロジック消費電力が直線的に増加しますが、同時にリフレッシュレートが高いほどTFTデータの書き込み時間が短くなり、画面設計が難しくなります。

2. メインスレッドとレンダリングスレッド

1. メインスレッドの作成

Android アプリのプロセスは Linux をベースにしており、その管理も Linux のプロセス管理機構に基づいているため、その作成でも fork 関数が呼び出されます。

フレームワーク/base/core/jni/com_android_internal_os_Zygote.cpp

1
 

pid_t pid = fork();
 

ここではフォークから出てくる処理をメインスレッドとみなすことができますが、このスレッドはまだ Android に接続されていないため、Android アプリのメッセージを処理できません; Android アプリのスレッドはメッセージのメカニズムに基づいて実行されるため、フォークから出てくるメインスレッドはAndroid の Message である必要があります メッセージ バインディングは Android アプリのさまざまなメッセージを処理できます

ActivityThreadはここで紹介されていますが、正確に言うと、ActivityThread は ProcessThread という名前が適切です。ActivityThread はフォークからのプロセスとアプリのメッセージを接続し、それらの連携によって Android アプリのメイン スレッドが形成されます。したがって、ActivityThread は実際にはスレッドではありませんが、メッセージ メカニズムに必要な MessageQueue、Looper、および Handler を初期化し、その Handler がほとんどの Message メッセージの処理を担当するため、私たちは ActivityThread がメイン スレッドであると考えることに慣れています。しかし実際には、それは単なるメインスレッド、つまり論理処理ユニットです。

1.1 アクティビティスレッドの作成

アプリプロセスがフォークアウトしたら、アプリプロセスに戻り、ActivityThread の Main 関数を見つけます。

com/android/internal/os/ZygoteInit.java

1
2
3
4
5
 

static Final Runnable childZygoteInit(
int targetSdkVersion, String[] argv, ClassLoader classLoader) { RuntimeInit.Arguments args = new RuntimeInit.Arguments(argv); return RuntimeInit.findStaticMain(args.startClass, args.startArgs, classLoader); }



 

ここでの startClass は ActivityThread で、これが見つかって呼び出された後、ロジックは ActivityThread のメイン関数に到達します。

android/app/ActivityThread.java

1
2
3
4
5
6
7 8 9 10 11 12 13
14






 

public static void main(String[] args) { //1. Looper の初期化、MessageQueue Looper.prepareMainLooper(); // 2. ActivityThread の初期化ActivityThread thread = new ActivityThread(); // 3. 主に AMS.attachApplicationLocked を呼び出し、Synchronize情報を処理し、初期化作業を行いますthread.attach(false, startSeq); // 4. メイン スレッドのハンドラーを取得します。ここで H. 基本的に、アプリのメッセージはこのハンドラーで処理されますif (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } // 5. 初期化が完了し、Looper が動作を開始しますLooper.loop(); }













 

main 関数が処理されると、メイン スレッドが正式にオンラインになり、動作を開始します。Perfetto のプロセスは次のとおりです。

1.2 アクティビティスレッドの機能

また、Android の 4 つの主要コンポーネントはすべてメインスレッドで実行されているとよく言われますが、実際には、ActivityThread のハンドラーのメッセージを見てください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 

class H extends Handler { //摘抄了部分
public static Final int BIND_APPLICATION = 110;
パブリック静的最終整数EXIT_APPLICATION = 111;
パブリック静的最終 int RECEIVER = 113;
パブリック静的最終 int CREATE_SERVICE = 114;
public static Final int STOP_SERVICE = 116;
パブリック静的最終整数BIND_SERVICE = 121;
パブリック静的最終整数UNBIND_SERVICE = 122;
パブリック静的最終 int DUMP_SERVICE = 123;
public static Final int REMOVE_PROVIDER = 131;
パブリック静的最終 int DISPATCH_PACKAGE_BROADCAST = 133;
パブリック静的最終 int DUMP_PROVIDER = 141;
public static Final int UNSTABLE_PROVIDER_DIED = 142;
パブリック静的最終整数 INSTALL_PROVIDER = 145;
パブリック静的最終整数ON_NEW_ACTIVITY_OPTIONS = 146;
}
 

ご覧のとおり、プロセスの作成、アクティビティの起動、サービス管理、レシーバー管理、プロバイダー管理はすべてここで処理され、特定のハンドルXXXに移動します。

2. レンダリングスレッドの作成と開発

メイン スレッドの話が終わったので、レンダリング スレッドについて話しましょう。レンダリング スレッドも RenderThread です。元の Android バージョンにはレンダリング スレッドはありません。レンダリング作業はすべて、CPU を使用してメイン スレッドで行われます。 RenderThread は Android Lollipop に新しく追加されたコンポーネントで、前のメイン スレッドのレンダリング作業の一部を引き受け、メイン スレッドの負担を軽減します。

2.1 ソフトウェア描画

一般にハードウェア アクセラレーションと呼ばれるのは、GPU アクセラレーションを指します。これは、RenderThread を使用してレンダリング アクセラレーションのために GPU を呼び出すこととして理解できます。現在の Android ではハードウェア アクセラレーションがデフォルトでオンになっているため、何も設定しない場合、プロセスにはデフォルトでメイン スレッドとレンダリング スレッド (コンテンツが表示される) が存在します。を追加すると、

1
 

android:hardwareAccelerated="false"
 

ハードウェア アクセラレーションをオフにできます。アプリがハードウェア アクセラレーションをオフにしたことをシステムが検出すると、RenderThread は初期化されません。メイン スレッドのみがあり、レンダリング スレッドはありません。CPU はレンダリングのために libSkia を直接呼び出します。

メインスレッドはレンダリング作業を行う必要があるため、実行時間が長くなりフリーズしやすくなると同時に、フレーム間のアイドル間隔も短くなり、他のMessageの実行時間が圧縮されてしまいます。

2.2 ハードウェアアクセラレーションによる描画

正常情况下,硬件加速是开启的,主线程的 draw 函数并没有真正的执行 drawCall ,而是把要 draw 的内容记录到 DIsplayList 里面,同步到 RenderThread 中,一旦同步完成,主线程就可以被释放出来做其他的事情,RenderThread 则继续进行渲染工作

主线程和渲染线程一帧的工作流程(每一帧都会遵循这个流程,不过有的帧需要处理的事情多,有的帧需要处理的事情少) ,重点看 “UI Thread ” 和 RenderThread 这两行

这张图对应的工作流程如下

  1. 主线程处于 Sleep 状态,等待 Vsync 信号
  2. Vsync 信号到来,主线程被唤醒,Choreographer 回调 FrameDisplayEventReceiver.onVsync 开始一帧的绘制
  3. 处理 App 这一帧的 Input 事件(如果有的话)
  4. 处理 App 这一帧的 Animation 事件(如果有的话)
  5. 处理 App 这一帧的 Traversal 事件(如果有的话)
  6. 主线程与渲染线程同步渲染数据,同步结束后,主线程结束一帧的绘制,可以继续处理下一个 Message(如果有的话,IdleHandler 如果不为空,这时候也会触发处理),或者进入 Sleep 状态等待下一个 Vsync
  7. 渲染线程首先需要从 BufferQueue 里面取一个 Buffer(dequeueBuffer) , 进行数据处理之后,调用 OpenGL 相关的函数,真正地进行渲染操作,然后将这个渲染好的 Buffer 还给 BufferQueue (queueBuffer) , SurfaceFlinger 在 Vsync-SF 到了之后,将所有准备好的 Buffer 取出进行合成(这个流程在下面的SurfaceFlinger会讲解)

2.3 渲染线程初始化

渲染线程初始化在真正需要 draw 内容的时候,一般我们启动一个 Activity ,在第一个 draw 执行的时候,会去检测渲染线程是否初始化,如果没有则去进行初始化

android/view/ViewRootImpl.java

1
2
 

mAttachInfo.mThreadedRenderer.initializeIfNeeded(
mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
 

在Perfetto表现如下:

后续直接调用 draw

android/graphics/HardwareRenderer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 

mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
choreographer.mFrameInfo.markDrawStart();

updateRootDisplayList(view, callbacks);

if (attachInfo.mPendingAnimatingRenderNodes != null) {
final int count = attachInfo.mPendingAnimatingRenderNodes.size();
for (int i = 0; i < count; i++) {
registerAnimatingRenderNode(
attachInfo.mPendingAnimatingRenderNodes.get(i));
}
attachInfo.mPendingAnimatingRenderNodes.clear();
attachInfo.mPendingAnimatingRenderNodes = null;
}

int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
setEnabled(false);
attachInfo.mViewRootImpl.mSurface.release();
attachInfo.mViewRootImpl.invalidate();
}
if ((syncResult & SYNC_REDRAW_REQUESTED) != 0) {
attachInfo.mViewRootImpl.invalidate();
}
}
 

上面的 draw 只是更新 DIsplayList ,更新结束后,调用 syncAndDrawFrame ,通知渲染线程开始工作,主线程释放。渲染线程的核心实现在 libhwui 库里面,其代码位于 frameworks/base/libs/hwui

frameworks/base/libs/hwui/renderthread/RenderProxy.cpp

1
2
3
 

int RenderProxy::syncAndDrawFrame() {
return mDrawFrameTask.drawFrame();
}
 

其核心流程在 Perfetto 上的表现如下:

2.4 主线程和渲染线程的分工

主线程负责处理进程 Message、处理 Input 事件、处理 Animation 逻辑、处理 Measure、Layout、Draw ,更新 DIsplayList ,但是不涉及 SurfaceFlinger 打交道;渲染线程负责渲染渲染相关的工作,一部分工作也是 CPU 来完成的,一部分操作是调用 OpenGL 函数来完成的

当启动硬件加速后,在 Measure、Layout、Draw 的 Draw 这个环节,Android 使用 DisplayList 进行绘制而非直接使用 CPU 绘制每一帧。DisplayList 是一系列绘制操作的记录,抽象为 RenderNode 类,这样间接的进行绘制操作的优点如下

  1. DisplayList 可以按需多次绘制而无须同业务逻辑交互
  2. 特定的绘制操作(如 translation, scale 等)可以作用于整个 DisplayList 而无须重新分发绘制操作
  3. 当知晓了所有绘制操作后,可以针对其进行优化:例如,所有的文本可以一起进行绘制一次
  4. 可以将对 DisplayList 的处理转移至另一个线程(也就是 RenderThread)
  5. 主线程在 sync 结束后可以处理其他的 Message,而不用等待 RenderThread 结束

3、游戏的主线程与渲染线程

游戏大多使用单独的渲染线程 ,直接跟 SurfaceFlinger 进行交互,其主线程的存在感比较低,绝大部分的逻辑都是自己在自己的渲染线程里面实现的。

大家可以看一下王者荣耀对应的 Perfetto ,一帧整体流程如下:

王者荣耀主线程的主要工作,就是把 Input 事件传给 Unity 的渲染线程,渲染线程收到 Input 事件之后,进行逻辑处理,画面更新等。

三、SurfaceFlinger讲解

SurfaceFlinger 的主要功能是接收缓冲区,创建缓冲区,并将缓冲区发送到显示器。WindowManager 为 SurfaceFlinger 提供了缓冲区和窗口元数据,SurfaceFlinger 使用这些缓冲区和窗口元数据来组合具有显示的表面。

详细定义可以看官网: SurfaceFlinger 的定义

在Perfetto中,我们关注的重点就是上面这幅图对应的部分

  1. App 部分
  2. BufferQueue 部分
  3. SurfaceFlinger 部分
  4. HWComposer 部分

这四部分,在 Perfetto 中都有可以对应的地方,以时间发生的顺序排序就是 1、2、3、4,下面我们从 Perfetto 的这四部分来看整个渲染的流程

1、App 部分

关于 App 部分,在上面的主线程渲染线程部分已经讲解过了,其主要的流程如下图:

从 SurfaceFlinger 的角度来看,App 部分主要负责生产 SurfaceFlinger 合成所需要的 Surface。

App 与 SurfaceFlinger 的交互主要集中在三点

  1. Vsync 信号的接收和处理
  2. RenderThread 的 dequeueBuffer
  3. RenderThread 的 queueBuffer

1.1 Vsync 信号的接收和处理

App 和 SurfaceFlinger 的第一个交互点就是 Vsync 信号的请求和接收,Vsync-App 信号到达,应用收到这个信号后,开始一帧的渲染准备

2、Triple Buffer

2.1 BufferQueue 部分

如下图,每个有显示界面的进程对应一个 BufferQueue,使用方创建并拥有 BufferQueue 数据结构,并且可存在于与其生产方不同的进程中,BufferQueue 工作流程如下:

上图主要是 dequeue、queue、acquire、release ,在这个例子里面,App 是生产者,负责填充显示缓冲区(Buffer);SurfaceFlinger 是消费者,将各个进程的显示缓冲区做合成操作

  1. dequeue(生产者发起) : 当生产者需要缓冲区时,它会通过调用 dequeueBuffer() 从 BufferQueue 请求一个可用的缓冲区,并指定缓冲区的宽度、高度、像素格式和使用标记。
  2. queue(生产者发起):生产者填充缓冲区并通过调用 queueBuffer() 将缓冲区返回到队列。
  3. acquire(消费者发起) :消费者通过 acquireBuffer() 获取该缓冲区并使用该缓冲区的内容
  4. release(消费者发起) :当消费者操作完成后,它会通过调用 releaseBuffer() 将该缓冲区返回到队列

2.2 Single Buffer

单 Buffer 的情况下,因为只有一个 Buffer 可用,那么这个 Buffer 既要用来做合成显示,又要被应用拿去做渲染

理想情况下,单 Buffer 是可以完成任务的(有 Vsync-Offset 存在的情况下)

  1. App 收到 Vsync 信号,获取 Buffer 开始渲染
  2. 间隔 Vsync-Offset 时间后,SurfaceFlinger 收到 Vsync 信号,开始合成
  3. 屏幕刷新,我们看到合成后的画面

但是很不幸,理想情况我们也就想一想,这期间如果 App 渲染或者 SurfaceFlinger 合成在屏幕显示刷新之前还没有完成,那么屏幕刷新的时候,拿到的 Buffer 就是不完整的,在用户看来,就有种撕裂的感觉

当然 Single Buffer 已经没有在使用,上面只是一个例子

2.3 Double Buffer

Double Buffer 相当于 BufferQueue 中有两个 Buffer 可供轮转,消费者在消费 Buffer的同时,生产者也可以拿到备用的 Buffer 进行生产操作

下面我们来看理想情况下,Double Buffer 的工作流程

但是 Double Buffer 也会存在性能上的问题,比如下面的情况,App 连续两帧生产都超过 Vsync 周期(准确的说是错过 SurfaceFlinger 的合成时机) ,就会出现掉帧情况

2.4 Triple Buffer

Triple Buffer 中,我们又加入了一个 BackBuffer ,这样的话 BufferQueue 里面就有三个 Buffer 可以轮转了,当 FrontBuffer 在被使用的时候,App 有两个空闲的 Buffer 可以拿去生产,就算生产过程中有 GPU 超时,CPU 任然可以拿到新的 Buffer 进行生产(即 SurfaceFling 消费 FrontBuffer,GPU 使用一个 BackBuffer,CPU使用一个 BackBuffer)

下面就是引入 Triple Buffer 之后,解决了 Double Buffer 中遇到的由于 Buffer 不足引起的掉帧问题

这里把两个图放到一起来看,方便大家做对比(一个是 Double Buffer 掉帧两次,一个是使用 Triple Buffer 只掉了一帧)

3、Triple Buffer 的作用

3.1 缓解掉帧

从上一节 Double Buffer 和 Triple Buffer 的对比图可以看到,在这种情况下(出现连续主线程超时),三个 Buffer 的轮转有助于缓解掉帧出现的次数(从掉帧两次 -> 只掉帧一次)

所以从第一节如何定义掉帧这里我们就知道,App 主线程超时不一定会导致掉帧,由于 Triple Buffer 的存在,部分 App 端的掉帧(主要是由于 GPU 导致),到 SurfaceFlinger 这里未必是掉帧,这是看 Perfetto 的时候需要注意的一个点

3.2 减少主线程和渲染线程等待时间

双 Buffer 的轮转, App 主线程有时候必须要等待 SurfaceFlinger(消费者)释放 Buffer 后,才能获取 Buffer 进行生产,这时候就有个问题,现在大部分手机 SurfaceFlinger 和 App 同时收到 Vsync 信号,如果出现App 主线程等待 SurfaceFlinger(消费者)释放 Buffer ,那么势必会让 App 主线程的执行时间延后,比如下面这张图,可以明显看到:Buffer B 并不是在 Vsync 信号来的时候开始被消费(因为还在使用),而是等 Buffer A 被消费后,Buffer B 被释放,App 才能拿到 Buffer B 进行生产,这期间就有一定的延迟,会让主线程可用的时间变短

3.3 降低 GPU 和 SurfaceFlinger 瓶颈

双 Buffer 的时候,App 生产的 Buffer 必须要及时拿去让 GPU 进行渲染,然后 SurfaceFlinger 才能进行合成,一旦 GPU 超时,就很容易出现SurfaceFlinger 无法及时合成而导致掉帧在三个 Buffer 轮转的时候,App 生产的 Buffer 可以及早进入 BufferQueue,让 GPU 去进行渲染(因为不需要等待,就算这里积累了 2 个 Buffer,下下一帧才去合成,这里也会提早进行,而不是在真正使用之前去匆忙让 GPU 去渲染),另外 SurfaceFlinger 本身的负载如果比较大,三个 Buffer 轮转也会有效降低 dequeueBuffer 的等待时间。

SurfaceFlinger 端的 Perfetto 如下所示

4、SurfaceFlinger工作流程

SurfaceFlinger 的主要工作就是合成:

当 VSYNC 信号到达时,SurfaceFlinger 会遍历它的层列表,以寻找新的缓冲区。如果找到新的缓冲区,它会获取该缓冲区;否则,它会继续使用以前获取的缓冲区。SurfaceFlinger 必须始终显示内容,因此它会保留一个缓冲区。如果在某个层上没有提交缓冲区,则该层会被忽略。SurfaceFlinger 在收集可见层的所有缓冲区之后,便会询问 Hardware Composer 应如何进行合成。

其 Perfetto 主线程可用看到其主要是在收到 Vsync 信号后开始工作

SurfaceFlinger相关代码在下面,后续会专门写一篇SurfaceFlinger实现流程:

frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

4.1 SurfaceFlinger启动流程图解

4.2 SurfaceFlinger 端判断掉帧

通常我们通过 Perfetto 判断应用是否掉帧的时候,一般是直接看 SurfaceFlinger 部分,主要是下面几个步骤

  1. SurfaceFlinger 的主线程在每个 Vsync-SF 的时候是否没有合成?
  2. 如果没有合成操作,那么需要看没有合成的原因:
    1. 因为 SurfaceFlinger 检查发现没有可用的 Buffer 而没有合成操作?
    2. 因为 SurfaceFlinger 被其他的工作占用(比如截图、HWC 等)?
  1. 如果有合成操作,那么需要看对应的 App 的 可用 Buffer 个数是否正常:如果 App 此时可用 Buffer 为 0,那么看 App 端为何没有及时 queueBuffer(这就一般是应用自身的问题了),因为 SurfaceFlinger 合成操作触发可能是其他的进程有可用的 Buffer

下图为APP耗时导致的掉帧:

SurfaceFlinger 端可以看到 SurfaceFlinger 主线程和合成情况和应用对应的 BufferQueue 中 Buffer 的情况。如上图,就是一个掉帧的例子。App 没有及时渲染完成,且此时 BufferQueue 中也没有前几帧的 Buffer,所以这一帧 SurfaceFlinger 没有合成对应 App 的 Layer,在用户看来这里就掉了一帧

从 App 端无法看出是否掉帧,对应的 SurfaceFlinger 的 Trace 如下, 可以看到由于有 Triple Buffer 的存在, SF 这里有之前 App 的 Buffer,所以尽管 App 超过了一帧的时间, 但是 SF 这里依然有可用来合成的 Buffer, 所以没有掉帧

4.3 逻辑掉帧

上面的掉帧我们是从渲染这边来看的,这种掉帧在 Perfetto 中可以很容易就发现;还存在一种掉帧情况叫逻辑掉帧

逻辑掉帧指的是由于应用自己的代码逻辑问题,导致画面更新的时候,不是以均匀或者物理曲线的方式,而是出现跳跃更新的情况,这种掉帧一般在 Perfetto 上没法看出来,但是用户在使用的时候可以明显感觉到

举一个简单的例子,比如说列表滑动的时候,如果我们滑动松手后列表的每一帧前进步长是一个均匀变化的曲线,最后趋近于 0,这样就是完美的;但是如果出现这一帧相比上一帧走了 20,下一帧相比这一帧走了 10,下下一帧相比下一帧走了 30,这种就是跳跃更新,在 Perfetto 上每一帧都是及时渲染且 SurfaceFlinger 都及时合成的,但是用户用起来就是觉得会卡. 不过我列举的这个例子中,Android 已经针对这种情况做了优化,感兴趣的可以去看一下 android/view/animation/AnimationUtils.java 这个类,重点看下面三个方法的使用

1
2
3
 

public static void lockAnimationClock(long vsyncMillis)
public static void unlockAnimationClock()
public static long currentAnimationTimeMillis()
 

Android 系统的动画一般不会有这个问题,但是应用开发者就保不齐会写这种代码,比如做动画的时候根据当前的时间(而不是 Vsync 到来的时间)来计算动画属性变化的情况,这种情况下,一旦出现掉帧,动画的变化就会变得不均匀。

5、HWComposer 部分

  1. Hardware Composer HAL (HWC) 用于确定通过可用硬件来合成缓冲区的最有效方法。作为 HAL,其实现是特定于设备的,而且通常由显示设备硬件原始设备制造商 (OEM) 完成。
  2. 当您考虑使用叠加平面时,很容易发现这种方法的好处,它会在显示硬件(而不是 GPU)中合成多个缓冲区。例如,假设有一部普通 Android 手机,其屏幕方向为纵向,状态栏在顶部,导航栏在底部,其他区域显示应用内容。每个层的内容都在单独的缓冲区中。您可以使用以下任一方法处理合成(后一种方法可以显著提高效率):
    1. 将应用内容渲染到暂存缓冲区中,然后在其上渲染状态栏,再在其上渲染导航栏,最后将暂存缓冲区传送到显示硬件。
    2. 将三个缓冲区全部传送到显示硬件,并指示它从不同的缓冲区读取屏幕不同部分的数据。
  1. 显示处理器功能差异很大。叠加层的数量(无论层是否可以旋转或混合)以及对定位和叠加的限制很难通过 API 表达。为了适应这些选项,HWC 会执行以下计算(由于硬件供应商可以定制决策代码,因此可以在每台设备上实现最佳性能):
    1. SurfaceFlinger 向 HWC 提供一个完整的层列表,并询问“您希望如何处理这些层?”
    2. HWC 的响应方式是将每个层标记为叠加层或 GLES 合成。
    3. SurfaceFlinger 会处理所有 GLES 合成,将输出缓冲区传送到 HWC,并让 HWC 处理其余部分。
  1. 当屏幕上的内容没有变化时,叠加平面的效率可能会低于 GL 合成。当叠加层内容具有透明像素且叠加层混合在一起时,尤其如此。在此类情况下,HWC 可以选择为部分或全部层请求 GLES 合成,并保留合成的缓冲区。如果 SurfaceFlinger 返回来要求合成同一组缓冲区,HWC 可以继续显示先前合成的暂存缓冲区。这可以延长闲置设备的电池续航时间。
  2. 运行 Android 4.4 或更高版本的设备通常支持 4 个叠加平面。尝试合成的层数多于叠加层数会导致系统对其中一些层使用 GLES 合成,这意味着应用使用的层数会对能耗和性能产生重大影响。

四、Choreographer讲解

Choreographer 的引入,主要是配合 Vsync ,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统通过对 Vsync 信号周期的调整,来控制每一帧绘制操作的时机. 目前大部分手机都是 60Hz 的刷新率,也就是 16.6ms 刷新一次,系统为了配合屏幕的刷新频率,将 Vsync 的周期也设置为 16.6 ms,每个 16.6 ms , Vsync 信号唤醒 Choreographer 来做 App 的绘制操作 ,这就是引入 Choreographer 的主要作用. 了解 Choreographer 还可以帮助 App 开发者知道程序每一帧运行的基本原理,也可以加深对 Message、Handler、Looper、MessageQueue、Measure、Layout、Draw 的理解

1、主线程运行机制的本质

在讲 Choreographer 之前,我们先理一下 Android 主线程运行的本质,其实就是 Message 的处理过程,我们的各种操作,包括每一帧的渲染操作 ,都是通过 Message 的形式发给主线程的 MessageQueue ,MessageQueue 处理完消息继续等下一个消息,MethodTrace 如下图所示

1.1 引入Choreographer前

引入 Vsync 之前的 Android 版本,渲染一帧相关的 Message ,中间是没有间隔的,上一帧绘制完,下一帧的 Message 紧接着就开始被处理。这样的问题就是,帧率不稳定,可能高也可能低,不稳定,MethodTrace 如下图所示

对于用户来说,稳定的帧率才是好的体验,比如你玩王者荣耀,相比 fps 在 60 和 40 之间频繁变化,用户感觉更好的是稳定在 50 fps 的情况.

所以 Android 的演进中,引入了 Vsync + TripleBuffer + Choreographer 的机制,其主要目的就是提供一个稳定的帧率输出机制,让软件层和硬件层可以以共同的频率一起工作。

1.2 引入 Choreographer后

Choreographer 的引入,主要是配合 Vsync ,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统通过对 Vsync 信号周期的调整,来控制每一帧绘制操作的时机 ,Vsync 信号到来唤醒 Choreographer 来做 App 的绘制操作 ,如果每个 Vsync 周期应用都能渲染完成,给用户的感觉就是非常流畅,这就是引入 Choreographer 的主要作用.

上面主要是以60HZ为例,当然目前使用 90Hz 刷新率屏幕的手机越来越多,Vsync 周期从 16.6ms 到了 11.1ms,上图中的操作要在更短的时间内完成,对性能的要求也越来越高。

2、Choreographer 作用

Choreographer 扮演 Android 渲染链路中承上启下的角色

  1. 承上:负责接收和处理 App 的各种更新消息和回调,等到 Vsync 到来的时候统一处理。比如集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操作) ,判断卡顿掉帧情况,记录 CallBack 耗时等
  2. 启下:负责请求和接收 Vsync 信号。接收 Vsync 事件回调(通过 FrameDisplayEventReceiver.onVsync );请求 Vsync(FrameDisplayEventReceiver.scheduleVsync)

从上面可以看出来, Choreographer 担任的是一个工具人的角色,他之所以重要,是因为通过 Choreographer + SurfaceFlinger + Vsync + TripleBuffer 这一套从上到下的机制,保证了 Android App 可以以一个稳定的帧率运行(20fps、90fps 或者 60fps),减少帧率波动带来的不适感。

了解 Choreographer 还可以帮助 App 开发者知道程序每一帧运行的基本原理,也可以加深对 Message、Handler、Looper、MessageQueue、Input、Animation、Measure、Layout、Draw 的理解 , 很多 APM 工具也用到了 Choreographer( 利用 FrameCallback + FrameInfo ) + MessageQueue ( 利用 IdleHandler ) + Looper ( 设置自定义 MessageLogging) 这些组合拳,深入了解了这些之后,再去做优化,脑子里的思路会更清晰。

另外虽然画图是一个比较好的解释流程的好路子,但是我个人不是很喜欢画图,因为平时 Perfetto 和 MethodTrace 用的比较多,Perfetto 是按从左到右展示整个系统的运行情况的一个工具(包括 cpu、SurfaceFlinger、SystemServer、App 等关键进程),使用 PerfettoMethodTrace 也可以很方便地展示关键流程。当你对系统代码比较熟悉的时候,看 Perfetto 就可以和手机运行的实际情况对应起来。所以下面的文章除了一些网图之外,其他的我会多以 Perfetto 来展示。

2.1 从 Perfetto 的角度来看 Choreogrepher 的工作流程

下图以滑动推特APP为例子,我们先看一下从左到右滑动桌面的一个完整的预览图(App 进程),可以看到 Perfetto 中从左到右,每一个绿色的帧都表示一帧,表示最终我们可以手机上看到的画面

  1. 图中每一个紫色的条宽度是一个 Vsync 的时间,这个是120HZ也就是 8.3ms
  2. 每一帧处理的流程:接收到 Vsync 信号回调-> UI Thread –> RenderThread –> SurfaceFlinger(图中未显示)
  3. UI Thread 和 RenderThread 就可以完成 App 一帧的渲染,渲染完的 Buffer 抛给 SurfaceFlinger 去合成,然后我们就可以在屏幕上看到这一帧了
  4. 可以看到推特滑动的每一帧耗时都很短(Ui Thread 耗时 + RenderThread 耗时),但是由于 Vsync 的存在,每一帧都会等到 Vsync 才会去做处理

有了上面这个整体的概念,我们将 UI Thread 的每一帧放大来看,看看 Choreogrepher 的位置以及 Choreogrepher 是怎么组织每一帧的

2.2 Choreographer 的工作流程

  1. Choreographer 初始化
    1. 初始化 FrameHandler ,绑定 Looper
    2. 初始化 FrameDisplayEventReceiver ,与 SurfaceFlinger 建立通信用于接收和请求 Vsync
    3. 初始化 CallBackQueues
  1. SurfaceFlinger 的 appEventThread 唤醒发送 Vsync ,Choreographer 回调 FrameDisplayEventReceiver.onVsync , 进入 Choreographer 的主处理函数 doFrame
  2. Choreographer.doFrame 计算掉帧逻辑
  3. Choreographer.doFrame 处理 Choreographer 的第一个 callback : input
  4. Choreographer.doFrame 处理 Choreographer 的第二个 callback : animation
  5. Choreographer.doFrame 处理 Choreographer 的第三个 callback : insets animation
  6. Choreographer.doFrame 处理 Choreographer 的第四个 callback : traversal
    1. traversal-draw 中 UIThread 与 RenderThread 同步数据
  1. Choreographer.doFrame 处理 Choreographer 的第五个 callback : commit ?
  2. RenderThread 处理绘制命令,将处理好的绘制命令发给 GPU 处理
  3. 调用 swapBuffer 提交给 SurfaceFlinger 进行合成(此时 Buffer 并没有真正完成,需要等 CPU 完成后 SurfaceFlinger 才能真正使用,新版本的 Perfetto 中有 gpu 的 fence 来标识这个时间)

第一步初始化完成后,后续就会在步骤 2-9 之间循环

同时也附上这一帧所对应的 MethodTrace(这里预览一下即可,下面会有详细的大图)

下面我们就从源码的角度,来看一下具体的实现

3、源码解析

下面从源码的角度来简单看一下,源码只摘抄了部分重要的逻辑,其他的逻辑则被剔除,另外 Native 部分与 SurfaceFlinger 交互的部分也没有列入,不是本文的重点,有兴趣的可以自己去跟一下。

3.1 Choreographer 的初始化

Choreographer 的单例初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 

// Thread local storage for the choreographer.
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
// 获取当前线程的 Looper
Looper looper = Looper.myLooper();
......
// 构造 Choreographer 对象
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
 

Choreographer 的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 

private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
// 1. 初始化 FrameHandler
mHandler = new FrameHandler(looper);
// 2. 初始化 DisplayEventReceiver
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
//3. 初始化 CallbacksQueues
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
......
}
 

FrameHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 

private final class FrameHandler extends Handler {
......
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME://开始渲染下一帧的操作
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC://请求 Vsync
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK://处理 Callback
doScheduleCallback(msg.arg1);
break;
}
}
}
 

Choreographer 初始化链

在 Activity 启动过程,执行完 onResume 后,会调用 Activity.makeVisible(),然后再调用到 addView(), 层层调用会进入如下方法

1
2
3
4
5
6
7
8
9
 

ActivityThread.handleResumeActivity(IBinder, boolean, boolean, String) (android.app) 
-->WindowManagerImpl.addView(View, LayoutParams) (android.view) 
-->WindowManagerGlobal.addView(View, LayoutParams, Display, Window) (android.view) 
-->ViewRootImpl.ViewRootImpl(Context, Display) (android.view) 
public ViewRootImpl(Context context, Display display) {
......
mChoreographer = Choreographer.getInstance();
......
}
 

3.2 FrameDisplayEventReceiver

Vsync 的注册、申请、接收都是通过 FrameDisplayEventReceiver 这个类,所以可以先简单介绍一下。 FrameDisplayEventReceiver 继承 DisplayEventReceiver , 有三个比较重要的方法

  1. onVsync – Vsync 信号回调
  2. run – 执行 doFrame
  3. scheduleVsync – 请求 Vsync 信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 

private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
......
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
......
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}

public void scheduleVsync() {
......
nativeScheduleVsync(mReceiverPtr);
......
}
}
 

3.3 Choreographer 中 Vsync 的注册

从下面的函数调用栈可以看到,Choreographer 的内部类 FrameDisplayEventReceiver.onVsync 负责接收 Vsync 回调,通知 UIThread 进行数据处理。

那么 FrameDisplayEventReceiver 是通过什么方式在 Vsync 信号到来的时候回调 onVsync 呢?答案是 FrameDisplayEventReceiver 的初始化的时候,最终通过监听文件句柄的形式,其对应的初始化流程如下

android/view/Choreographer.java

1
2
3
4
5
6
7
 

private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
......
}
 

android/view/Choreographer.java

1
2
3
 

public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}
 

android/view/DisplayEventReceiver.java

1
2
3
4
5
6
 

public DisplayEventReceiver(Looper looper, int vsyncSource) {
......
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
vsyncSource);
}
 

简单来说,FrameDisplayEventReceiver 的初始化过程中,通过 BitTube(本质是一个 socket pair),来传递和请求 Vsync 事件,当 SurfaceFlinger 收到 Vsync 事件之后,通过 appEventThread 将这个事件通过 BitTube 传给 DisplayEventDispatcher ,DisplayEventDispatcher 通过 BitTube 的接收端监听到 Vsync 事件之后,回调 Choreographer.FrameDisplayEventReceiver.onVsync ,触发开始一帧的绘制,如下图

3.4 Choreographer 处理一帧的逻辑

Choreographer 处理绘制的逻辑核心在 Choreographer.doFrame 函数中,从下图可以看到,FrameDisplayEventReceiver.onVsync post 了自己,其 run 方法直接调用了 doFrame 开始一帧的逻辑处理

android/view/Choreographer.java

1
2
3
4
5
6
7
8
9
10
11
12
 

public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
......
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
 

doFrame 函数主要做下面几件事

  1. 计算掉帧逻辑
  2. 记录帧绘制信息
  3. 执行 CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_INSETS_ANIMATION、CALLBACK_TRAVERSAL、CALLBACK_COMMIT
计算掉帧逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 

void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
......
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
}
......
}
......
}
 

Choreographer.doFrame 的掉帧检测比较简单,从下图可以看到,Vsync 信号到来的时候会标记一个 start_time ,执行 doFrame 的时候标记一个 end_time ,这两个时间差就是 Vsync 处理时延,也就是掉帧

记录帧绘制信息

Choreographer 中 FrameInfo 来负责记录帧的绘制信息,doFrame 执行的时候,会把每一个关键节点的绘制时间记录下来,我们使用 dumpsys gfxinfo 就可以看到。当然 Choreographer 只是记录了一部分,剩余的部分在 hwui 那边来记录。

从 FrameInfo 这些标志就可以看出记录的内容,后面我们看 dumpsys gfxinfo 的时候数据就是按照这个来排列的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 

// Various flags set to provide extra metadata about the current frame
private static final int FLAGS = 0;

// Is this the first-draw following a window layout?
public static final long FLAG_WINDOW_LAYOUT_CHANGED = 1;

// A renderer associated with just a Surface, not with a ViewRootImpl instance.
public static final long FLAG_SURFACE_CANVAS = 1 << 2;

@LongDef(flag = true, value = {
FLAG_WINDOW_LAYOUT_CHANGED, FLAG_SURFACE_CANVAS })
@Retention(RetentionPolicy.SOURCE)
public @interface FrameInfoFlags {}

// The intended vsync time, unadjusted by jitter
private static final int INTENDED_VSYNC = 1;

// Jitter-adjusted vsync time, this is what was used as input into the
// animation & drawing system
private static final int VSYNC = 2;

// The time of the oldest input event
private static final int OLDEST_INPUT_EVENT = 3;

// The time of the newest input event
private static final int NEWEST_INPUT_EVENT = 4;

// When input event handling started
private static final int HANDLE_INPUT_START = 5;

// When animation evaluations started
private static final int ANIMATION_START = 6;

// When ViewRootImpl#performTraversals() started
private static final int PERFORM_TRAVERSALS_START = 7;

// When View:draw() started
private static final int DRAW_START = 8;
 

doFrame 函数记录从 Vsync time 到 markPerformTraversalsStart 的时间

1
2
3
4
5
6
7
8
9
10
11
12
13
 

void doFrame(long frameTimeNanos, int frame) {
......
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
// 处理 CALLBACK_INPUT Callbacks
mFrameInfo.markInputHandlingStart();
// 处理 CALLBACK_ANIMATION Callbacks
mFrameInfo.markAnimationsStart();
// 处理 CALLBACK_INSETS_ANIMATION Callbacks
// 处理 CALLBACK_TRAVERSAL Callbacks
mFrameInfo.markPerformTraversalsStart();
// 处理 CALLBACK_COMMIT Callbacks
......
}
 

执行 Callbacks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 

void doFrame(long frameTimeNanos, int frame) {
......
// 处理 CALLBACK_INPUT Callbacks
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
// 处理 CALLBACK_ANIMATION Callbacks
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
// 处理 CALLBACK_INSETS_ANIMATION Callbacks
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
// 处理 CALLBACK_TRAVERSAL Callbacks
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
// 处理 CALLBACK_COMMIT Callbacks
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
......
}
 

Input 回调调用栈

input callback 一般是执行 ViewRootImpl.ConsumeBatchedInputRunnable

android/view/ViewRootImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 

final class ConsumeBatchedInputRunnable implements Runnable {
@Override
public void run() {
doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
}
}
void doConsumeBatchedInput(long frameTimeNanos) {
if (mConsumeBatchedInputScheduled) {
mConsumeBatchedInputScheduled = false;
if (mInputEventReceiver != null) {
if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)
&& frameTimeNanos != -1) {
scheduleConsumeBatchedInput();
}
}
doProcessInputEvents();
}
}
 

Input 时间经过处理,最终会传给 DecorView 的 dispatchTouchEvent,这就到了我们熟悉的 Input 事件分发

Animation 回调调用栈

一般我们接触的多的是调用 View.postOnAnimation 的时候,会使用到 CALLBACK_ANIMATION

1
2
3
4
5
6
7
8
9
10
11
 

public void postOnAnimation(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.mChoreographer.postCallback(
Choreographer.CALLBACK_ANIMATION, action, null);
} else {
// Postpone the runnable until we know
// on which thread it needs to run.
getRunQueue().post(action);
}
}
 

那么一般是什么时候回调用到 View.postOnAnimation 呢,我截取了一张图,大家可以自己去看一下,接触最多的应该是 startScroll,Fling 这种操作

其调用栈根据其 post 的内容,下面是桌面滑动松手之后的 fling 动画。

另外我们的 Choreographer 的 FrameCallback 也是用的 CALLBACK_ANIMATION

1
2
3
4
5
6
7
8
 

public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}

postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
 

Traversal 调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
 

void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//为了提高优先级,先 postSyncBarrier
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}

final class TraversalRunnable implements Runnable {
@Override
public void run() {
// 真正开始执行 measure、layout、draw
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 这里把 SyncBarrier remove
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 真正开始
performTraversals();
}
}
private void performTraversals() {
// measure 操作
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// layout 操作
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
// draw 操作
if (!cancelDraw && !newSurface) {
performDraw();
}
}
 

doTraversal 的 TraceView 示例

3.5 下一帧的 Vsync 请求

由于动画、滑动、Fling 这些操作的存在,我们需要一个连续的、稳定的帧率输出机制。这就涉及到了 Vsync 的请求逻辑,在连续的操作,比如动画、滑动、Fling 这些情况下,每一帧的 doFrame 的时候,都会根据情况触发下一个 Vsync 的申请,这样我们就可以获得连续的 Vsync 信号。

看下面的 scheduleTraversals 调用栈(scheduleTraversals 中会触发 Vsync 请求)


我们比较熟悉的 invalidate 和 requestLayout 都会触发 Vsync 信号请求

我们下面以 Animation 为例,看看 Animation 是如何驱动下一个 Vsync ,来持续更新画面的

ObjectAnimator 动画驱动逻辑

android/animation/ObjectAnimator.java

1
2
3
 

public void start() {
super.start();
}
 

android/animation/ValueAnimator.java

1
2
3
4
5
6
7
8
9
 

private void start(boolean playBackwards) {
......
addAnimationCallback(0); // 动画 start 的时候添加 Animation Callback
......
}
private void addAnimationCallback(long delay) {
......
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
 

android/animation/AnimationHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 

public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
// post FrameCallback
getProvider().postFrameCallback(mFrameCallback);
}
......
}

// 这里的 mFrameCallback 回调 doFrame,里面 post了自己
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
// post 自己
getProvider().postFrameCallback(this);
}
}
};
 

调用 postFrameCallback 会走到 mChoreographer.postFrameCallback ,这里就会触发 Choreographer 的 Vsync 请求逻辑

android/animation/AnimationHandler.java

1
2
3
 

public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
 

android/view/Choreographer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 

private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

if (dueTime <= now) {
// 请求 Vsync scheduleFrameLocked ->scheduleVsyncLocked-> mDisplayEventReceiver.scheduleVsync ->nativeScheduleVsync
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
 

通过上面的 Animation.start 设置,利用了 Choreographer.FrameCallback 接口,每一帧都去请求下一个 Vsync
动画过程中一帧的 TraceView 示例

3.6 源码小结

  1. Choreographer 是线程单例的,而且必须要和一个 Looper 绑定,因为其内部有一个 Handler 需要和 Looper 绑定,一般是 App 主线程的 Looper 绑定
  2. DisplayEventReceiver 是一个 abstract class,其 JNI 的代码部分会创建一个IDisplayEventConnection 的 Vsync 监听者对象。这样,来自 AppEventThread 的 VSYNC 中断信号就可以传递给 Choreographer 对象了。当 Vsync 信号到来时,DisplayEventReceiver 的 onVsync 函数将被调用。
  3. DisplayEventReceiver 还有一个 scheduleVsync 函数。当应用需要绘制UI时,将首先申请一次 Vsync 中断,然后再在中断处理的 onVsync 函数去进行绘制。
  4. Choreographer 定义了一个 FrameCallback interface,每当 Vsync 到来时,其 doFrame 函数将被调用。这个接口对 Android Animation 的实现起了很大的帮助作用。以前都是自己控制时间,现在终于有了固定的时间中断。
  5. Choreographer 的主要功能是,当收到 Vsync 信号时,去调用使用者通过 postCallback 设置的回调函数。目前一共定义了五种类型的回调,它们分别是:
    1. CALLBACK_INPUT : 处理输入事件处理有关
    2. CALLBACK_ANIMATION : 处理 Animation 的处理有关
    3. CALLBACK_INSETS_ANIMATION : 处理 Insets Animation 的相关回调
    4. CALLBACK_TRAVERSAL : 处理和 UI 等控件绘制有关
    5. CALLBACK_COMMIT : 处理 Commit 相关回调,主要是是用于执行组件 Application/Activity/Service 的 onTrimMemory,在 ApplicationThread 的 scheduleTrimMemory 方法中向 Choreographer 插入的;另外这个 Callback 也提供了一个监测一帧耗时的时机
  1. ListView 的 Item 初始化(obtain\setup) 会在 input 里面也会在 animation 里面,这取决于
  2. CALLBACK_INPUTCALLBACK_ANIMATION 会修改 view 的属性,所以要先与 CALLBACK_TRAVERSAL 执行

4、APM 与 Choreographer

由于 Choreographer 的位置,许多性能监控的手段都是利用 Choreographer 来做的,除了自带的掉帧计算,Choreographer 提供的 FrameCallback 和 FrameInfo 都给 App 暴露了接口,让 App 开发者可以通过这些方法监控自身 App 的性能,其中常用的方法如下:

  1. 利用 FrameCallback 的 doFrame 回调
  2. 利用 FrameInfo 进行监控
    1. 使用 :adb shell dumpsys gfxinfo framestats
    2. 示例 :adb shell dumpsys gfxinfo com.meizu.flyme.launcher framestats
  1. 利用 SurfaceFlinger 进行监控
    1. 使用 :adb shell dumpsys SurfaceFlinger –latency
    2. 示例 :adb shell dumpsys SurfaceFlinger –latency com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher#0
  1. 利用 SurfaceFlinger PageFlip 机制进行监控
    1. 使用 : adb service call SurfaceFlinger 1013
    2. 备注:需要系统权限
  1. Choreographer 自身的掉帧计算逻辑
  2. BlockCanary 基于 Looper 的性能监控

4.1 利用 FrameCallback 的 doFrame 回调

FrameCallback 接口

1
2
3
 

public interface FrameCallback {
public void doFrame(long frameTimeNanos);
}
 

接口使用

1
 

Choreographer.getInstance().postFrameCallback(youOwnFrameCallback );
 

接口处理

1
2
3
4
5
 

public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
......
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
 

4.2 利用 FrameInfo 进行监控

adb shell dumpsys gfxinfo framestats

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 

Window: StatusBar
Stats since: 17990256398ns
Total frames rendered: 1562
Janky frames: 361 (23.11%)
50th percentile: 6ms
90th percentile: 23ms
95th percentile: 36ms
99th percentile: 101ms
Number Missed Vsync: 33
Number High input latency: 683
Number Slow UI thread: 273
Number Slow bitmap uploads: 8
Number Slow issue draw commands: 18
Number Frame deadline missed: 287
HISTOGRAM: 5ms=670 6ms=128 7ms=84 8ms=63 9ms=38 10ms=23 11ms=21 12ms=20 13ms=25 14ms=39 15ms=65 16ms=36 17ms=51 18ms=37 19ms=41 20ms=20 21ms=19 22ms=18 23ms=15 24ms=14 25ms=8 26ms=4 27ms=6 28ms=3 29ms=4 30ms=2 31ms=2 32ms=6 34ms=12 36ms=10 38ms=9 40ms=3 42ms=4 44ms=5 46ms=8 48ms=6 53ms=6 57ms=4 61ms=1 65ms=0 69ms=2 73ms=2 77ms=3 81ms=4 85ms=1 89ms=2 93ms=0 97ms=2 101ms=1 105ms=1 109ms=1 113ms=1 117ms=1 121ms=2 125ms=1 129ms=0 133ms=1 150ms=2 200ms=3 250ms=0 300ms=1 350ms=1 400ms=0 450ms=0 500ms=0 550ms=0 600ms=0 650ms=0

---PROFILEDATA---
Flags,IntendedVsync,Vsync,OldestInputEvent,NewestInputEvent,HandleInputStart,AnimationStart,PerformTraversalsStart,DrawStart,SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers,FrameCompleted,DequeueBufferDuration,QueueBufferDuration,
0,10158314881426,10158314881426,9223372036854775807,0,10158315693363,10158315760759,10158315769821,10158316032165,10158316627842,10158316838988,10158318055915,10158320387269,10158321770654,428000,773000,
0,10158332036261,10158332036261,9223372036854775807,0,10158332799196,10158332868519,10158332877269,10158333137738,10158333780654,10158333993206,10158335078467,10158337689561,10158339307061,474000,885000,
0,10158348665353,10158348665353,9223372036854775807,0,10158349710238,10158349773102,10158349780863,10158350405863,10158351135967,10158351360446,10158352300863,10158354305654,10158355814509,471000,836000,
0,10158365296729,10158365296729,9223372036854775807,0,10158365782373,10158365821019,10158365825238,10158365975290,10158366547946,10158366687217,10158367240706,10158368429248,10158369291852,269000,476000,
 

4.3 利用 SurfaceFlinger 进行监控

命令解释:

  1. 数据的单位是纳秒,时间是以开机时间为起始点
  2. 每一次的命令都会得到128行的帧相关的数据

数据:

  1. 第一行数据,表示刷新的时间间隔refresh_period
  2. 第1列:这一部分的数据表示应用程序绘制图像的时间点
  3. 第2列:在SF(软件)将帧提交给H/W(硬件)绘制之前的垂直同步时间,也就是每帧绘制完提交到硬件的时间戳,该列就是垂直同步的时间戳
  4. 第3列:在SF将帧提交给H/W的时间点,算是H/W接受完SF发来数据的时间点,绘制完成的时间点。

掉帧 jank 计算

每一行都可以通过下面的公式得到一个值,该值是一个标准,我们称为jankflag,如果当前行的jankflag与上一行的jankflag发生改变,那么就叫掉帧

ceil((C - A) / refresh-period)

4.4 利用 SurfaceFlinger PageFlip 机制进行监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 

Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
                data.writeInterfaceToken("android.ui.ISurfaceComposer");
mFlinger.transact(1013, data, reply, 0);
final int pageFlipCount = reply.readInt();

final long now = System.nanoTime();
final int frames = pageFlipCount - mLastPageFlipCount;
final long duration = now - mLastUpdateTime;
mFps = (float) (frames * 1e9 / duration);
mLastPageFlipCount = pageFlipCount;
mLastUpdateTime = now;
reply.recycle();
data.recycle();
 

4.5 Choreographer 自身的掉帧计算逻辑

SKIPPED_FRAME_WARNING_LIMIT 默认为30 , 由 debug.choreographer.skipwarning 这个属性控制

1
2
3
4
5
6
7
 

if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
}
 

4.6 BlockCanary

Blockcanary 做性能监控使用的是 Looper 的消息机制,通过对 MessageQueue 中每一个 Message 的前后进行记录,打到监控性能的目的

android/os/Looper.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 

public static void loop() {
...
for (;;) {
...
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
...
}
}
 

5、MessageQueue 与 Choreographer

所谓的异步消息其实就是这样的,我们可以通过 enqueueBarrier 往消息队列中插入一个 Barrier,那么队列中执行时间在这个 Barrier 以后的同步消息都会被这个 Barrier 拦截住无法执行,直到我们调用 removeBarrier 移除了这个 Barrier,而异步消息则没有影响,消息默认就是同步消息,除非我们调用了 Message 的 setAsynchronous,这个方法是隐藏的。只有在初始化 Handler 时通过参数指定往这个 Handler 发送的消息都是异步的,这样在 Handler 的 enqueueMessage 中就会调用 Message 的 setAsynchronous 设置消息是异步的,从上面 Handler.enqueueMessage 的代码中可以看到。

所谓异步消息,其实只有一个作用,就是在设置 Barrier 时仍可以不受 Barrier 的影响被正常处理,如果没有设置 Barrier,异步消息就与同步消息没有区别,可以通过 removeSyncBarrier 移除 Barrier

5.1 SyncBarrier 在 Choreographer 中使用的一个示例

scheduleTraversals 的时候 postSyncBarrier

1
2
3
4
5
6
7
8
9
 

void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//为了提高优先级,先 postSyncBarrier
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
 

doTraversal 的时候 removeSyncBarrier

1
2
3
4
5
6
7
8
9
 

void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 这里把 SyncBarrier remove
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 真正开始
performTraversals();
}
}
 

Choreographer post Message 的时候,会把这些消息设为 Asynchronous ,这样 Choreographer 中的这些 Message 的优先级就会比较高,

1
2
3
4
 

Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
 

五、SystemServer

1、窗口动画

Systrace 中的 SystemServer 一个比较重要的地方就是窗口动画,由于窗口归 SystemServer 来管,那么窗口动画也就是由 SystemServer 来进行统一的处理,其中涉及到两个比较重要的线程,Android.Anim 和 Android.Anim.if 这两个线程。

2、ActivityManagerService

AMS 和 WMS 算是 SystemServer 中最繁忙的两个 Service 了,与 AMS 相关的 Trace 一般会用 TRACE_TAG_ACTIVITY_MANAGER 这个 TAG,在 Systrace 中的名字是 ActivityManager

下面是启动一个新的进程的时候,AMS 的输出

在进程和四大组件的各种场景一般都会有对应的 Trace 点来记录,比如大家熟悉的 ActivityStart、ActivityResume、activityStop 等,这些 Trace 点有一些在应用进程,有一些在 SystemServer 进程,所以大家在看 Activity 相关的代码逻辑的时候,需要不断在这两个进程之间进行切换,这样才能从一个整体的角度来看应用的状态变化和 SystemServer 在其中起到的作用。

3、WindowManagerService

与 WMS 相关的 Trace 一般会用 TRACE_TAG_WINDOW_MANAGER 这个 TAG,在 Systrace 中 WindowManagerService 在 SystemServer 中多在对应的 Binder 中出现,比如下面应用启动的时候,relayoutWindow 的 Trace 输出

在 Window 的各种场景一般都会有对应的 Trace 点来记录,比如大家熟悉的 relayoutWIndow、performLayout、prepareToDisplay 等

4、Input

4.1 input在Perfetto的实现流程

下面这张图是一个概览图,以滑动推特app为例 (滑动应用包括一个 Input_Down 事件 + 若干个 Input_Move 事件 + 一个 Input_Up 事件,这些事件和事件流都会在 Perfetto 上有所体现,这也是我们分析 Perfetto 的一个重要的切入点),主要牵扯到的模块是 SystemServer 和 App 模块,下图是他们各个区域在Perfetto上表现

放大后Input在Perfetto上的表现

InputReaderInputDispatcher 是跑在 SystemServer 里面的两个 Native 线程,负责读取和分发 Input 事件,我们分析 Perfetto 的 Input 事件流,首先是找到这里。下面针对上图中标号进行简单说明

1.触摸屏每隔几毫秒扫描一次,如果有触摸事件,那么把事件上报到对应的驱动

2.InputReader 负责从 EventHub 里面把 Input 事件读取出来,放到 InboundQueue 中然后交给 InputDispatcher 进行事件分发

3.InputDispatcher 从 InboundQueue 中取出 Input 事件进行包装然后派发到各个 App(连接) 的 OutBoundQueue ,同时将事件记录到各个 App(连接) 的 WaitQueue。

4.App 接收到 Input 事件,同时记录到 PendingInputEventQueue ,然后对事件进行分发处理

5.App 处理完成后,回调 InputManagerService 将负责监听的 WaitQueue 中对应的 Input 移除

注:

OutboundQueue 里面放的是即将要被派发给对应 AppConnection 的事件

WaitQueue 里面记录的是已经派发给 AppConnection 但是 App 还在处理没有返回处理成功的事件

deliverInputEvent 标识 App UI Thread 被 Input 事件唤醒

PendingInputEventQueue 里面记录的是 App 需要处理的 Input 事件,这里可以看到已经到了应用进程 。

通过上面的流程,一次 Input 事件就被消耗掉了(当然这只是正常情况,还有很多异常情况、细节处理,这里就不细说了,自己看相关流程的时候可以深挖一下)

① InputReader

InputReader 是一个 Native 线程,跑在 SystemServer 进程里面,其核心功能是从 EventHub 读取事件、进行加工、将加工好的事件发送到 InputDispatcher

InputReader Loop 流程如下

  1. getEvents:通过 EventHub (监听目录 /dev/input )读取事件放入 mEventBuffer ,而mEventBuffer 是一个大小为256的数组, 再将事件 input_event 转换为 RawEvent
  2. processEventsLocked: 对事件进行加工, 转换 RawEvent -> NotifyKeyArgs(NotifyArgs)
  3. QueuedListener->flush:将事件发送到 InputDispatcher 线程, 转换 NotifyKeyArgs -> KeyEntry(EventEntry)

核心代码 loopOnce 处理流程如下:

InputReader 核心 Loop 函数 loopOnce 逻辑如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 

void InputReader::loopOnce() {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    std::vector<InputDeviceInfo> inputDevices;
    { // acquire lock
......
//获取输入事件、设备增删事件,count 为事件数量
    size_t count = mEventHub ->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    {
......
        if (count) { //处理事件
            processEventsLocked(mEventBuffer, count);
        }

    }
......
    mQueuedListener->flush();//将事件传到 InputDispatcher,这里getListener 得到的就是 InputDispatcher
}
 

② InputDispatcher

上面的 InputReader 调用 mQueuedListener->flush 之后 ,将 Input 事件加入到InputDispatcher 的 mInboundQueue ,然后唤醒 InputDispatcher

InputDispatcher 的核心逻辑如下:

  1. dispatchOnceInnerLocked(): 从 InputDispatcher 的 mInboundQueue 队列,取出事件 EventEntry。另外该方法开始执行的时间点 (currentTime) 便是后续事件 dispatchEntry 的分发时间 (deliveryTime)
  2. dispatchKeyLocked():满足一定条件时会添加命令 doInterceptKeyBeforeDispatchingLockedInterruptible;
  3. enqueueDispatchEntryLocked():生成事件 DispatchEntry 并加入 connection 的 outbound 队列
  4. startDispatchCycleLocked():从 outboundQueue 中取出事件 DispatchEntry, 重新放入 connection 的 waitQueue 队列;
  5. InputChannel.sendMessage 通过 socket 方式将消息发送给远程进程;
  6. runCommandsLockedInterruptible():通过循环遍历的方式,依次处理 mCommandQueue 队列中的所有命令。而 mCommandQueue 队列中的命令是通过 postCommandLocked() 方式向该队列添加的。

其核心处理逻辑在 dispatchOnceInnerLocked 这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    // Ready to start a new event.
    // If we don't already have a pending event, go grab one.
    if (! mPendingEvent) {
        if (mInboundQueue.isEmpty()) {
        } else {
            // Inbound queue has at least one entry.
            mPendingEvent = mInboundQueue.dequeueAtHead();
            traceInboundQueueLengthLocked();
        }

        // Poke user activity for this event.
        if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
            pokeUserActivityLocked(mPendingEvent);
        }

        // Get ready to dispatch the event.
        resetANRTimeoutsLocked();
    }
    case EventEntry::TYPE_MOTION: {
        done = dispatchMotionLocked(currentTime, typedEntry,
                &dropReason, nextWakeupTime);
        break;
    }

    if (done) {
        if (dropReason != DROP_REASON_NOT_DROPPED) {
            dropInboundEventLocked(mPendingEvent, dropReason);
        }
        mLastDropReason = dropReason;
        releasePendingEventLocked();
        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
    }
}
 

③ InboundQueue

InputDispatcher 执行 notifyKey 的时候,会将 Input 事件封装后放到 InboundQueue 中,后续 InputDispatcher 循环处理 Input 事件的时候,就是从 InboundQueue 取出事件然后做处理

④ OutboundQueue

Outbound 意思是出站,这里的 OutboundQueue 指的是要被 App 拿去处理的事件队列,每一个 App(Connection) 都对应有一个 OutboundQueue ,从 InboundQueue 那一节的图来看,事件会先进入 InboundQueue ,然后被 InputDIspatcher 派发到各个 App 的 OutboundQueue

⑤ waitQueue

当 InputDispatcher 将 Input 事件分发出去之后,将 DispatchEntry 从 outboundQueue 中取出来放到 WaitQueue 中,当 publish 出去的事件被处理完成(finished),InputManagerService 就会从应用中得到一个回复,此时就会取出 WaitQueue 中的事件,从 Perfetto 中看就是对应 App 的 WaitQueue 减少

如果主线程发生卡顿,那么 Input 事件没有及时被消耗,也会在 WaitQueue 这里体现出来,如下图:

4.2 Input 刷新与 Vsync

Input 的刷新取决于触摸屏的采样,目前比较多的屏幕采样率是 120Hz,对应就是 8.3ms 采样一次,我们来看一下其在 Perfetto 上的展示

可以看到上图中, InputReader 每隔 8.3ms 就可以读上来一个数据,交给 InputDispatcher 去分发给 App ,那么是不是屏幕采样率越高越好呢?也不一定,比如上面那张图,虽然 InputReader 每隔 8.3ms 就可以读上来一个数据给 InputDispatcher 去分发给 App ,但是从 WaitQueue 的表现来看,应用并没有消耗这个 Input 事件,这是为什么呢?

原因在于应用消耗 Input 事件的时机是 Vsync 信号来了之后,刷新率为 60Hz 的屏幕,一般系统也是 60 fps ,也就是说两个 Vsync 的间隔在 16.6ms ,这期间如果有两个或者三个 Input 事件,那么必然有一个或者两个要被抛弃掉,只拿最新的那个。也就是说:

  1. 在屏幕刷新率和系统 FPS 都是 60 的时候,盲目提高触摸屏的采样率,是没有太大的效果的,反而有可能出现上面图中那样,有的 Vsync 周期中有两个 Input 事件,而有的 Vsync 周期中有三个 Input 事件,这样造成事件不均匀,可能会使 UI 产生抖动
  2. 在屏幕刷新率和系统 FPS 都是 60 的时候,使用 120Hz 采样率的触摸屏就可以了
  3. 如果在屏幕刷新率和系统 FPS 都是 90 的时候 ,那么 120Hz 采样率的触摸屏显然不够用了,这时候应该采用 180Hz 采样率的屏幕

4.3 Input 调试信息

Dumpsys Input 主要是 Debug 用,我们也可以来看一下其中的一些关键信息,到时候遇到了问题也可以从这里面找 , 其命令如下:

1
 

adb shell dumpsys input
 

其中的输出比较多,我们终点截取 Device 信息、InputReader、InputDispatcher 三段来看就可以了

① Device 信息

主要是目前连接上的 Device 信息,下面摘取的是 touch 相关的

1
2
3
4
5
6
7
8
9
10
11
12
13
 

    3: main_touch
      Classes: 0x00000015
      Path: /dev/input/event6
      Enabled: true
      Descriptor: 4055b8a032ccf50ef66dbe2ff99f3b2474e9eab5
      Location: main_touch/input0
      ControllerNumber: 0
      UniqueId: 
      Identifier: bus=0x0000, vendor=0xbeef, product=0xdead, version=0x28bb
      KeyLayoutFile: /system/usr/keylayout/main_touch.kl
      KeyCharacterMapFile: /system/usr/keychars/Generic.kcm
      ConfigurationFile: 
      HaveKeyboardLayoutOverlay: false
 

② Input Reader 状态

InputReader 这里就是当前 Input 事件的一些展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
 

  Device 3: main_touch
    Generation: 24
    IsExternal: false
    HasMic:     false
    Sources: 0x00005103
    KeyboardType: 1
    Motion Ranges:
      X: source=0x00005002, min=0.000, max=1079.000, flat=0.000, fuzz=0.000, resolution=0.000
      Y: source=0x00005002, min=0.000, max=2231.000, flat=0.000, fuzz=0.000, resolution=0.000
      PRESSURE: source=0x00005002, min=0.000, max=1.000, flat=0.000, fuzz=0.000, resolution=0.000
      SIZE: source=0x00005002, min=0.000, max=1.000, flat=0.000, fuzz=0.000, resolution=0.000
      TOUCH_MAJOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000
      TOUCH_MINOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000
      TOOL_MAJOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000
      TOOL_MINOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000
    Keyboard Input Mapper:
      Parameters:
        HasAssociatedDisplay: false
        OrientationAware: false
        HandlesKeyRepeat: false
      KeyboardType: 1
      Orientation: 0
      KeyDowns: 0 keys currently down
      MetaState: 0x0
      DownTime: 521271703875000
    Touch Input Mapper (mode - direct):
      Parameters:
        GestureMode: multi-touch
        DeviceType: touchScreen
        AssociatedDisplay: hasAssociatedDisplay=true, isExternal=false, displayId=''
        OrientationAware: true
      Raw Touch Axes:
        X: min=0, max=1080, flat=0, fuzz=0, resolution=0
        Y: min=0, max=2232, flat=0, fuzz=0, resolution=0
        Pressure: min=0, max=127, flat=0, fuzz=0, resolution=0
        TouchMajor: min=0, max=512, flat=0, fuzz=0, resolution=0
        TouchMinor: unknown range
        ToolMajor: unknown range
        ToolMinor: unknown range
        Orientation: unknown range
        Distance: unknown range
        TiltX: unknown range
        TiltY: unknown range
        TrackingId: min=0, max=65535, flat=0, fuzz=0, resolution=0
        Slot: min=0, max=20, flat=0, fuzz=0, resolution=0
      Calibration:
        touch.size.calibration: geometric
        touch.pressure.calibration: physical
        touch.orientation.calibration: none
        touch.distance.calibration: none
        touch.coverage.calibration: none
      Affine Transformation:
        X scale: 1.000
        X ymix: 0.000
        X offset: 0.000
        Y xmix: 0.000
        Y scale: 1.000
        Y offset: 0.000
      Viewport: displayId=0, orientation=0, logicalFrame=[0, 0, 1080, 2232], physicalFrame=[0, 0, 1080, 2232], deviceSize=[1080, 2232]
      SurfaceWidth: 1080px
      SurfaceHeight: 2232px
      SurfaceLeft: 0
      SurfaceTop: 0
      PhysicalWidth: 1080px
      PhysicalHeight: 2232px
      PhysicalLeft: 0
      PhysicalTop: 0
      SurfaceOrientation: 0
      Translation and Scaling Factors:
        XTranslate: 0.000
        YTranslate: 0.000
        XScale: 0.999
        YScale: 1.000
        XPrecision: 1.001
        YPrecision: 1.000
        GeometricScale: 0.999
        PressureScale: 0.008
        SizeScale: 0.002
        OrientationScale: 0.000
        DistanceScale: 0.000
        HaveTilt: false
        TiltXCenter: 0.000
        TiltXScale: 0.000
        TiltYCenter: 0.000
        TiltYScale: 0.000
      Last Raw Button State: 0x00000000
      Last Raw Touch: pointerCount=1
        [0]: id=0, x=660, y=1338, pressure=44, touchMajor=44, touchMinor=44, toolMajor=0, toolMinor=0, orientation=0, tiltX=0, tiltY=0, distance=0, toolType=1, isHovering=false
      Last Cooked Button State: 0x00000000
      Last Cooked Touch: pointerCount=1
        [0]: id=0, x=659.389, y=1337.401, pressure=0.346, touchMajor=43.970, touchMinor=43.970, toolMajor=43.970, toolMinor=43.970, orientation=0.000, tilt=0.000, distance=0.000, toolType=1, isHovering=false
      Stylus Fusion:
        ExternalStylusConnected: false
        External Stylus ID: -1
        External Stylus Data Timeout: 9223372036854775807
      External Stylus State:
        When: 9223372036854775807
        Pressure: 0.000000
        Button State: 0x00000000
        Tool Type: 0
 

③ InputDispatcher 状态

InputDispatch 这里的重要信息主要包括

  1. FocusedApplication :当前获取焦点的应用
  2. FocusedWindow : 当前获取焦点的窗口
  3. TouchStatesByDisplay
  4. Windows :所有的 Window
  5. MonitoringChannels :Window 对应的 Channel
  6. Connections :所有的连接
  7. AppSwitch: not pending
  8. Configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 

Input Dispatcher State:
  DispatchEnabled: 1
  DispatchFrozen: 0
  FocusedApplication: name='AppWindowToken{ac6ec28 token=Token{a38a4b ActivityRecord{7230f1a u0 com.meizu.flyme.launcher/.Launcher t13}}}', dispatchingTimeout=5000.000ms
  FocusedWindow: name='Window{3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher}'
  TouchStatesByDisplay:
    0: down=true, split=true, deviceId=3, source=0x00005002
      Windows:
        0: name='Window{3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher}', pointerIds=0x80000000, targetFlags=0x105
        1: name='Window{8cb8f7 u0 com.android.systemui.ImageWallpaper}', pointerIds=0x0, targetFlags=0x4102
  Windows:
    2: name='Window{ba2fc6b u0 NavigationBar}', displayId=0, paused=false, hasFocus=false, hasWallpaper=false, visible=true, canReceiveKeys=false, flags=0x21840068, type=0x000007e3, layer=0, frame=[0,2136][1080,2232], scale=1.000000, touchableRegion=[0,2136][1080,2232], inputFeatures=0x00000000, ownerPid=26514, ownerUid=10033, dispatchingTimeout=5000.000ms
    3: name='Window{72b7776 u0 StatusBar}', displayId=0, paused=false, hasFocus=false, hasWallpaper=false, visible=true, canReceiveKeys=false, flags=0x81840048, type=0x000007d0, layer=0, frame=[0,0][1080,84], scale=1.000000, touchableRegion=[0,0][1080,84], inputFeatures=0x00000000, ownerPid=26514, ownerUid=10033, dispatchingTimeout=5000.000ms
    9: name='Window{3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher}', displayId=0, paused=false, hasFocus=true, hasWallpaper=true, visible=true, canReceiveKeys=true, flags=0x81910120, type=0x00000001, layer=0, frame=[0,0][1080,2232], scale=1.000000, touchableRegion=[0,0][1080,2232], inputFeatures=0x00000000, ownerPid=27619, ownerUid=10021, dispatchingTimeout=5000.000ms
  MonitoringChannels:
    0: 'WindowManager (server)'
  RecentQueue: length=10
    MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (524.5, 1306.4)]), policyFlags=0x62000000, age=61.2ms
    MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (543.5, 1309.4)]), policyFlags=0x62000000, age=54.7ms
  PendingEvent: <none>
  InboundQueue: <empty>
  ReplacedKeys: <empty>
  Connections:
    0: channelName='WindowManager (server)', windowName='monitor', status=NORMAL, monitor=true, inputPublisherBlocked=false
      OutboundQueue: <empty>
      WaitQueue: <empty>
    5: channelName='72b7776 StatusBar (server)', windowName='Window{72b7776 u0 StatusBar}', status=NORMAL, monitor=false, inputPublisherBlocked=false
      OutboundQueue: <empty>
      WaitQueue: <empty>
    6: channelName='ba2fc6b NavigationBar (server)', windowName='Window{ba2fc6b u0 NavigationBar}', status=NORMAL, monitor=false, inputPublisherBlocked=false
      OutboundQueue: <empty>
      WaitQueue: <empty>
    12: channelName='3c007ad com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher (server)', windowName='Window{3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher}', status=NORMAL, monitor=false, inputPublisherBlocked=false
      OutboundQueue: <empty>
      WaitQueue: length=3
        MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (634.4, 1329.4)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=2, age=17.4ms, wait=16.8ms
        MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (647.4, 1333.4)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=2, age=11.1ms, wait=10.4ms
        MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (659.4, 1337.4)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=2, age=5.2ms, wait=4.6ms
  AppSwitch: not pending
  Configuration:
    KeyRepeatDelay: 50.0ms
    KeyRepeatTimeout: 500.0ms
 

5、Binder

很多卡顿问题和响应速度的问题,是因为跨进程 binder 通信的时候,锁竞争导致 binder 通信事件变长,影响了调用端。最常见的就是应用渲染线程 dequeueBuffer 的时候 SurfaceFlinger 主线程阻塞导致 dequeueBuffer 耗时,从而导致应用渲染出现卡顿; 或者 SystemServer 中的 AMS 或者 WMS 持锁方法等待太多, 导致应用调用的时候等待时间比较长导致主线程卡顿

5.1 Binder 架构图

5.2 Binder调用图解

Binder 主要是用来跨进程进行通信,可以看下面这张图,简单显示了在 Perfetto 中 ,Binder 通信是如何显示的

点击 Binder 可以查看其详细信息,其中有的信息在分析问题的时候可以用到,这里不做过多的描述

5.3 Binder 持锁图解

Monitor 指的是当前锁对象的池,在 Java 中,每个对象都有两个池,锁(monitor)池和等待池:

锁池(同步队列 SynchronizedQueue ):假设线程 A 已经拥有了某个对象(注意:不是类 )的锁,而其它的线程想要调用这个对象的某个 synchronized 方法(或者 synchronized 块),由于这些线程在进入对象的 synchronized 方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程 A 拥有,所以这些线程就进入了该对象的锁池中。

这里用了争夺(contention)这个词,意思是这里由于在和目前对象的锁正被其他对象(Owner)所持有,所以没法得到该对象的锁的拥有权,所以进入该对象的锁池

Owner : 指的是当前拥有这个对象的锁的对象。这里是 Binder:1605_B,4667 是其线程 ID。

6、HandlerThread

6.1 BackgroundThread

com/android/internal/os/BackgroundThread.java

1
2
3
 

private BackgroundThread() {
super("android.bg", android.os.Process.THREAD_PRIORITY_BACKGROUND);
}
 

Systrace 中的 BackgroundThread

BackgroundThread 在系统中使用比较多,许多对性能没有要求的任务,一般都会放到 BackgroundThread 中去执行

7、ServiceThread

ServiceThread 继承自 HandlerThread ,下面介绍的几个工作线程都是继承自 ServiceThread ,分别实现不同的功能,根据线程功能不同,其线程优先级也不同:UIThread、IoThread、DisplayThread、AnimationThread、FgThread、SurfaceAnimationThread

每个 Thread 都有自己的 Looper 、Thread 和 MessageQueue,互相不会影响。Android 系统根据功能,会使用不同的 Thread 来完成。

7.1 UiThread

com/android/server/UiThread.java

1
2
3
 

private UiThread() {
super("android.ui", Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);
}
 

Systrace 中的 UiThread,一般分析手写笔问题的时候会看

UiThread 被使用的地方如下,具体的功能可以自己去源码里面查看,关键字是 UiThread.get()

7.2 IoThread

com/android/server/IoThread.java

1
2
3
 

private IoThread() {
super("android.io", android.os.Process.THREAD_PRIORITY_DEFAULT, true /*allowIo*/);
}
 

IoThread 被使用的地方如下,具体的功能可以自己去源码里面查看,关键字是 IoThread.get()

7.3 DisplayThread

com/android/server/DisplayThread.java

1
2
3
4
5
 

private DisplayThread() {
// DisplayThread runs important stuff, but these are not as important as things running in
// AnimationThread. Thus, set the priority to one lower.
super("android.display", Process.THREAD_PRIORITY_DISPLAY + 1, false /*allowIo*/);
}
 

Systrace 中的 DisplayThread

7.4 AnimationThread

com/android/server/AnimationThread.java

1
2
3
 

private AnimationThread() {
super("android.anim", THREAD_PRIORITY_DISPLAY, false /*allowIo*/);
}
 

Systrace 中的 AnimationThread

AnimationThread 在源码中的使用,可以看到 WindowAnimator 的动画执行也是在 AnimationThread 线程中的,Android P 增加了一个 SurfaceAnimationThread 来分担 AnimationThread 的部分工作,来提高 WindowAnimation 的动画性能

7.5 FgThread

com/android/server/FgThread.java

1
2
3
 

private FgThread() {
super("android.fg", android.os.Process.THREAD_PRIORITY_DEFAULT, true /*allowIo*/);
}
 

Systrace 中的 FgThread

FgThread 在源码中的使用,可以自己搜一下,下面是具体的使用的一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 

FgThread.getHandler().post(() -> {
synchronized (mLock) {
if (mStartedUsers.get(userIdToLockF) != null) {
Slog.w(TAG, "User was restarted, skipping key eviction");
return;
}
}
try {
mInjector.getStorageManager().lockUserKey(userIdToLockF);
} catch (RemoteException re) {
throw re.rethrowAsRuntimeException();
}
if (userIdToLockF == userId) {
for (final KeyEvictedCallback callback : keyEvictedCallbacks) {
callback.keyEvicted(userId);
}
}
});
 

7.6 SurfaceAnimationThread

1
2
3
4
 

com/android/server/wm/SurfaceAnimationThread.java
private SurfaceAnimationThread() {
super("android.anim.lf", THREAD_PRIORITY_DISPLAY, false /*allowIo*/);
}
 

Systrace 中的 SurfaceAnimationThread

SurfaceAnimationThread 的名字叫 android.anim.lf , 与 android.anim 有区别,

这个 Thread 主要是执行窗口动画,用于分担 android.anim 线程的一部分动画工作,减少由于锁导致的窗口动画卡顿问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 

SurfaceAnimationRunner(@Nullable AnimationFrameCallbackProvider callbackProvider,
AnimatorFactory animatorFactory, Transaction frameTransaction,
PowerManagerInternal powerManagerInternal) {
SurfaceAnimationThread.getHandler().runWithScissors(() -> mChoreographer = getSfInstance(),
0 /* timeout */);
mFrameTransaction = frameTransaction;
mAnimationHandler = new AnimationHandler();
mAnimationHandler.setProvider(callbackProvider != null
? callbackProvider
: new SfVsyncFrameCallbackProvider(mChoreographer));
mAnimatorFactory = animatorFactory != null
? animatorFactory
: SfValueAnimator::new;
mPowerManagerInternal = powerManagerInternal;
}

六、参考文档:

  1. VSYNC
  2. CPU 电源状态
  3. Android開発マスターコース
  4. SW-VSYNCの生成と配信
  5. CPU 論理コアをバインドするための強力なツール - タスクセット
  6. Androidのパフォーマンス
  7. 入力システム—InputReader スレッド - Gityuan ブログ | Yuan Huihui のテクノロジー ブログ
  8. 入力システム - スタートアップ - Gityuan ブログ | Yuan Huihui のテクノロジー ブログ
  9. 入力システム—InputDispatcher スレッド - Gityuan ブログ | Yuan Huihui のテクノロジー ブログ
  10. あなたは知識のない荒野に来てしまったようです - Zhihu
  11. Android バインダーのメカニズムを理解する 1/3: ドライバーの章
  12. Android バインダーのメカニズムを理解する 2/3: C++ レイヤー
  13. Android バインダーのメカニズムを理解する 3/3: Java レイヤー
  14. グラフィックス | Android オープンソース プロジェクト | Android オープンソース プロジェクト
  15. [翻訳] RenderThread を理解する - Nuggets
  16. ウェブサイトをアップグレード中…
  17. Flutter レンダリング プロセスの簡単な分析 - Nuggets
  18. Flutter レンダリング メカニズム - UI スレッド - Gityuan ブログ | Yuan Huihui のテクノロジー ブログ
  19. プロセスとスレッドの概要 | アプリの品質 | Android 開発者
  20. Android アプリケーションと SurfaceFlinger の間の接続を確立するプロセス - 短い本
  21. 振付師の原則 - Gityuan Blog | Yuan Huihui の技術ブログ
  22. https://developer.android.com/reference/android/view/Choreographer
  23. https://www.jishuwen.com/d/2Vcc
  24. Android のパフォーマンス評価と最適化 - 流暢性評価 - Nuggets
  25. Android ハードウェア アクセラレーション (2) - RenderThread と OpenGL GPU レンダリング - ナゲット
  26. Android グラフィック システムの概要 - Gityuan ブログ | Yuan Huihui のテクノロジー ブログ
  27. http://echuang54.blogspot.com/2015/01/dispsync.html

おすすめ

転載: blog.csdn.net/weixin_47465999/article/details/131613815