我们所写的View是如何被添加与显示的呢?接下来我以 Android API 28 为准,Activity 的生命周期为主线,以图文的方式来展现我看源码的思路,内容有点多,如有不当,欢迎交流指正😃
总体思路
在Activity
的生命周期中我们都说在Activity#onCreate
中创建了View
,在Activity#onResume
之后显示View,那么这个过程是怎么样的?最后View
又是如何渲染到屏幕上的呢?我们带着以下问题看源码:
- 在
Activity#onCreate
中调用Activity#setContentView
,完成了什么工作? - 说是在
Activity#onResume
之后完成屏幕渲染的,有源码的证据吗?最后又是怎么渲染的?
在onCreate中调用setContentView
我们给Activity
编写布局后,习惯在Activity#onCreate
中调用Activity#setContentView
,这个方法都做了什么工作呢?我们来看看它的具体内容:
private Window mWindow;
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}
这里通过getWindow
获取了Window
,然后调用了Window#setContentView
方法,Window
是一个抽象类,setContentView
是其中的抽象方法,那这里到底执行了谁的代码?换句话说,mWindow
被赋值的对象是谁呢?
Activity
的创建代码在ActivityThread#performLaunchActivity
中,在利用 反射 创建Activity
后,调用了Activity#attach
方法,之后又通过Instrumentation
调用了Activity#onCreate
方法。在Activity#attach
方法中,mWindow
被赋值:
mWindow = new PhoneWindow(this, window, activityConfigCallback);
说明mWindow
所指向的就是PhoneWindow
的对象,调用了PhoneWindow
的方法。这里我画了一个简单的图,浅色框框代表类,深色框框代表方法:
接下来我们再来看看PhoneWindow#setContentView
中都做了什么。
1、PhoneWindow#installDecor
在PhoneWindow#setContentView
中第一步先调用了PhoneWindow#installDecor
,该方法中对mDecor
与mContentParent
进行了赋值:
if (mDecor == null) {
mDecor = generateDecor(-1);
......
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
......
}
mDecor
就是DecorView
,PhoneWindow#generateDecor
方法中直接 new 了一个DecorView
并返回。- 而
mContentParent
是一个FramenLayout
,在PhoneWindow#generateLayout
方法中会加载一个 ID 为R.layout.screen_simple
的资源,并将生成的View
加入mDecor
中,再从其中获取 ID 为com.android.internal.R.id.content
的View返回:
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
ID的信息在Window.java
中:
/**
* The ID that the main layout in the XML layout file should have.
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
好了,经过PhoneWindow#installDecor
之后,我们构建了View
树的基本框架:
我们所编写的xml
布局的最终会通过Activity#setContentView
放入最内层的FrameLayout
中,它就是mContentParent
,不过现在还没有放入。
2、LayoutInflater#inflate
回到PhoneWindow#setContentView
方法,在没有转场动画的情况下,它的第二步是调用LayoutInflater#inflate
mLayoutInflater.inflate(layoutResID, mContentParent);
LayoutInfater的作用是什么?
在LayoutInflater
的注释中这样说到:
Instantiates a layout XML file into its corresponding
{@link android.view.View}
objects. It is never used directly. Instead, use{@link android.app.Activity#getLayoutInflater()}
or{@link Context#getSystemService}
to retrieve a standard LayoutInflater instance that is already hooked up to the current context and correctly configured for the device you are running on.
意思就是LayoutInflater
的作用是将XML
转换为Java
对象的工具,并将转换出来的对象放入对应的View
中(当然也可以不放)。一般不直接使用,通过上面所说的方法获取一个标准的实例。它会预先实例化并按照当前设备的要求配置好,并与当前Context
关联。
LayoutInflater从哪里来?
在PhoneWindow
创建的时候,会获取一个LayoutInflater
的实例:
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
这里的LayoutInflater
其实是PhoneLayoutInflater
,具体的创建过程,大家可以去看我的下一篇文章(如果对你有帮助也请帮忙点个赞😂 ):
源码阅读#LayoutInflater的创建过程 & AsyncLayoutInflater原理
LayoutInflater如何创建View?
上面我们提到,在LayoutInflater#inflate
方法中,XML
被转化为View
的对象并形成View
树,该方法中首先将XML
转换为了XmlResourceParser
对象:
final XmlResourceParser parser = res.getLayout(resource);
接着继续调用LayoutInflater#inflate
的重载方法,在LayoutInflater#inflate
的最终重载方法里有重要的三步:
- 首先通过
LayoutInflater
中的createViewFromTag
方法创建了根布局(XML
最外层View
)。 - 其次通过
rInflateChildren
方法构建子布局(这里面使用了 循环+递归 的方式实现了深度优先遍历XML
元素,调用createViewFromTag
方法创建各个View
,并会使用ViewGroup#addView
方关联View
,构建出View
树)。 - 最后调用了
ViewGroup#addView
方法将生成的View
加入一开始传入的父布局中。
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
......
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
......
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
......
if (root != null && attachToRoot) {
root.addView(temp, params);
}
......
}
createViewFromTag
中会首先使用各个工厂类(Factory
、Factory2
对象)创建View
,如果没创建,则最终调用LayoutInflater#createView
方法通过 反射 创建各个View
。
最后通过ViewGruop#addView
方法将View
关联(关联的本质就是给View
中的mParent
赋值,它是一个ViewParent
接口变量,而ViewGroup
与ViewRootImp
实现了ViewParent
接口)。
到此为止我们就获取了整个View
树(这些只是对象,并没有显示到屏幕上),经历了以下方法:
到此为止,我们的Activity#onCreate
阶段就结束了。我们来回答第一个问题:在Activity#onCreate
中调用Activity#setContentView
,完成了创建View
、形成View
树的工作。
但是,但是,注意了,这里会有一个疑问,“inflate”的过程调用了ViewGruop#addView
,为什么没有显示到界面上?addView
可是会触发整个绘制过程的呀。嗯,我们接着看下一个问题。
调用了inflate方法,触发了addView,为什么没有显示到屏幕上?
这里的关键就是View
中的mParent
变量。
PhoneWindow
在调用LayoutInflater#inflate
方法时传入了mContentParent
,并且通过重载方法后attachToRoot
为true
:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);// 这里root不为空
}
所以,在LayoutInflater#inflate
的最终重载方法里会调用ViewGroup#addView
方法:
if (root != null && attachToRoot) {
root.addView(temp, params);
}
public void addView(View child, int index, LayoutParams params) {
......
// addViewInner() will call child.requestLayout() when setting the new LayoutParams
// therefore, we call requestLayout() on ourselves before, so that the child's request
// will be blocked at our level
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
这其中有三个方法:
- requestLayout:其中利用了
mParent
对象调用了ViewParent#requestLayout
。
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
- invalidate:其中利用
mParent
对向调用了ViewParent#invalidateChild
。
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
......
p.invalidateChild(this, damage);
}
- addViewInner:其中会将子
View
的mParent
赋值为this
即ViewGroup
对象本身。
requestLayout
中,如果mParent
不为空,这里会一直调用父View
的requestLayout
,最终调用ViewRootImpl
的requestLayout
(下面会讲到原因)。但是此时(onCreate
阶段)mParent
为空,所以以上两个方法并没有做多少实质上的工作。所以,在Activity#onCreate
阶段进行addView
,只会对mParent
进行赋值,并没有触发能把View
渲染到屏幕上的方法。
😑为什么mParent
是空的呢?
因为mParent
只有在两个地方被赋值,一个就是上面所说的addViewInner
方法,它在ViewGroup
中,另一个就是在ViewRootImpl
中的setView
方法中。
ViewRootImpl#setView
会调用View#assignParent
(assignParent
方法会给View
的mParent
赋值):
view.assignParent(this);
这里的this
就是ViewRootImpl
对象本身。
ViewRootImpl#setView
又是在哪里被调用的呢?在Activity#handleResumeActivity
中,先调用了Activity#performResumeActivity
,然后调用了WindowManager#addView
,然后在WindwoManagerGlobal
中调用了ViewRootImpl#setView
:
这里的WindowManager#addView
传入的是DecorView
,所以,不同于其他View
,DecorView
的mParent
是ViewRootImpl
,所以ViewGroup#requestLayout
中的mParent.requestLayout
向上调用的终点是ViewRootImpl#requestLayout
。
可以看到,WindowManager#addView
过程是在Activity#onResume
之后触发的,所以,前面ViewGroup#addView
之中的前两个方法执行时(addViewInner
在这两个方法之后调用),mParent
还是空的。
😏所以,在Activity#onCreate
中,只是构建了View
树,却没有进行绘制~~
在onResume之后调用WindowManager的addView
在上面的叙述中有说到如何调用ViewRootImpl#setView
,它是在Activity#onResume
之后被触发的,这里先回答第二个问题的一部分:屏幕渲染是在Activity#onResume
之后,它就是在ViewRootImpl#requestLayout
之后触发的。
首先ViewRootImpl#requestLayout
会调用到ViewRootImpl#scheduleTraversals
方法:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
然后scheduleTraversals
会设置同步屏障,并调用postCallback
,传入一个Runnable
的任务(mTraversalScheduled
用于控制每帧只绘制一次,mTraversalRunnable
最终会被执行):
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
......
}
}
Choreographer#postCallback
继续调用Choreographer#postCallbackDelayed
,在Choreographer#postCallbackDelayedInternal
方法中,delayMillis
为0
:
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);
}
}
}
继续调用了scheduleFrameLocked
方法:
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_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);
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
这里一般会走到scheduleVsyncLocked
方法中:
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
然后调用DisplayEventReceiver
中的scheduleVsync
:
/**
* 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);
}
}
最终调用native
方法nativeScheduleVsync
,前面通过mHandler
发送的异步消息,最终都会走到这个方法(这里用了同步屏障,保证了这些消息可以优先被处理)。
最终,nativeScheduleVsync
会向SurfaceFlinger
注册VSync
信号的监听,VSync
信号由SurfaceFlinger
实现并定时发送,当Vsync
信号来的时候就会回调FrameDisplayEventReceiver#onVsync
,这个方法给发送一个带时间戳Runnable
消息,这个Runnable
消息的run()
现就是FrameDisplayEventReceiver#run
, 接着就会执行Choreographer#doFrame
。doFrame
会执行doCallbacks
方法就是上面代码中mCallbackQueues
中的callback,就是前面提到的mTraversalRunnable
:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
doTraversal
会调用到performTraversals
,这个方法中就会调用到我们常说的三大方法:performMeasure
,performLayout
,performDraw
。
在ViewRootImpl
创建的时候,会创建一个Surface
:
// These can be accessed by any thread, must be protected with a lock.
// Surface can never be reassigned or cleared (use Surface.clear()).
public final Surface mSurface = new Surface();
performDraw
最终会触发ViewRootImpl#drawSoftware
,这个Surface
在ViewRootImpl#drawSoftware
方法中会被加上数据:
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
......
// 获取Surface中的画布
canvas = mSurface.lockCanvas(dirty);
......
// 绘制
mView.draw(canvas);
......
// 发送数据
surface.unlockCanvasAndPost(canvas);
......
}
最终,unlockCanvasAndPost
会使用native
方法发送绘制数据(如果hwuiContext不为空会使用hwui):
public void unlockCanvasAndPost(Canvas canvas) {
synchronized (mLock) {
checkNotReleasedLocked();
if (mHwuiContext != null) {
mHwuiContext.unlockAndPost(canvas);
} else {
unlockSwCanvasAndPost(canvas);
}
}
}
此时数据会被加载到一个缓冲区中,然后当SurfaceFlinger
发送VSync
信号后,便会最终显示到屏幕上啦(这里用到了双缓冲的机制,或者是三缓冲)。
总结一下,这里有两个信号监听:
nativeScheduleVsync
会注册一个VSync
信号的监听,会回调FrameDisplayEventReceiver#onVsync
,从而开始创建绘制数据(此时在CPU中)。drawSoftware
会将数据放入缓冲区,等待VSync
信号,然后将数据(从CPU)传给GPU。
值得一提的是,在SurfaceFlinger
的实现里,通常一个Surface
有两块Buffer
, 一块用于绘画,一块用于显示,两个Buffer
按照固定的频率进行交换,从而实现Window
的动态刷新:
到这里,我们所写的XML
文件就真正绘制出来啦~ 💪💪💪
好了,第二个问题,源码证据,如上所述,最后是怎么渲染的,就是native
层的工作了,大家可以去了解下双缓冲机制。
关于后半部分的参考文章(几片文章内容上可能有重叠,但是都有自己理解很到位的地方):
- 深入浅出Android屏幕刷新原理
- Android Vsync 原理浅析
- Android性能优化典范之Understanding VSYNC
- Android卡顿原理分析和SurfaceFlinger,Surface概念简述
写的有点长,有不妥的地方的话希望能指出,谢谢。