1.总体渲染流程
本文介绍安卓应用的渲染流程,每次需要更新界面的时候用先向SurfaceFllinger请求Vsync信号,应用进程接收到这个信号之后,开始绘制View树,然后在RenderThread线程调用skia库挥着HWUI去渲染,渲染好的Surface会交给SurfaceFlinger去合成,最终由屏幕显示出具体的画面。
2.重要类
3.更新一帧的具体流程
3.1 触发场景
有两个触发点
1.应用启动的时候第一次渲染。 应用启动的时候执行完Activity.onResume方法之后会调用ViewRoot.setView方法去刷新第一帧,当然setview方法最终还是调用Choreographer.postCallback方法请求vsync信号,不熟悉这个过程的可以参考之前文章《安卓应用启动流程》
2.界面更新的时候 当开发者需要更新界面的时候,可以调用View.invalidate方法或者是View.requestlayput方法来触发界面更新 View.invalidate最终会调用到Choreographer.postCallback去请求vsync信号,而View.requestlayput也是最终会调用Choreographer.postCallback去请求vsync信号。
View.invalidate过程如下:
public void invalidate() {
// 最终会调用到invalidateInternal方法
invalidate(true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
// 这里的p指的是marent属性,其实也就是ViewRootImpl对象
p.invalidateChild(this, damage);
}
public ViewParent invalidateChildInParent(...) {
// 这个方法里头会调用scheduleTraversals方法
invalidate();
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 这里取请求vsync信号
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
接下来看看requestLayout函数的内部实现
public void requestLayout() {
if (mParent != null && !mParent.isLayoutRequested()) {
// 这里的p指的是marent属性,其实也就是ViewRootImpl对象
mParent.requestLayout();
}
public void requestLayout() {
// 接下来和View.invalidate的过程是一样的
scheduleTraversals();
}
3.2 vsync信号的处理
Choreographer收到vsync信号之后会在会包装成一个消息丢进主线程的消息队列中,然后这个消息会由FrameHandler来处理,FrameHandler最终会调用ViewRootImpl.performTraversals方法来绘制这一阵
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
VsyncEventData vsyncEventData) {
// 构造一个异步消息,这个消息的callback绑定的就是FrameDisplayEventReceiver对象,这个类继承自runable
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
// 放进消息队列,处理消息的之后时候会执行FrameDisplayEventReceiver.run方法
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
FrameDisplayEventReceiver.run方法里面最终会调用ViewRootImlp.doTraversal方法完成绘制
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
}
3.3 UI线程的绘制
ViewRootImpl.performTraversals绘制的时候会经过measure, layout, draw三个过程。 measure过程确定每个view的大小,layout过程确定每个view的位置,draw过程会绘制每个view的样子。
private void performTraversals() {
if (windowShouldResize ) {
// 根据需要执行messure,
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// 根据需要执行layout, invalite跟新的时候这个didLayout变量为false,不需要重新layout
// requestLayout的时候这个变量为true,需要重新layout
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
//执行draw流程, 这个函数最终会调用draw方法
if (!performDraw()) {
mActiveSurfaceSyncGroup.markSyncReady();
}
}
draw方法方法的主要职责是执行视图树的绘制操作。它会处理绘制缓存、调用视图树的draw方法,并确保所有需要绘制的视图都被正确地渲染到屏幕上
private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (isHardwareEnabled()) {
// 如果开启了硬件加速,会走这里,安卓4以上的版本默认会开启
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
// 如果没有开启硬件加速,那么会走软件渲染
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
}
return useAsyncReport;
}
// ThreadedRenderer.java
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
updateRootDisplayList(view, callbacks);
// 调用native层的方法RenderProxy::syncAndDrawFrame通知GPU线程完成实际的渲染
int syncResult = syncAndDrawFrame(frameInfo);
}
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
updateViewTreeDisplayList(view);
}
private void updateViewTreeDisplayList(View view) {
// 调用根view, 也就是decorview的updateDisplayListIfDirty方法,这个方法在父类View中实现的
view.updateDisplayListIfDirty();
}
updateViewTreeDisplayList方法最总会调用到view.draw方法完成绘制,每个view都会先绘制背景,然后绘制内容,再绘制子view,最后绘制前景
View.java
private void updateViewTreeDisplayList(View view) {
// 调用根view, 也就是decorview的updateDisplayListIfDirty方法,这个方法在父类View中实现的
view.updateDisplayListIfDirty();
}
public RenderNode updateDisplayListIfDirty() {
draw(canvas);
}
public void draw(@NonNull Canvas canvas) {
// 1, 绘制背景
drawBackground(canvas);
// 2. 绘制内容
onDraw(canvas);
// 3. 绘制子view
dispatchDraw(canvas);
// 4. 绘制前景
onDrawForeground(canvas);
}
3.4 GPU Thread线程的渲染
在GPU线程中,会用opengles来完成界面的最终渲染。 紧接着上面说过的RenderProxy::syncAndDrawFrame方法,看看内部实现
int RenderProxy::syncAndDrawFrame() {
// 最终会调用到DrawFrameTask::postAndWait方法
return mDrawFrameTask.drawFrame();
}
void DrawFrameTask::postAndWait() {
// 这里运行run本对象的run方法
mRenderThread->queue().post([this]() { run(); });
}
void DrawFrameTask::run() {
context->draw(solelyTextureViewUpdates);
}
完成界面的渲染之后,接下来就是提交给SurfaceFlinger去合成,然后就显示在界面上了。
4. 一些常见疑问
Q 每次调用view.invalidate方法刷新的时候都是重新绘制整个屏幕吗
A 不是的,仅仅绘制需要更新的矩形区域
Q invalidate和requestlayput更新的区别,
A invalidate只会重新draw,而requestlayout会重新measure, layout和draw
参考
安卓14源码