banner
NEWS LETTER

Android View绘制的三大流程

Scroll down

前言

一个View从构造到显示,需要经历以下步骤:
1、创建View对象(构造)
2、确定View占的空间尺寸(measure)
3、确定了空间尺寸,就需要确定摆放在哪个位置(layout)
4、确认了摆放位置,就需要确定在上面展示些什么东西(draw)

requestLayout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
ViewRootImpl.java
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//检查是否是主线程,如果不是则直接抛出异常,ViewRootImpl创建的时候生成一个主线程引用
//用当前线程和引用比较,如果是同一个则是主线程
//这也是为什么在子线程对View进行更新、绘制会报错的原因
checkThread();
//用来标记需要进行layout
mLayoutRequested = true;
//绘制请求
scheduleTraversals();
}
}

void scheduleTraversals() {
if (!mTraversalScheduled) {
//标记一次绘制请求,用来屏蔽短时间内的重复请求
mTraversalScheduled = true;
//往主线程Looper队列里放同步屏障消息,用来控制异步消息的执行
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//放入mChoreographer队列里
//主要是将mTraversalRunnable放入队列
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
//省略
}
}

这里引入了Choreographer类,该类是ViewRootImpl构造时候创建的,通过ThreadLocal方式获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Choreographer.java
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
//ViewRootImpl在主线程构造,这里获取的是主线程的looper
Looper looper = Looper.myLooper();
//构造Choreographer对象
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
return choreographer;
}
};

private Choreographer(Looper looper, int vsyncSource) {
//记录looper
mLooper = looper;
//定义Handler接收message
mHandler = new FrameHandler(looper);
//定义DisplayEventReceiver子类,用来接送底层刷新信号
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;;
//内部队列,用来维护各种请求,比如Traversal callback
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
}

放入队列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 {
//异步执行
}
}
}

最后调用到DisplayEventReceiver scheduleVsync方法:

1
2
3
4
5
6
7
8
9
10
11
DisplayEventReceiver.java
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 方法,注册同步脉冲信号事件,告诉底层我需要刷新信号了,记得你的刷新时间到了,给我发送信号
//底层每16ms刷新一次,如果上层没有注册同步脉冲信号事件,则底层刷新的时候不会通知上层。
nativeScheduleVsync(mReceiverPtr);
}
}

好了,到这里requestLayout()已经完成了,就等待底层的刷新信号了。
秉着在哪里注册,就在哪里接收的原则,来看看DisplayEventReceiver类,找到了dispatchVsync方法:

1
2
3
4
5
6
7
8
DisplayEventReceiver.java
// Called from native code.
@SuppressWarnings("unused")
@UnsupportedAppUsage
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
//由DisplayEventReceiver子类FrameDisplayEventReceiver重写
onVsync(timestampNanos, physicalDisplayId, frame);
}

DisplayEventReceiver 是抽象类,其子类是FrameDisplayEventReceiver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
FrameDisplayEventReceiver.java
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
//省略
//构造Message,并使用this,也就是回调自身run方法
Message msg = Message.obtain(mHandler, this);
//设置为异步消息,遇到屏障消息优先执行异步消息
//确保刷新信号能能够及时执行,也就是view绘制优先级是最高的
msg.setAsynchronous(true);
//发送消息
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}

@Override
public void run() {
mHavePendingVsync = false;
//执行刷新消息
//最终是取出mCallbackQueues里的方法执行
doFrame(mTimestampNanos, mFrame);
}

doFrame里取出的方法在什么时候放入的呢?就是之前

mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

放入的。因此doFrame最后会回调mTraversalRunnable run方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ViewRootImpl.java
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}

void doTraversal() {
//没有取消绘制的话则开始绘制
if (mTraversalScheduled) {
mTraversalScheduled = false;
//移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

//真正开始执行measure、layout、draw等方法
performTraversals();
}
}

兜兜转转又回到了ViewRootImpl里。
requestLayout总结为:

1、将绘制请求添加到待执行队列,并发送消息给底层,表示自己有内容需要刷新。这时候reqeustLayout已经执行完毕了。
2、底层间隔时间刷新时,检测到上层的注册信号,因此发送给上层表示我这边已经刷新了,你赶紧换换你的界面吧。
3、收到底层信号时,发送到主线程looper队列里,并标记我这是要告诉别人这是界面刷新的信号哦,耽搁不得,赶紧优先执行。
4、执行第一步的请求,进行view三大绘制流程。

这里有两个问题需要注意一下,还记得代码里提及的一些标记,过滤短时间内的重复请求。

1、mTraversalScheduled 标记,如果这次绘制请求没有被回调执行之前,那么下次请求将忽略,比如短时间内重复的requestLayout。
2、mFrameScheduled 标记,如果底层的刷新信号没有来之前,再次发送给底层的信号将被忽略。

Android

其他文章
目录导航 置顶
  1. 1. 前言
  2. 2. requestLayout
请输入关键词进行搜索