点击上方蓝字关注我,每天一见,给你力量
前言
早呀各位。今天继续屏幕刷新机制的知识讲解,上文说到vsync的处理,每一帧UI的绘制前期处理都在Choreographer中实现,那么今天就来看看这个神奇的舞蹈编舞师是怎么将UI变化反应到屏幕上的。
(今天全是代码~)
代码未动,图先行
UI变化
上期说到app并不是每一个vsync信号都能接收到的,只有当应用有绘制需求的时候,才会通过scheduledVsync 方法申请VSYNC信号。
那我们就从有绘制需求开始看,当我们修改了UI后,都会执行invalidate方法进行绘制,这里我们举例setText方法,再回顾下修改UI时候的流程:
可以看到,最后会调用到父布局ViewRootImpl的scheduleTraversals方法。
public ViewRootImpl(Context context, Display display) {
//...
mChoreographer = Choreographer.getInstance();
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
//...
}
}
为了方便查看,我只留了相关代码。可以看到,在ViewRootImpl构造方法中,实例化了Choreographer对象,并且在发现UI变化的时候调用的scheduleTraversals方法中,调用了postSyncBarrier方法插入了同步屏障,然后调用了postCallback方法,并且传入了一个mTraversalRunnable(后面有用处,先留意一下),暂时还不知道这个方法是干嘛的。继续看看。
Choreographer实例化
//Choreographer.java
public static Choreographer getInstance() {
return sThreadInstance.get();
}
private static final ThreadLocal sThreadInstance =
new ThreadLocal() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
//...
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
//...
return choreographer;
}
};
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
mHandler = new FrameHandler(looper);
//初始化FrameDisplayEventReceiver
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
//一帧间隔时间
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
}
ThreadLocal,是不是有点熟悉?之前说Handler的时候说过,Handler是怎么获取当前线程的Looper的?就是通过这个ThreadLocal,同样,这里也是用到ThreadLocal来保证每个线程对应一个Choreographer。
存储方法还是一样,以ThreadLocal为key,Choreographer为value存储到ThreadLocalMap中,不熟悉的朋友可以再翻到《Handler另类难点三问》看看。
所以这里创建的mHandler就是ViewRootImpl所处的线程的handler。接着看postCallback做了什么。
postCallback
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
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;
}
}
}
void doScheduleCallback(int callbackType) {
synchronized (mLock) {
if (!mFrameScheduled) {
final long now = SystemClock.uptimeMillis();
if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
scheduleFrameLocked(now);
}
}
}
}
在ViewRootImpl中调用了postCallback方法之后,可以看到通过addCallbackLocked方法,添加了一条CallbackRecord数据,其中action就是对应之前ViewRootImpl的mTraversalRunnable。
然后判断设定的时间是否在当前时间之后,也就是有没有延迟,如果有延迟就发送延迟消息消息MSG_DO_SCHEDULE_CALLBACK到Handler所在线程,并最终执行到scheduleFrameLocked方法。如果没有延迟,则直接执行scheduleFrameLocked。
scheduleFrameLocked(准备申请VSYNC信号)
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
//是否运行在主线程
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);
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
case MSG_DO_FRAME:
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
doScheduleVsync();
break;
void doScheduleVsync() {
synchronized (mLock) {
if (mFrameScheduled) {
scheduleVsyncLocked();
}
}
}
该方法中,首先判断了是否开启了VSYNC(上节说过Android4.1之后默认开启VSYNC),如果开启了,判断在不在主线程,如果是主线程就运行scheduleVsyncLocked,如果不在就切换线程,也会调用到scheduleVsyncLocked方法,而这个方法就是我们之前说过的申请VSYNC信号的方法了。
如果没有开启VSYNC,则直接调用doFrame方法。
另外可以看到,刚才我们用到Handler发送消息的时候,都调用了msg.setAsynchronous(true)方法,这个方法就是设置消息为异步消息。因为我们刚才一开始的时候设置了同步屏障,所以异步消息就会先执行,这里的设置异步也就是为了让消息第一时间执行而不受其他Handler消息影响。
小结1
通过上面一系列方法,我们能得到一个初步的逻辑过程了:
在这个过程中,Handler发送了延迟消息,切换了线程,并且给消息都设置了异步,保证最先执行。
继续看scheduleVsyncLocked方法。
scheduleVsyncLocked
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
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);
}
}
代码很简单,就是通过FrameDisplayEventReceiver,请求native层面的垂直同步信号VSYNC。
这个FrameDisplayEventReceiver是在Choreographer构造方法中实例化的,继承自DisplayEventReceiver,主要就是处理VSYNC信号的申请和接收。
刚才说到调用nativeScheduleVsync方法申请VSYNC信号,然后当收到VSYNC信号的时候就会回调onVsync方法了。
onVsync(接收VSYNC信号)
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);
}
}
这里同样通过Handler发送了一条消息,执行了本身的Runnable回调方法,也就是doFrame()。
doFrame(绘制帧数据)
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
//...
//当前帧的vsync信号来的时间,假如为12分200ms
long intendedFrameTimeNanos = frameTimeNanos;
//当前时间,也就是开始绘制的时间,假如为12分150ms
startNanos = System.nanoTime();
//计算时间差,如果大于一个帧时间,则是跳帧了。比如是50ms,大于16ms
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
//计算掉了几帧,50/16=3帧
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
//计算一帧内时间差,50=2ms
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
//修正时间,vsync信号应该来得时间,为12分148ms,保证和绘制时间对应上
frameTimeNanos = startNanos - lastFrameOffset;
}
if (frameTimeNanos < mLastFrameTimeNanos) {
//信号时间已过,不能再绘制了,等待下一个vsync信号,保证后续时间同步上
scheduleVsyncLocked();
return;
}
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
}
try {
//执行相关的callback任务
mFrameInfo.markInputHandlingStart();
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);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
这里主要的工作就是:
doCallbacks(执行任务)
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
if (callbackType == Choreographer.CALLBACK_COMMIT) {
final long jitterNanos = now - frameTimeNanos;
Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
if (jitterNanos >= 2 * mFrameIntervalNanos) {
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
+ mFrameIntervalNanos;
frameTimeNanos = now - lastFrameOffset;
mLastFrameTimeNanos = frameTimeNanos;
}
}
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
c.run(frameTimeNanos);
}
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
do {
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;
@UnsupportedAppUsage
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
}
其实就是按类型,从mCallbackQueues队列中取任务,并执行对应的CallbackRecord的run方法。
而这个run方法中,判断了token,并执行了action的对应方法。再回头看看我们当初ViewRootImpl传入的方法:
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
token为空,所以会执行到action也就是mTraversalRunnable的run方法。
所以兜兜转转,又回到了ViewRootImpl本身,通过Choreographer申请了VSYNC信号,然后接收了VSYNC信号,最终回到自己这里,开始view的绘制。
最后看看mTraversalRunnable的run方法。
mTraversalRunnable
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();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
这就很熟悉了吧,调用了performTraversals方法,也就是开始了测量,布局,绘制的步骤。同时,关闭了同步屏障。
总结
最后再看看总结图:
参考
https://blog.csdn.net/stven_king/article/details/78775166
感谢大家的阅读,有一起学习的小伙伴可以关注下公众号—码上积木❤️
每日三问知识点/面试题,积少成多。
点在看你最好看
html5 header标签是什么意思?html5 header标签的用法详解(附实例)
本篇文章主要的向大家介绍了关于html5 header标签的元素的基本介绍,和header标签的用法实例解析。下面就让我们一起来看看这篇关于html5 header标签的文章吧
一、首先我们来说说html5 header标签元素基本介绍
header元素是一种具有引导和导航作用的结构元素,通常用来放置整个页面或页面内的一个内容区块的标题,但也可以包含其他的内容,比如在header里面放置logo图片、搜索表单等等。
注意:一个页面内并没有限制header的出现次数,也就是说我们可以在同一页面内,不同的内容区块上分别加上一个header元素。
在HTML5版本之前习惯使用div标签布局网页,在HTML5在DIV标签基础上新增header标签元素。也叫“”头部标签。以前我们在div css布局中常常把网页大致分为头部、内容、底部。对于大结构我们常常使用div里加id进行布局。而头部常常使用或进行布局,特点与传统DIV布局不同,少了div做标签,而是新增元素标签。
正应为大家公认html布局中对“header”为常用命名,所以在HTML5新增了个header标签元素。可以这样理解为什么在html5中新增header为标签元素。
除了直接使用header标签外,也可以对header设置class或id。
html5 header标签的用法实例:
对主页的介绍:
PHP中文网
专注于编程(PHP中文网)
PHP中文网的html5语义化标签之结构标签
article、section、hgroup、aside、nav...
...这里面包含了很多东西...
登录后复制
代码效果如图:
这就是最基本的用法了。在上面的结构中,我们可以看到使用header我们定义了一篇文章的标题和内容。这里header标签的使用并不是页面的页头,而是文章的页头。
所以在HTML5中,header的使用更加灵活,你可以根据你的需要来定义和组织document结构。
同样,在架构页面时,header标签一般都放在页面的顶部,但是如果你想把他放在左侧,右侧甚至底部都没有关系,标签只定义了本身在页面的角色,而不是位置。当然更具搜索引擎优化原则,重要内容最后在架构页面的时候提前。
二、现在再来说说兼容性的问题了:
因为header标签是HTML5新增标签元素,所以旧版本浏览器均不支持,需要IE9+以上浏览器、最新谷歌Chrome等浏览器才支持。当然国内360浏览器、百度浏览器、遨游浏览器等浏览器均借用系统自带IE内核,所以国内浏览器实际上与你系统自带浏览器IE版本相同,所以你IE浏览器在IE9或以上版本自然就兼容HTML5新增标签元素。
现在我们升级一下上文的代码:
HTML5教程php中文网
[手机版] [PHP中文网]
登录后复制
效果如图:
以上就是关于HTML5 header标签的用法介绍,本篇文章就介绍这么多了,想看更多的,欢迎来PHP中文网,有问题在下方留言。
【小编推荐】
关于html5中的section标签与div标签的区别(内有实例)
html article标签有什么用?html article标签的使用方法介绍