本文正在参与「金石计划 . 瓜分6万现金大奖」
Hi,我是小余。本文已收录到GitHub Androider-Planet中。这儿有 Android 进阶成长常识体系,重视大众号 [小余的自习室] ,在成功的路上不迷路!
为什么要学习屏幕改写常识?
许多同学觉得屏幕改写制作常识点对他们开发不重要,没必要学习这些东西,这部分同学或许平时维护的是一些中小型项目或许应用是安装在特定设备上,只要求写写主界面,做一些简略的网络请求,事务交互相关常识,对功能这块要求不是很高,的确触及不到太多屏幕改写这块常识。
但对一些大中型项目来说或许就不相同了:他们触及事务较多,设备品种较多,往往一个app内部集成了十几个子事务甚至上百个,这对应用功能要求就愈加严格了,app的体会也会直接导致用户的留存问题。
所以学习屏幕制作这类理论性较强的常识也是非常有必要的。
假如你想进阶成为高级开发:屏幕制作这块常识也是一个绕不过去的坎。
1.带着问题出发
前面一篇卡顿优化的文章咱们说过,干流屏幕改写频率是每秒60次(高的有90,120等),也便是16.6ms改写一次屏幕, 假如咱们在主线程做一些耗时的操作,最直观的现象便是屏幕卡顿,其实便是产生了丢帧。
由此抛出几个问题:
- 1.16.6ms是什么意思,每次16.6ms都会调用一个制作流程么?
- 2.为什么在主线程做一些耗时操作会呈现卡顿?丢帧?
- 3.丢帧是个什么意思,是字面上的直接丢掉当时帧仍是拖延显现当时帧?
- 4.双缓冲是什么?有了双缓存就不会呈现丢帧了么?三缓冲呢?
- 5.了解Vsync么?它的效果是什么?
- 6.画面撕裂是怎么造成的?
- 7.编舞者是个什么东西?
带着这些问题咱们出发吧。
2.Android屏幕改写前置常识
CPU/GPU:
-
CPU:
中央处理器
,首要用于核算数据,在Android中首要用于三大制作流程中Surface的核算进程,起着生产者的效果 -
GPU:
图画处理器
,首要用于游戏画面的烘托,在Android中首要用于将CPU核算好的Surface数据组成后放到buffer中,让显现器进行读取,起着消费者的效果。
如下图:
其中GPU在架构中是以SurfaceFlinger
服务的形式作业
SurfaceFlinger:
SurfaceFlinger效果是接受多个来源的图形显现数据Surface,组成后发送到显现设备。
比方咱们的主界面中:或许会有statusBar,侧滑菜单,主界面,这些View都是独立Surface烘托和更新,终究提交给SF后,SF依据Zorder,透明度,大小,方位等参数,组成为一个数据buffer,传递HWComposer或许OpenGL处理,终究给显现器。
逐行扫描
屏幕在改写buffer的时分,并不是一次性扫描完结,而是从左到右,从上到下的一个读取进程,次序显现一屏的每个像素点,你为什么看不到,由于太快了嘛,按60HZ的屏幕改写率来算,这个进程只要16.66666…ms。
帧、帧率(数)、屏幕改写率:
在视频领域中,帧就代表一张图片。玩过短视频剪辑的朋友应该对此很了解。
图中为放大后的一帧图片。
而帧率和屏幕改写率确是两个不同的概念:
- 帧率:表明GPU在1s中内能够烘托多少帧到buffer中,单位是fps,这儿要了解的是帧率是一个动态的,比方咱们平时说的60fps,仅仅1s内最多能够烘托60帧,假如咱们屏幕是静止的,则GPU此刻就没有任何操作,帧率就为0.
- 屏幕改写率:屏幕在1s内去buffer中取数据的次数,单位为HZ,常见屏幕改写率为60HZ。和帧率不相同,屏幕改写率是一个固定值和硬件参数有关。
画面撕裂:
画面撕裂简略说便是显现器把多个帧显现在同一个画面中。如图:
画面撕裂的原因:咱们知道屏幕改写率是固定的,假定为60HZ,正常状况下当咱们的GPU的帧率也是60fps的时分,GPU制作完一帧,屏幕改写一帧,这样是不会出问题的,可是随着GPU显卡功能的提升,GPU的帧率超越60fps后,就会呈现画面撕裂的状况,实践在帧率较低的时分也会呈现撕裂的状况。
所以其本质是帧率和屏幕改写率的不一致导致的撕裂。
那或许大家要说了,等屏幕一帧改写完结后,再将新的一帧存到buffer中不就能够了,那你要知道,前期的4.0之前设备是只要一个buffer,且其并没有buffer同步的概念,屏幕读取buffer中的数据时,GPU是不知道的,屏幕读取的一起,GPU也在写入,导致buffer被掩盖,呈现同一画面运用的是不同帧的数据。
那既然是由于运用同一个Buffer引起的画面撕裂,运用两个buffer不就能够了?
双缓冲
前面咱们提到画面撕裂是由于单buffer引起的,在4.1之前,运用了双缓冲来处理画面撕裂。
- GPU写入的缓存为:
Back Buffer
- 屏幕改写运用的缓存为:
Frame Buffer
如下图:
由于运用双buffer,屏幕改写时,frame buffer不会产生变化,通过交流buffer来完成帧数据切换,那什么时分交流buffer呢?
这就要引进Vsync的概念了。
VSync(垂直同步)
咱们知道假如一个屏幕在改写的进程中,是不能交流buffer的,只要等屏幕改写完结后今后才能够考虑buffer的交流.
那具体什么时分交流呢?当设备屏幕改写完毕后到下一帧改写前,由于没有屏幕改写,所以这段时刻便是缓存交流的最佳时刻
。
此刻硬件屏幕会宣布一个脉冲信号,奉告GPU和CPU能够交流了,这个便是Vsync信号。
有了双缓冲和VSync是不是就都ok了?尽管上面方式能够处理屏幕撕裂的问题,可是仍是会呈现一些其他问题。
Jank
双缓冲buffer交流还有个前提便是GPU现已准备好了back buffer的数据,假如在Vsync到来时back buffer并没有准备好,就不会进行缓存的交流,屏幕显现的仍是前一帧画面,这种状况便是Jank。
有了上面的根底咱们再来聊聊Android屏幕改写机制的演变进程
3.屏幕改写机制的演变进程
Android屏幕改写机制演变进程按buffer的个数能够分为3个阶段:
- 1.
单buffer时代
- 2.
双buffer时代
- 3.
三buffer时代
1.单buffer时代
GPU和显现器共用一块buffer,会引起画面撕裂。
2.双buffer时代
2.1:在引进VSync前(Drawing without VSync)
- CPU:表明CPU制作的时刻段
- GPU:表明GPU组成back buffer的时刻段
- Display:显现器读取frame buffer的时刻段
按时刻次序:
- 1.Display显现第0帧画面,而CPU和GPU正在组成第1帧,且在Display显现下一帧之前完结了。
- 2.由于GPU在Display第一个VSync来之前完结了back buffer的填充,此刻交流back buffer和frame buffer,屏幕进行改写,能够正常显现第一帧数据。
- 3.再来看第2个VSync,第二个VSync到来之时,GPU并没有及时的填充back buffer,这个时分不能交互buffer,屏幕改写的仍是第1帧的画面。就说这儿产生了“jank”
- 4.在第3个VSync信号到来时,第2帧的数据现已写入back buffer,第3帧的数据GPU还没写入,所以这个时分交互buffer显现的是第2帧的数据
- 5.同理,在第4个VSync时,第3帧数据现已处理完毕,交流buffer后显现的是第2帧的数据
这儿产生jank
的原因是:在第2帧CPU处理数据的时分太晚了,GPU没有及时将数据写入到buffer中,导致jank的产生。
假如能够把CPU制作流程提前到每个VSync信号来的时分进行CPU的制作,那是不是就能够让CPU的核算以及GPU的组成写入buffer的操作有完好的16.6ms。
2.1:在引进VSync后(Drawing with VSync)
为了进一步优化功能,谷歌在4.1之后对屏幕制作与改写进程引进了Project Butter(黄油工程
),体系在收到VSync信号之后,立刻进行CPU的制作以及GPU的buffer写入。
这样就能够让cpu和gpu有个完好的16.6ms处理进程。最大极限的削减jank的产生。
引进VSync后,新的问题又呈现了:如下图:
由于主线程做了一些相对杂乱耗时逻辑,导致CPU和GPU的处理时刻超越16.6ms,由于此刻back buffer写入的是B帧数据,在交流buffer前不能被掩盖,而frame buffer被Display用来做改写用,所以在B帧写入back buffer完结到下一个VSync信号到来之前两个buffer都被占用了,CPU无法持续制作,这段时刻就会被空着, 于是又呈现了三缓存。
3.三buffer时代
为了进一步优化用户体会,Google在双buffer的根底上又增加了第三个buffer(Graphic Buffer), 如图:
按时刻次序:
- 1.第一个jank是无法避免的,由于第一个B帧处理超时,A帧肯定是会重复的。
- 2.在第一个VSync信号来时,尽管back buffer以及frame buffer都被占用了,CPU此刻会启用第三个Graphic Buffer,避免了CPU的闲暇状况。
这儿能够最大极限避免2中CPU闲暇的状况,记住仅仅最大极限,没有说一定能避免。
那又有人要说了,那就再多开几个不就能够了,是的,buffer越多jank越少,可是你得考虑性价比: 3 buffer现已能够最大极限的避免jank的产生了,再多的buffer起到的效果就微乎其微,反而由于buffer的数量太多,浪费更多内存,得不偿失。 buffer收益比:
不过假想下哪天由于硬件的改善,3 buffer现已满足不了的时分,谷歌又会加4 buffer,5 buffer..这都是或许的作业。
4.Choreographer
前面咱们分析“双buffer时代“说过:谷歌在4.1之后对屏幕制作与改写进程引进了Project Butter(黄油工程),体系在收到VSync信号之后,立刻进行CPU的制作以及GPU的buffer写入。这样就能够让cpu和gpu有个完好的16.6ms处理进程。最大极限的削减jank的产生。
那么在Android源码层是怎么完成的呢?
那便是这小节要解说的Choreographer,译为编舞者,多么唯美的词,看来写这个源码的开发者也是个很高雅的绅士。
Choreographer在屏幕制作中的效果:
- 1.注册VSync信号回调
- 2.接收SurfaceFlinger服务回调的onSync事情,SurfaceFlinger服务在接收到硬件宣布的定时中止信号VSync后,将信号传递给App,这儿App的接收者便是1中注册的回调。 一般SurfaceFlinger服务接收到的中止信号VSync和App收到的VSync回调是有个offsets的。
下面就来看下Choreographer在源码层的作业原理:
首要声明当时运用的是8.0的源码
1.源码入口scheduleTraversals:
咱们知道一个View在添加到窗口中时,制作流程会调用到ViewRootImpl的setView()办法,setView办法会调用requestLayout()办法请求制作,requestLayout办法中会调用scheduleTraversals()办法,那就从scheduleTraversals开端吧。
void scheduleTraversals() {
//这个字段确保该View现已制作过,不会重复制作
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//添加了一个同步屏障,确保异步制作音讯优先履行
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//post一个制作的Runnable使命,即View的layout/measure/draw流程以及后续的GPU组成流程。
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
//现已制作过,才会走到内部
if (mTraversalScheduled) {
mTraversalScheduled = false;
//移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
//开端履行performTraversals
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
scheduleTraversals办法首要做了下面作业:
- 1.先检查mTraversalScheduled是否现已制作过,没有制作过持续走下面流程,并将mTraversalScheduled标志置为true,避免重复制作
- 2.调用当时Handler的Looper的MessageQueue的postSyncBarrier,设置一个同步屏障。
- 3.运用mChoreographer发送一个Choreographer.CALLBACK_TRAVERSAL的使命。
进入mChoreographer.postCallback办法里边看看:
2.使命提交postCallback
postCallback终究会调用到postCallbackDelayedInternal办法.
private void postCallbackDelayedInternal(int callbackType,..) {
...
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
//将action封装到一个CallBackRecord中并放到mCallbackQueues的callbackType索引处
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
//表明delayMillis=0 当即履行
scheduleFrameLocked(now);
} else {
//发送一个异步推迟使命msg.what = MSG_DO_SCHEDULE_CALLBACK,action = mTraversalRunnable,
//这儿通过Handler后终究也会履行到scheduleFrameLocked
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
postCallbackDelayedInternal做了下面这些作业:
- 1.将action封装到一个CallBackRecord中并放到mCallbackQueues的callbackType索引处
- 2.假如是当即履行的音讯,则直接调用scheduleFrameLocked
- 3.假如是推迟音讯,则发送一个MSG_DO_SCHEDULE_CALLBACK的msg
咱们来看下MSG_DO_SCHEDULE_CALLBACK的Handler逻辑:
这儿mHandler = FrameHandler类目标
private final class FrameHandler extends Handler {
public FrameHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
doScheduleCallback(msg.arg1);
break;
}
}
MSG_DO_SCHEDULE_CALLBACK的音讯类型会走到doScheduleCallback,msg.arg1 = callbackType,
进入doScheduleCallback办法:
void doScheduleCallback(int callbackType) {
synchronized (mLock) {
if (!mFrameScheduled) {
final long now = SystemClock.uptimeMillis();
if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
scheduleFrameLocked(now);
}
}
}
}
判别mCallbackQueues[callbackType]是否有需求使命。有使命则履行scheduleFrameLocked ** 能够看到postCallbackDelayedInternal终究都是履行scheduleFrameLocked办法**
直接看scheduleFrameLocked办法
3.回调注册scheduleVsync
private void scheduleFrameLocked(long now) {
//检测mFrameScheduled是否现已为true
if (!mFrameScheduled) {
mFrameScheduled = true;
//是否敞开了VSYNC,4.1之后默许敞开,直接看这儿即可
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on vsync.");
}
// 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.
//假如是在主线程上,则直接调用scheduleVsyncLocked
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
//其他线程则需求运用Handler将使命履行在主线程中。
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);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
- 1.检测mFrameScheduled是否现已为true
- 2.假如敞开了VSYNC则调用scheduleVsyncLocked办法,没有敞开则发送一个MSG_DO_FRAME的msg给mHandler。Android 4.1之后默许敞开了VSYNC,所以直接看USE_VSYNC流程即可
- 3.假如是运行在当时线程的上,当时线程是UI线程。则直接调用scheduleVsyncLocked办法。
- 4.假如没有运行在主线程上, 则发送一个MSG_DO_SCHEDULE_VSYNC的msg给mHandler。依据FrameHandler的源码能够看出终究也是走到scheduleVsyncLocked办法
看scheduleVsyncLocked办法:
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
这个mDisplayEventReceiver是什么时分赋值的呢。
咱们来看Choreographer的结构办法:
private Choreographer(Looper looper, int vsyncSource) {
//设置looper
mLooper = looper;
//创立mHandler实例
mHandler = new FrameHandler(looper);
//4.1默许敞开,所以直接运用的是FrameDisplayEventReceiver类目标
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
//初始化一个CallbackQueue数组目标
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
}
回到scheduleVsyncLocked办法:调用了mDisplayEventReceiver.scheduleVsync(),mDisplayEventReceiver是FrameDisplayEventReceiver类目标
进入FrameDisplayEventReceiver类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 {
nativeScheduleVsync(mReceiverPtr);
}
}
public DisplayEventReceiver(Looper looper, int vsyncSource) {
if (looper == null) {
throw new IllegalArgumentException("looper must not be null");
}
mMessageQueue = looper.getQueue();
//这个办法会将当时目标this = mDisplayEventReceiver以及mMessageQueue,vsyncSource传递给native层目标,并回来native层目标的地址值mReceiverPtr
mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
vsyncSource);
mCloseGuard.open("dispose");
}
scheduleVsync中调用的是nativeScheduleVsync办法进行注册,注册的是一个mReceiverPtr,这是一个native层的目标地址,这个地址是在DisplayEventReceiver结构办法中初始化,调用nativeInit办法回来的,nativeInit办法传入一个this,这个this便是前面的mDisplayEventReceiver目标,所以重新回到前面的mDisplayEventReceiver解说,
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
...
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);
}
}
FrameDisplayEventReceiver类完成了onVsync办法,这个办法便是native层在接收到VSync信号后回调的办法。onVsync办法直接发送一个异步音讯,履行的使命是自己的run办法。
run中履行doFrame(),进入doFrame看看:
4.图帧制作doFrame
void doFrame(long frameTimeNanos, int frame) {
...
try {
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();
//处理CALLBACK_TRAVERSAL,三大制作流程
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
//处理CALLBACK_COMMIT事情
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
...
}
doFrame中:
- 1.履行输入事情
- 2.处理动画事情
- 3.处理CALLBACK_TRAVERSAL,三大制作流程,其实便是前面的mTraversalRunnable事情
- 4.处理CALLBACK_COMMIT提交帧事情
进入doCallbacks办法:
void doCallbacks(int callbackType, long frameTimeNanos) {
for (CallbackRecord c = callbacks; c != null; c = c.next) {
if (DEBUG_FRAMES) {
Log.d(TAG, "RunCallback: type=" + callbackType
+ ", action=" + c.action + ", token=" + c.token
+ ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
}
c.run(frameTimeNanos);
}
}
循环履行callbacks中的记载:
private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
}
CallbackRecord的run办法在token = null的状况下履行的是action的run办法 这儿再看
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
传入的token的确为null,且action = mTraversalRunnable,
这样整个处理流程就闭环了。
这儿token = FRAME_CALLBACK_TOKEN是在什么状况下呢?
5.帧率核算postFrameCallback
在Choreographer的postFrameCallback办法中:
public void postFrameCallback(FrameCallback callback) {
postFrameCallbackDelayed(callback, 0);
}
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);
}
终究也是履行到postCallbackDelayedInternal办法,不同之处在于,其传入的token是FRAME_CALLBACK_TOKEN
那么这个办法有什么效果呢?核算丢帧。
咱们运用下面的办法对丢丢帧30次以上在logcat中打印一个日志。
- 1.创立一个FrameCallback子类
public class YourFrameCallback implements Choreographer.FrameCallback {
private static final String TAG = "FPS_TEST";
private long mLastFrameTimeNanos = 0;
private long mFrameIntervalNanos;
public YourFrameCallback(long lastFrameTimeNanos) {
mLastFrameTimeNanos = lastFrameTimeNanos;
mFrameIntervalNanos = (long)(1000000000 / 60.0);
}
@Override
public void doFrame(long frameTimeNanos) {
//初始化时刻
if (mLastFrameTimeNanos == 0) {
mLastFrameTimeNanos = frameTimeNanos;
}
final long jitterNanos = frameTimeNanos - mLastFrameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if(skippedFrames>30){
//丢帧30以上logcat打印日志
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
}
mLastFrameTimeNanos=frameTimeNanos;
//由于每次doFrame都会消费掉,需求重新注册下一帧回调
Choreographer.getInstance().postFrameCallback(this);
}
}
- 2.在Application发动的时分:调用Choreographer的postFrameCallback办法,并传入一个FrameCallback
Choreographer.getInstance().postFrameCallback(new YourFrameCallback(System.nanoTime()));
对这小节总结:
-
1.在Choreographer的结构函数中会创立一个FrameDisplayEventReceiver类目标,这个目标完成了onVSync办法,用于VSync信号回调。
-
2.FrameDisplayEventReceiver这个目标的父类结构办法中会调用nativeInit办法将当时FrameDisplayEventReceiver目标传递给native层,native层回来一个地址mReceiverPtr给上层。
-
3.主线程在scheduleVsync办法中调用nativeScheduleVsync,并传入2中回来的mReceiverPtr,这样就在native层就正式注册了一个FrameDisplayEventReceiver目标。
-
4.native层在GPU的唆使下会定时回调FrameDisplayEventReceiver的onVSync办法,然后完成了:在VSync信号到来时,当即履行doFrame办法
-
5.doFrame办法中会履行输入事情,动画事情,layout/measure/draw流程并提交数据给GPU。这样就闭环了
制作流程图如下:来自《Android 之 Choreographer 详细分析》
5.Handler同步屏障机制
中心思维:在主线程Looper获取msg的时分,让异步音讯优先履行,同步音讯滞后。
这儿先上一张原理图:
咱们依次对图中几个点进行源码解说:
- 1.同步屏障的创立
- 2.异步音讯的优先履行
1.同步屏障的创立
运用的是:MessageQueue#postSyncBarrier()
/**
* 同步屏障便是一个同步音讯,只不过这个音讯的target为null
*/
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
// 从音讯池中获取Message
final Message msg = Message.obtain();
msg.markInUse();
// 初始化Message目标的时分,并没有给Message.target赋值,
// 因此Message.target==null
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
// 这儿的when是要参加的Message的时刻
// 这儿遍历是找到Message要参加的方位
while (p != null && p.when <= when) {
// 假如敞开同步屏障的时刻(假定记为T)T不为0,且当时的同步
// 音讯里有时刻小于T,则prev也不为null
prev = p;
p = p.next;
}
}
// 依据prev是否为null,将msg按照时刻次序刺进到音讯行列的合适方位
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
注释中有说明了,这儿的屏障音讯便是一个target为null的音讯,由于其不需求履行使命音讯,仅仅用来设置一堵墙.
再依据时刻戳将其刺进到合适的方位。
2.异步音讯的优先履行
移步到:MessageQueue的next办法:
Message next() {
...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// 1.假如nextPollTimeoutMillis=-1,一向堵塞不会超时
// 2.假如nextPollTimeoutMillis=0,不会堵塞,当即回来
// 3.假如nextPollTimeoutMillis>0,最长堵塞nextPollTimeoutMillis毫秒(超时)
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 获取体系开机到现在的时刻戳
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 取出target==null的音讯
// 假如target==null,那么它便是屏障,需求循环遍历,
// 一向往后找到第一个异步音讯,即msg.isAsynchronous()为true
// 这个target==null的音讯,不会被取出处理,一向会存在
// 每次处理异步音讯的时分,都会从头开端轮询
// 都需求经历从msg.target开端的遍历
if (msg != null && msg.target == null) {
// 运用一个do..while循环
// 轮询音讯行列里的音讯,这儿运用do..while循环的原因
// 是由于do..while循环中取出的这第一个音讯是target==null的音讯
// 这个音讯是同步屏障的标志音讯
// 接下去进行遍历循环取出Message.isAsynchronous()为true的音讯
// isAsynchronous()为true便是异步音讯
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
// 假如有音讯需求处理,先判别时刻有没有到,假如没有到的话设置堵塞时刻
if (now < msg.when) {
// 核算出离履行时刻还有多久赋值给nextPollTimeoutMillis
// 表明nativePollOnce办法要等待nextPollTimeoutMillis时长后回来
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 获取到音讯
mBlocked = false;
// 链表操作,获取msg而且删去该节点
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
// 回来拿到的音讯
return msg;
}
} else {
// 没有音讯,nextPollTimeoutMillis复位
nextPollTimeoutMillis = -1;
}
...
}
}
}
优先判别是否有同步屏障存在,然后取同步屏障后边的异步音讯进行处理。就达到了优先履行异步音讯的意图。
好了,关于同步屏障机制就讲到这儿。
有了上面这些根底解说。下面临一开端的问题进行一个总结概括。
6.问题总结
1.16.6ms是什么意思,每次16.6ms都会调用一个制作流程么?
16.6ms是指改写频率为是60HZ,1s需求履行60次,均匀每次16.6ms。也能够了解为VSync的一个周期是16.6ms。 并非每次16.6ms都会履行三大制作流程,屏幕静止状况,CPU并不会履行制作流程
2.画面撕裂是怎么造成的?
画面撕裂是前期运用的是一个buffer进行屏幕的改写读取和GPU的写入操作,且不存在同步锁的状况下,新数据掩盖旧的数据导致一张画面显现多帧的场景
3.为什么在主线程耗时,布局层级太多,会呈现卡顿?丢帧?
主线程耗时,布局层级太多会影响CPU的核算和GPU的组成进程,超越VSync信号后,需求等下一个VSync信号来才能进行buffer交流,产生丢帧现象
4.丢帧是个什么意思,是字面上的直接丢掉当时帧仍是拖延显现当时帧?
丢帧是指在第一个VSync信号来之前并没有组成好back buffer数据,无法交流buffer,屏幕改写的仍是上一帧数据,这便是丢帧。 下一个VSync信号来之后,back buffer制作好后,再交流buffer,所以其不会丢掉,而是拖延显现,直观感受便是卡顿。
5.双缓冲是什么?有了双缓存就万事大吉了么?三缓冲呢?
双缓冲是指运用两个buffer进行数据的缓存,一个用于GPU的组成,一个用于屏幕的改写,互不搅扰,避免呈现画面撕裂的场景 有了双缓存仍是会呈现丢帧的现象和CPU的闲暇等问题。 三缓冲便是在双缓冲上再增加一个Graphic Buffer,避免CPU长时刻闲暇。
6.了解Vsync么?它的效果是什么?
VSync(垂直同步)有两个效果: 1.提示GPU进行buffer的交流 2.提示CPU当即进入屏幕制作进程,别闲着啦。
7.编舞者是个什么东西?
编舞者Choreographer是4.1今后引进的: 效果:用来提示CPU在VSync信号来时当即对View进行制作,避免呈现CPU闲暇状况。
好了,就讲到这儿吧,上面文章触及了:UI制作流程,Handler同步异步音讯机制,屏幕改写流程,卡顿优化等常识点
假如你能把本文触及的常识点都吸收,对后期framework层的学习是很有帮助的。
假如你喜欢我的文章,请帮忙点赞,重视,这是对我的巨大鼓励!
欢迎重视我的大众号:小余的自习室
参考
数据的活动——核算机是怎么显现一个像素的
聊聊Android屏幕改写机制
Android垂直同步信号VSync的产生及传播结构详解