網站首頁 編程語言 正文
一、首先介紹一些基礎知識
1.刷新率(Refresh Rate):
刷新率代表屏幕在一秒內刷新屏幕的次數,用赫茲來表示。赫茲是頻率的單位,一秒震動的次數。這個刷新率取決于硬件固定的參數。這個值一般是60Hz。即每16.66ms刷新一次屏幕。
2.幀速率(Frame Rate):
幀速率代表了GPU在一秒內繪制操作的幀數。比如30FPS、60FPS。Frame Per Second。
3.如果兩個設備獨立運行,如果刷新率和幀速率不同步,會引發以下兩種問題。
如果幀速率高于刷新率,制作的頻率大于展示的頻率,會導致屏幕圖像的展示的跳躍,跳幀的現象。
如果幀速率小于刷新率,制作的頻率小于展示的頻率,會導致屏幕圖像的展示的中斷,掉幀的現象。
4.android為了解決上面的問題,在4.1版本中引入了Projectbuffer.
ProjectBuffer對Android Display系統進行了重構,引入了三個核心元素,即Vsync,TripleBuffer和Choreographer。
其中Vsync是Vertical Synchronization 垂直同步是縮寫。是一種在PC上已經很早就廣泛使用的技術。
引入是Vsync來進行控制CPUGPU的繪制和屏幕刷新同步進行。
而編舞者choreography的引入,主要是配合Vsync,給上層App的渲染提供一個穩定的時機。Vsync到來的時候,Choreographer可以根據Vsync信號,統一管理應用的輸入、動畫、繪制等任務的執行情況。Choreographer就像一個指揮家一樣,來把控著UI的繪制,所以取名編舞者。
二、android源碼中Choreographer是如何運行
1.首先在ViewRootImpl構造函數中創建了Choreographer對象
public ViewRootImpl(Context context, Display display) {
mChoreographer = Choreographer.getInstance();
}
public static Choreographer getInstance() {
return sThreadInstance.get();
}
當調用get時,如果為null,會調用initialValue()方法。并把Choreographer實例和ThreadLocal綁定。
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
//因為后面會用到handler通訊,所以必須有一個Looper循環
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);
//如果是主線程,則把choreographer賦值給mMainInstance
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
2.看Choreographer構造函數
mLastFrameTimeNanos:記錄上一幀繪制的時間。
mFrameIntervalNanos:屏幕繪制一幀的時間間隔,這個是納秒值。如果屏幕刷新率是60Hz,那么刷新一幀的時間間隔就是16.66.......毫秒。
private Choreographer(Looper looper, int vsyncSource) {
// 傳一個Looper進來,
mLooper = looper;
//用來處理消息的。
mHandler = new FrameHandler(looper);
//USE_VSYNC 是否使用Vsync
//boolean USE_VSYNC = SystemProperties.getBoolean("debug.choreographer.vsync", true);
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
//上一幀繪制的時間
mLastFrameTimeNanos = Long.MIN_VALUE;
//1秒是1000毫秒,1毫秒是1000微秒,1微秒是1000納秒
//1秒就是1*1000*1000*1000=10的九次方納秒
//繪制一幀的時間間隔----納秒。如果是60Hz,那么刷新一幀展示的時間就是16.66毫秒。
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
//初始化回調隊列,后面會從這個回調隊列中取出Runnable執行run方法。
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
}
獲取屏幕的刷新率:
//屏幕的刷新率,一秒鐘可以刷新屏幕多少次,通常是60Hz
private static float getRefreshRate() {
DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(
Display.DEFAULT_DISPLAY);
return di.getMode().getRefreshRate();
}
3.初始化工作完成,那么Choreographer是怎么跑起來的,入口函數在哪?
對于UI繪制來說是入口在RootViewImpl的scheduleTraversals()方法中。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//發送一個屏障消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//注意第一個參數CALLBACK_TRAVERSAL,回調函數的類型。
//mTraversalRunnable 回調函數要執行的runnable。
//第三個參數token,傳了一個null
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
//第一個參數callbackType 有五種類型,這幾個回調是有順序的。
1.CALLBACK_INPUT 輸入回調,首先運行
2.CALLBACK_ANIMATION 動畫回調,這個在將動畫原理的時候,會看到
3.CALLBACK_INSETS_ANIMATION inset和update相關的動畫,運行在上面兩個回調之后,
4.CALLBACK_TRAVERSAL 遍歷回調,用于處理布局和繪制
5.CALLBACK_COMMIT Commit回調,在Traversal繪制回調之后。
接下來看postCallbackDelayedInternal方法
第二個參數就是上面的mTraversalRunnable。
第四個參數延遲的時間,這里延遲時間是0,沒有延遲
所以這個方法走if判斷的第一個分支
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
//將runnable加入回調隊列
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
上面傳過來的delayMillis是0,所以走第一個分支。
if (dueTime <= now) {
scheduleFrameLocked(now);
} else { //如果有延遲,則發送一個延遲的異步消息。這種消息在handler同步屏障文章中介紹過
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
//如果使用垂直同步
if (USE_VSYNC) {
//判斷是否運行在主線程,如果是則直接調用scheduleVsyncLocked()
//如果運行在子線程則通過發送handler 的方式也會調用到scheduleVsyncLocked()
if (isRunningOnLooperThreadLocked()) {//Looper.myLooper() == mLooper
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
}else{
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
scheduleVsyncLocked()方法。
private void scheduleVsyncLocked() {
//調用父類 DisplayEventReceiver的方法
mDisplayEventReceiver.scheduleVsync();
}
在scheduleVsync()方法中會調用nativeScheduleVsync,這是一個native方法,在native層執行完畢后會回調到java層的方法dispatchVsync()
scheduleVsync:向native層去請求一個Vsync信號。
dispatchVsync:請求到Vsync信號后,執行Java層的UI繪制和渲染邏輯。
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 { // 調用native 方法
//調用Native方法請求一個Vsync信號,然后會從native層回調java層的dispatchVsync方法
nativeScheduleVsync(mReceiverPtr);
}
}
timestampNanos:從Native層傳遞過來的一個時間戳,Vsync從native層發出的時間。
// Called from native code.
//從native層回調java層的dispatchVsync方法
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
onVsync(timestampNanos, physicalDisplayId, frame);
}
在這又發送了一個異步消息,并且 Message.obtain(mHandler, this);第二個參數是一個callBack回調。所以沒有handler的情況下,會執行這個回調函數。但是傳的是this,所以就會執行this的run方法。這個this就是FrameDisplayEventReceiver的實例,在Choreographer的構造函數中初始化的。
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
mTimestampNanos = timestampNanos;
mFrame = frame;
//得到message 添加了一個回調函數,this,則會調用run方法
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
在FrameDisplayEventReceiver的run方法中,調用的doFrame方法
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
return; // no work to do
}
//sync信號發出的時間,
long intendedFrameTimeNanos = frameTimeNanos;
//當前的時間
startNanos = System.nanoTime();
//兩者相減得到的時間差,就是底層消息通訊和回調所消耗的時間
final long jitterNanos = startNanos - frameTimeNanos;
//如果這個時間差大于了一幀的時間間隔。
if (jitterNanos >= mFrameIntervalNanos) {
//計算跳過了多少幀
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
//注意下面這行日子,如果跳幀大于30幀,系統會打印下面這行log,在主線程做了太多工作,會造成UI卡頓。
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
//取模,得到的值就是一幀多出來的時間
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
}
//用當前時間減去多出來的時間,就是下一幀要繪制的時間
//進行繪制時間的修正,保證每一次的繪制時間間隔都是mFrameIntervalNanos
frameTimeNanos = startNanos - lastFrameOffset;
}
//如果底層傳過來的時間,小于上一幀繪制的時間,正常情況下,frameTimeNanos都是大于上一幀繪制的時間的。
if (frameTimeNanos < mLastFrameTimeNanos) {
//跳過本次的繪制,請求下一幀的時間
scheduleVsyncLocked();
return;
}
//以上的判斷,都是為了控制繪制的頻率。
if (mFPSDivisor > 1) {
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
scheduleVsyncLocked();
return;
}
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
//重置標志位,可以再次進入scheduleFrameLocked
mFrameScheduled = false;
//將底層傳過來的時間,記錄為本次繪制的時間,也就是下一幀傳過來時,上一幀繪制的時間。
mLastFrameTimeNanos = frameTimeNanos;
}
try {
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
//根據Choreographer的CallBack類型,進行callBack的回調。
//輸入
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
//動畫
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
//界面繪制
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
//commit
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
這個是很重要的一個方法。
通過這個方法中的邏輯能夠看出:Choreographer控制App層UI繪制的節奏和頻率。
然后會按順序執行一些列的doCallBacks函數。
首先會根據callbackType,從鏈表中取出CallBackRecord。然后再遍歷CallBackRecord,調用他的run方法。
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
//根據callbacktype,從鏈表中拿到 CallbackRecord
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
for (CallbackRecord c = callbacks; c != null; c = c.next) {
//執行CallbackRecord的run方法
c.run(frameTimeNanos);
}
}
}
根據token來進行區分是FrameCallback類型還是Runnable。
主要這里的token傳進來的是null,所以會執行else分支。
這個action就是mTraversalRunnable,調用mTraversalRunnable的run方法。
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
在它的run方法中執行了doTraversal()。
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//刪除屏障消息
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
//調用測量、布局和繪制方法
performTraversals();
}
}
performTraversals()方法中就會調用
performMeasure、performLayout、performDraw,對View進行測量、布局、和繪制。
原文鏈接:https://blog.csdn.net/niuyongzhi/article/details/126203029
相關推薦
- 2023-06-17 Queue隊列中join()與task_done()的關系及說明_python
- 2022-09-08 Docker容器使用方法詳解_docker
- 2022-07-08 正則表達式中問號(?)的正確用法詳解_正則表達式
- 2022-11-19 詳解C語言內核中的鏈表與結構體_C 語言
- 2022-10-28 go語言?nil使用避坑指南_Golang
- 2022-06-01 docker安裝nginx并配置ssl的方法步驟_docker
- 2022-09-18 centos+nginx+uwsgi部署django項目上線_python
- 2022-06-02 C++實現投骰子的隨機游戲_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支