android重绘机制,Android绘制原理之刷新机制

我们都知道,Android是16ms刷新一帧,而通常我们所理解的刷新是“每个view的draw()方法被调用”,所以这里就有一个问题了,Android系统底层每隔16ms就发出一个垂直同步信号,那么是不是每个view的draw()方法都会每个16ms调用一次呢?如果这样的话系统消耗岂不是非常大?是不是有什么特殊优化手段?

1. 垂直同步信号的使用者——Choreographer

Choreographer是Android4.1和垂直同步信号机制一起引入的,我们都知道垂直同步信号其实是操作系统底层的一种时钟中断,那么java层是如何利用这个中断的呢?主要就是Choreographer这个类来协调接收的。

这里我们不会分析这个类的具体实现,主要简单的介绍下如何接收底层中断等一些简单的用法,便于大家理解后面的知识。

1.1 中断信号利用原则

由于中断信号时源源不断的,所以为了避免滥用中断信号,原则是:需要接收中断信号必须向系统注册一个接收者,下次产生了新的中断就会回调这个接受者的回调方法。注意,每次注册只能接收一次中断,想要继续接收必须重新注册。

1.2 中断信号接收者

首先我们看下这个信号接收者:

public abstract class DisplayEventReceiver {

//省略其他代码

/**

* Called when a vertical sync pulse is received.

* The recipient should render a frame and then call {@link #scheduleVsync}

* to schedule the next vertical sync pulse.

*

* @param timestampNanos The timestamp of the pulse, in the {@link System#nanoTime()}

* timebase.

* @param builtInDisplayId The surface flinger built-in display id such as

* {@link SurfaceControl#BUILT_IN_DISPLAY_ID_MAIN}.

* @param frame The frame number. Increases by one for each vertical sync interval.

*/

public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {

}

/**

* Schedules a single vertical sync pulse to be delivered when the next

* display frame begins.

*/

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);

}

}

//省略其他方法

}

这里我只列出了两个方法,onVsync就是中断会回调的方法,只会回调一次,如果希望接收下一次中断信号,就要手动调用scheduleVsync()方法。

1.3 Choreographer利用DisplayEventReceiver干了什么

// The display event receiver can only be accessed by the looper thread to which

// it is attached. We take care to ensure that we post message to the looper

// if appropriate when interacting with the display event receiver.

private final FrameDisplayEventReceiver mDisplayEventReceiver;

Choreographer有一个这样的成员变量,主要都是通过这个成员变量来接收中断信号的:

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) {

//省略其他代码

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发送了一个消息,其实主要就是为了切换到主线程执行这里的run()方法, 换句话说每次中断信号来了最终都会回调到doFrame()方法,到了这里,Chogreographer是如何使用中断信号的就很清楚了,换句话说如果想要接收中断信号做些什么,我们只需要写在 doFrame()方法 里就好了。

1.4 如何利用doFrame()方法

现在我们知道了,利用Choreographer能够达到在垂直中断信号产生时回调doFrame()方法的目的,那么我们怎么将自己要执行的代码塞到doFrame()方法中去呢?

我们先看下doFrame()方法的源码:

void doFrame(long frameTimeNanos, int frame){

mFrameInfo.markInputHandlingStart();

//省略其他代码

doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

//省略其他代码

doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);

//省略其他代码

doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

//省略其他代码

doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);

//省略其他代码

}

可以看到doFrame()方法其实会执行一系列的callBack回调,我们可以将自己的任务塞到这些callBack中去得到执行,具体如下:

public void postCallback(int callbackType, Runnable action, Object token) {

postCallbackDelayed(callbackType, action, token, 0);

}

通过Choreographer.postCallback()方法,我们就可以让自己的Runnable在下一次垂直信号产生时得到执行。

2. UI绘制与刷新本质

UI界面的改变核心是一些会影响UI的变量的值的改变,这些值改变后我们接收垂直同步信号,在下一次信号中断产生时根据新的UI变量重新绘制当前界面即可做到UI的刷新。总结下主要是两点:

UI布局变量的改变

注册垂直同步信号中断监听,在下一次垂直同步信号来临时重绘界面。

3. UI是如何绘制的

想要在垂直同步信号来临时重绘界面,我们必须先了解UI到具体如何绘制的。这里我就不再带领大家一步步探究,而是直接说出结论了。

Android 的ui是按照树型结构组织的,而这个树的根节点(DecorView)就是由一个ViewRootImpl持有,UI的树型遍历绘制也是由ViewRootImpl发起的。这里我们看下ViewRootImpl是如何调用DecorView的draw()方法的:

d16b5409503d

draw()调用流程

通过上面的调用图可以看到关键其实是scheduleTraversals()方法,他会通过Choreograpter.postCallback()方法注册一个回调,该回调能让整个UI树在下一次垂直同步信号来临时得到绘制。

4. 常用的刷新原理

现在我们回过头来看下我们经常用的刷新方法,主要是requestLayout()和invalidate()方法,这两个方法都会一直沿着UI树往上找,最终会调用到ViewRootImpl的scheduleTraversals()方法,这样就会在下一次垂直同步信号产生时重新绘制整个界面。

5. 结语

本文基本没有涉及什么代码,主要是重垂直同步信号的原理入手,宏观的介绍了UI的绘制与刷新原理,个人理解,如果有误,恳请指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值