android中Invalidate和postInvalidate的区别
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android中Invalidate和postInvalidate的区别相关的知识,希望对你有一定的参考价值。
参考技术A invalidate:UI线程自身中使用,postInvalidate:在非UI线程中使用。本回答被提问者和网友采纳
Project Butter与invalidate
一、Project Butter
google希望摆脱android UI交互方面的滞后、卡顿问题,在android4.1提出了Project Butter,力争UI如黄油般丝滑。
为了确保一致的帧速率,Android 4.1 将 Vsync 计时扩展到了所有由 Android 框架完成的绘图和动画中。包括应用渲染、轻触事件、画面构成和显示刷新在内的一切操作均按照 16 毫秒的 Vsync 检测信号同步运行,因此帧不会提前或延迟。
Android 4.1 还在图形管道中添加了三重缓冲,以实现更加一致的渲染,让滚动、分页和动画等更加流畅。
Android 4.1 通过以下两种方式降低轻触延迟:将轻触同步到 Vsync 计时,以及实际预测屏幕刷新时您的手指所在的位置。轻触延迟降低后,轻触响应速度会更快、更一致。此外,在处于不活动状态一定时间后,Android 会在下一次轻触事件发生时应用 CPU 输入增强,以确保不会出现延迟。
1.1 Vsync 计时
android使用cpu进行测量和布局,使用gpu进行绘制和渲染,最终效果在屏幕上显示。由于绘制渲染和屏幕显示都是按分辨率像素点逐行进行的,需要一定的时间消耗, 如果绘制渲染和显示使用同一个缓冲区buffer,那么就会同时进行渲染和显示,界面显示两个及以上不同帧数据最终导致界面撕裂。
所以,android界面渲染和显示一般采用双缓冲机制,分为Back Buffer和Frame Buffer,基本原理就是采用两块buffer,Back Buffer也称离屏缓冲区用于CPU/GPU后台绘制,Frame Buffer用户前台屏幕显示,当Back Buffer数据准备好后,他们才会交换内存地址。
android绘制的双缓冲机制确实解决了屏幕撕裂问题,但是这里面还有一个问题,交换内存地址的时机选择。什么时候交换数据屏幕开始显示准备好的数据并且CPU/GPU开始准备下一帧数据呢?假定CPU/GPU绘制频率大于界面的绘制频率,如果CPU/GPU不知道当前界面是否显示完成,CPU/GPU绘制完第一帧数据后等了很久才去绘制第二帧数据,那么这期间屏幕上只能一直显示这个多余的(Jank)第一帧数据,出现界面的掉帧、卡顿现象。
如何解决这个场景呢?这就需要设定一个时间间隔定时的告知CPU/GPU可以进行数据交互并准备绘制下一帧数据了,假定CPU/GPU绘制频率大于界面的绘制频率,那么这个时间间隔就取决于界面显示所消耗的时间。这里就引入两个概念:行频(Horizontal scanning)和场频(Vertical scanning,行频又称“水平扫描频率”代表屏幕每秒钟从左到右扫描的次数;场频也称“垂直扫描频率”代表每秒钟整个屏幕刷新的次数。当扫描完整个屏幕设备重新回到第一行开始下轮扫描,这中间有个间隙,称为Vertical Blanking Interval(VBI),如果在这个时间间隔去交换buffer效果最佳。Vsync(Vertical synchronization,垂直同步)就是利用VBI时期的Vertical Sync pulse来保证双缓冲交换的最佳时间点。
1.2 三重缓冲
Vsync可以解决双缓冲机制下PU/GPU绘制频率大于界面的绘制频率的场景,那么如果CPU/GPU绘制频率小于界面的绘制频率呢?
如上图所示,约定上图未显示第一个Vsync信号,那么图中的第一个Vsync信号实际上是第二个Vsync信号,依次递增。当界面显示第一帧数据时,CPU/GPU在准备第二帧数据,但是GPU准备第二帧数据时耗时比较就,在第二个Vsync信号到来时还未准备好,Back Buffer被锁定,那么界面只能继续显示第一帧数据,当GPU准备好第二帧数据时却没有Vsync信号,这就导致CPU和很大一部分GPU资源浪费。终于到了第三个Vsync信号到来,此时终于显示之前超时的第二帧数据了,同时CPU/GPU在准备第四帧数据,和之前一样,GPU准备第四帧数据又超时了,那么第四个Vsync信号到来时又只能显示第二帧数据,这就导致再次卡顿。从中可以发现规律,由于CPU/GPU绘制频率小于界面的绘制频率,基本上每隔一个Vsync信号就卡一次,这就导致界面刷新频率降低的一半。
如何解决呢?那么如果两个缓冲不行,那就再加一个缓冲区,三个缓冲区应该可以了吧。看下图
图中可以看到,由于CPU/GPU绘制频率小于界面的绘制频率,生产者的生产速度不能满足消费者的消费速度了,三缓冲情况是Frame Buffer还是一个,Back Buffer变成了两个,通过增加生产者数量来满足消费者。当第二个Vsync信号到来时发现GPU还没绘制完到当前Back Buffer,那么就启动备选的第三个Back Buffer重新开启一次新的绘制,虽然第二帧Vsync信号到来时还是只能显示第一帧数据,但是由于适时增加了一个Back Buffer使得后面就不再卡顿了(可以被打脸一次,但是不会一直被打脸啦)。
二、invalidate流程
讲到invalidate流程先要解释几个关键类。
1.1 SurfaceFlinger
SurfaceFlinger 接受来自多个源的数据缓冲区,然后将它们进行合成并发送到显示屏。
在屏幕处于两次刷新之间时,屏幕会向 SurfaceFlinger 发送 VSYNC 信号。当 SurfaceFlinger 接收到 VSYNC 信号后,SurfaceFlinger 会遍历其层列表(层是 Surface 和 SurfaceControl 实例的组合),以查找新的缓冲区。如果 SurfaceFlinger 找到新的缓冲区,SurfaceFlinger 会获取缓冲区;否则,SurfaceFlinger 会继续使用上一次获取的那个缓冲区。
1.2 Hardware Composer HAL (HWC)
硬件混合渲染器,Hardware Composer HAL (HWC) 确定使用可用硬件合成缓冲区的最有效的方法,作为 HAL,其实现是特定于设备的,而且通常由显示硬件原始设备制造商 (OEM) 完成。
1.3 Choreographer
Choreographer(编舞者)可以周期性的接收到屏幕发出的Vsync信号,然后知道动画、输入以及绘制。ValueAnimator.start(),View#postOnAnimation等方法底层实现就是Choreographer,Choreographer也经常与ViewRootImpl一起协作,当然你可以可以直接使用Choreographer.getInstance().postFrameCallback()定制一些你自己的绘制流程。下一节通过View.invalidate方法展示Choreographer与ViewRootImpl协作渲染的流程。
1.4 View.invalidate流程
1.4.1 invalidateInternal
View.invalidate先调用View.invalidateInternal方法,.invalidateInternal方法中调用父view的invalidateChild方法,实际上最终的ViewParent实现就是ViewRootImpl。
//View.java
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
……
// Propagate the damage rectangle to the parent view.
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
……
}
1.4.2 invalidateChild
ViewRootImpl中的invalidateChild方法调用invalidateChildInParent方法,invalidateChildInParent方法调用了invalidateRectOnScreen方法,invalidateRectOnScreen方法调用了大家熟悉的scheduleTraversals()方法。
//ViewRootImpl.java
private void invalidateRectOnScreen(Rect dirty) {
……
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals();
}
}
1.4.3 scheduleTraversals
大家都知道scheduleTraversals()方法里面有一个TraversalRunnable类型的action用于执行View的测量、布局和绘制三大操作,但是这个action是如何触发的呢?这里调用了Choreographer的postCallback方法并把TraversalRunnable作为参数传过去。上面有提到Back buffer的绘制以及Frame buffer的显示都需要Vsync触发,那么这里的mChoreographer.postCallback应该就和Vsync信号申请有关了。
//ViewRootImpl.java
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); //设置同步屏障,同步消息被堵塞,只有异步消息可以执行
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); //调用mChoreographer申请Vsync
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending(); //通知下一帧的硬件渲染即将开始
pokeDrawLockIfNeeded();
}
}
1.4.4 postCallback
Choreographer的postCallback方法调用了零延迟的postCallbackDelayed方法,postCallbackDelayed又调用了postCallbackDelayedInternal方法,postCallbackDelayedInternal方法首先将TraversalRunnable放入到任务序列mCallbackQueues[callbackType]中,由于delayMillis传入的是0,所以这里会调用scheduleFrameLocked方法。
//Choreographer.java
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) {
scheduleFrameLocked(now); //这里会执行
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
1.4.5 scheduleFrameLocked
scheduleFrameLocked方法会根据是否使用USE_VSYNC变量从而执行不同操作,USE_VSYNC变量值可以通过SystemProperties属性设置,如果使用USE_VSYNC那么就从硬件屏幕获取VSYNC信号触发后面的绘制工作,否则就软件模拟延迟一定时间去执行MSG_DO_FRAME消息,最终执行doFrame方法,实际上使用USE_VSYNC变量最终也是会执行doFrame方法。分析下使用USE_VSYNC的情况,根据当前是否在looper线程上执行又分为两个分支,实际上不管是通过scheduleVsyncLocked方法还是MSG_DO_SCHEDULE_VSYNC消息最终都会通过调用FrameDisplayEventReceiver对象的scheduleVsync方法来申请底层的VSYNC信号。scheduleVsync()方法会调用FrameDisplayEventReceiver的父类DisplayEventReceiver.java的native方法,逻辑比较复杂,这里就不展开了。
//Choreographer.java
// Enable/disable vsync for animations and drawing.
private static final boolean USE_VSYNC = SystemProperties.getBoolean(
"debug.choreographer.vsync", true);
……
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
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.
if (isRunningOnLooperThreadLocked()) {
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);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);//调用doFrame方法
msg.setAsynchronous(true);//设置异步
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
1.4.6 FrameDisplayEventReceiver
上面提到如果使用USE_VSYNC变量时会调用FrameDisplayEventReceiver对象的scheduleVsync方法来申请底层的VSYNC信号,FrameDisplayEventReceiver继承自抽象类DisplayEventReceiver并实现了onVsync方法,根据onVsync方法的注释可以看出onVsync方法在VSYNC脉冲到来时会被调用从而开始新一轮的数据帧渲染。所以,当VSYNC脉冲到来时FrameDisplayEventReceiver的onVsync方法会调用。onVsync方法会发起一个异步消息,这里FrameDisplayEventReceiver类自身实现了Runnable接口并且实现了run接口方法,所以这个handler最终调用的是FrameDisplayEventReceiver类自身的run()方法,真是一波骚操作,我不禁竖起了大拇指。那么最终doFrame方法会执行。
//Choreographer$FrameDisplayEventReceiver.java
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);//第二个参数是自身,FrameDisplayEventReceiver自身实现了Runnable接口
msg.setAsynchronous(true);//异步消息
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
}
1.4.7 doFrame
doFrame方法调用了四次doCallbacks方法并传入了不同的类型分别是Choreographer.CALLBACK_INPUT(对应输入事件,优先级最高)、Choreographer.CALLBACK_ANIMATION(动画事件,优先级次之)、Choreographer.CALLBACK_TRAVERSAL(绘制事件,优先级更低)、Choreographer.CALLBACK_COMMIT(完成事件,优先级最低)。这里主要分析Choreographer.CALLBACK_TRAVERSAL绘制事件。
//Choreographer.java
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
……
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();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
……
}
1.4.8 doCallbacks
doCallbacks方法遍历取出了之前添加到1.4.4中添加到mCallbackQueues[callbackType]中的Runable任务,并挨个执行,那么1.4.3中的TraversalRunnable任务就会执行,我们熟悉的测量、布局、绘制开始启动。
//Choreographer.java
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
……
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {//遍历取出之前添加到1.4.4中添加到mCallbackQueues[callbackType]中的Runable任务
if (DEBUG_FRAMES) {
Log.d(TAG, "RunCallback: type=" + callbackType
+ ", action=" + c.action + ", token=" + c.token
+ ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
}
c.run(frameTimeNanos); //执行Runable任务
}
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
do {
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
以上是关于android中Invalidate和postInvalidate的区别的主要内容,如果未能解决你的问题,请参考以下文章
android中Invalidate和postInvalidate的差别
android中Invalidate和postInvalidate的区别
android中Invalidate和postInvalidate的区别
invalidate和postInvalidate 的区别及使用