この記事では、主に次の問題を解決します。
- Androidのリフレッシュレートは60フレーム/秒であることは誰もが知っていますが、これはonDrawメソッドが16msごとに呼び出されるということですか?
- インターフェイスを再描画する必要がない場合
16ms
、画面が到着したときに画面が更新されますか? - 電話をした
invalidate()
直後に画面を更新しますか? - フレームロスはメインスレッドの時間のかかる操作が原因と言われていますが、なぜメインスレッドがフレームロスを引き起こすのでしょうか?
- 画面が更新されそうなときに
OnDraw()
描画すると、フレームはドロップされますか?
さて、上記の質問で、答えを見つけるためにソースコードを入力します。
1.画面描画プロセス
画面描画メカニズムの基本原則は、次のように要約できます。
画面全体を描画する基本的なプロセスは次のとおりです。
- アプリケーションがシステムサービスにバッファを要求する
- システムサービスはバッファを返します
- アプリケーションが描画された後、バッファはシステムサービスに送信されます。
あなたがそれをAndroidに入れたら、それはそれです:
Androidでは、Surfaceがメモリの一部に相当し、メモリアプリケーションが成功すると、アプリ側に描画する場所があります。Androidのビュー描画は今日の焦点ではないので、これで終わりです〜
2.画面更新分析
画面の更新のタイミングは、図に示すように、Vsync信号が到着したときです。
Android側では、誰Vsync
が世代を管理していますか?アプリケーションを更新するように誰が私たちに通知しますか?Androidでは、Vysnc
信号の生成は基盤となるHWComposerが担当し、通知アプリケーションはJavaレイヤーでChoreographer
更新されます。Androidの画面全体の更新の中心はこれChoreographer
です。以下のコードを見てみましょう。UIを再描画するたびにrequestLayout()が呼び出されるため、このメソッドから始めます。
2.1 requestLayout()
----》类名:ViewRootImpl
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
//重点
scheduleTraversals();
}
}
2.2 scheduleTraversals()
----》类名:ViewRootImpl
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
......
}
}
ご覧のとおり、ここでは即時の再描画はありませんが、次の2つの処理が行われています。
- SyncBarrier(同期バリア)をメッセージキューに挿入する
- により、
Cherographer post
Acallback
次に、このSyncBarrier(同期バリア)について簡単に説明します。非同期バリアの役割は次のとおりです。
- 同期メッセージの実行を防止する
- 非同期メッセージに優先順位を付ける
なぜこれを設計するのSyncBarrier
ですか?主な理由は、Androidでは緊急性が高く、すぐに実行する必要があるメッセージがあるためです。メッセージキューに一般的なメッセージが多すぎると、実行までに時間がかかる場合があります。
この時点で、私のような人もいます。メッセージに優先順位を付けて、優先順位に従って並べ替えないのはなぜですか。ホールドを取得PriorityQueue
まだ終わっていませんか?
私の理解では、Androidではメッセージキューのデザインは単一のリンクリストであり、リンクリスト全体の順序は時間順にソートされています。このときに優先ソートルールを追加すると、複雑になります。一方、メッセージを制御できなくなります。優先度はユーザー自身が入力できるため、ごちゃごちゃになりませんか?ユーザーが常に常に最高の優先度を満たすと、システムメッセージが長時間消費され、システム全体で問題が発生し、最終的にユーザーエクスペリエンスに影響します。したがって、Androidの同期バリアの設計は依然として非常に賢明だと思います〜
要約するとscheduleTraversals()
、実行後、非同期メッセージの優先実行を保証するためにバリアが挿入されます。
小さな思考の質問を挿入します。メソッド内で続けてrequestLayout()を呼び出す場合は、次のように質問します。システムは複数のバリアを挿入するか、複数のコールバックをポストしますか?答えはノーです、なぜですか?変数mTraversalScheduledが表示されますか?答えです〜
2.3 Choreographer.postCallback()
まずに関する簡単な話取るChoreographer
、Choreographer
振付師と呼ばれる中国語の翻訳を、その主な役割は、体系的かつ協調を行うことです。(実際の作業で振付家をググるにはオンラインにできます。このクラスの名前は本当に適切です〜)Choreographerのアプリケーションはどのように初期化されますか?getInstance()
メソッドを介して:
public static Choreographer getInstance() {
return sThreadInstance.get();
}
// Thread local storage for the choreographer.
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
if (looper == null) {
throw new IllegalStateException("The current thread must have a looper!");
}
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
これはChoreographer
、シングルトンの代わりに各スレッドが個別のコピーを持っていることを皆に思い出させるためにここに掲載されています。
さて、コードに戻ります:
----》类名:Choreographer
//1
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
//2
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
....
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
//3
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
...
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
...
}
}
Choreographerpost
callback
置くCallbackQueue
ことを、これはCallbackQueue
単独でリンクされたリストです。
最初にcallbackType
、CallbackQueue
単一リンクリストが取得され、次にコールバックが時系列に従って単一リンクリストに挿入されます。
2.4 scheduleFrameLocked()
----》类名:Choreographer
private void scheduleFrameLocked(long now) {
...
// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
...
}
}
}
scheduleFrameLocked
役割は次のとおりです。
現在のスレッドがCherographer
ワーカースレッドの場合、直接実行します。scheduleVysnLocked
それ以外の場合は、非同期メッセージがメッセージキューに送信されます。非同期メッセージは同期バリアの影響を受けず、メッセージもメッセージキューの先頭に挿入されます。これは、メッセージが非常に緊急であることを示しています。
ソースコードを追跡したところ、MSG_DO_SCHEDULE_VSYNC
このメッセージが実際にscheduleFrameLocked
このメソッドを実際に実行したことが判明したため、scheduleVsyncLocked()
このメソッドを直接追跡しました。
2.5 scheduleVsyncLocked()
----》类名:Choreographer
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
----》类名:DisplayEventReceiver
public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
+ "receiver has already been disposed.");
} else {
//mReceiverPtr是Native层一个类的指针地址
//这里这个类指的是底层NativeDisplayEventReceiver这个类
//nativeScheduleVsync底层会调用到requestNextVsync()去请求下一个Vsync,
//具体不跟踪了,native层代码更长,还涉及到各种描述符监听以及跨进程数据传输
nativeScheduleVsync(mReceiverPtr);
}
}
ここで、新しいクラスを確認できます。DisplayEventReceiver
このクラスの役割は、Vsync信号の監視を登録することDisplayEventReceiver
です。これは、次のVsync信号が到着したときに通知されます。
どこに通知しますか?ソースコードのコメントは非常に明確です。
----》类名:DisplayEventReceiver
// Called from native code. <---注释还是很良心的
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
onVsync(timestampNanos, builtInDisplayId, frame);
}
次のVysnc信号が到着すると、onVsync
メソッドは最終的に呼び出されます。
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) { }
内部をクリックして確認してください。これは空の実装であり、クラス定義に戻ります。抽象クラスであることが判明しました。その実装クラスは:、でFrameDisplayEventReceiver
定義されていCherographer
ます:
----》类名:Choreographer
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
....
}
2.6 FrameDisplayEventReceiver.onVysnc()
----》类名:Choreographer
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, 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() {
....
doFrame(mTimestampNanos, mFrame);
}
}
onVsync
このメソッドは、Cherographerが配置されているスレッドのメッセージキューにメッセージを送信します。このメッセージはそれ自体(Runnableを実装しています)なので、doFrame()
メソッドは最終的に呼び出されます。
2.7 doFrame(mTimestampNanos、mFrame)
doFrame()
処理は2つの段階に分かれています。
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
//1、阶段一
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.");
}
...
}
...
}
frameTimeNanos
これは現在のタイムスタンプです。開始時間から現在の時間をmFrameIntervalNano
引いて、このフレームの処理にかかる時間を取得します。それよりも大きい場合は、処理に時間がかかったことを意味し、一般的なThe application may be doing too much work on its main thread。
フェーズ2:
void doFrame(long frameTimeNanos, int frame) {
...
try {
//阶段2
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
}
...
}
doFrame()
プロセスの2番目の段階は、プロセスcallback
のCallbackQueue
内部から実行時間まで、すべての種類をcallback
処理するcallback
ことですが、これについてはどうですか?
ここで前のpostCallback()
操作を思い出してください:
このCallbackは実際には1つだけmTraversalRunnable
です。これはRunnableであり、最終的にrun()
メソッドを呼び出してインターフェースの実際の更新を実現します。
----》类名:ViewRootImpl
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
...
performTraversals();
...
}
}
private void performTraversals() {
...
//开始真正的界面绘制
performDraw();
...
}
3.まとめ
長いコード追跡の後、インターフェースの更新プロセス全体が完了したと見なされます。以下に要約します。
4.質問の回答
Q:
Androidのリフレッシュレートは60フレーム/秒であることは誰もが知っていますonDraw
が、これはメソッドが16msごとに呼び出されるということですか?
A:
ここでは60フレーム/秒が画面の更新頻度ですが、onDraw()
メソッドが呼び出されるかどうかは、アプリケーションがrequestLayout()
登録監視のために呼び出されるかどうかによって異なります。
Q:
インターフェイスを再描画する必要がない場合、画面は16ミリ秒後に更新されますか?
A:
再描画する必要がない場合、アプリケーションはVsync信号を受信しませんが、更新されますが、描画されるデータは変更されません。
Q:
invalidate()を呼び出した直後に画面を更新しますか?
A:
いいえ、次のVsync信号が到着するまで
Q:
フレームの損失は、メインスレッドが時間のかかる操作を行ったためであると言います。メインスレッドが時間のかかる操作を行った理由は、フレームの損失です。
A:
その理由は、メインスレッドで時間のかかる操作が実行されると、次のフレームの描画に影響を与え、その結果、インターフェイスがVsync
現時点で更新できなくなり、フレームが失われるためです。
Q:
画面が更新されそうなときにOnDraw()
描画すると、フレームはドロップされますか?
Vsync信号は周期的であり、onDraw()を開始してもインターフェイスの更新に影響を与えないため、これはそれほど重要ではありません。