我们所写的View是如何被添加与显示的呢?接下来我以 Android API 28 为准,Activity 的生命周期为主线,以图文的方式来展现我看源码的思路,内容有点多,如有不当,欢迎交流指正😃
在Activity
的生命周期中我们都说在Activity#onCreate
中创建了View
,在Activity#onResume
之后显示View,那么这个过程是怎么样的?最后View
又是如何渲染到屏幕上的呢?我们带着以下问题看源码:
Activity#onCreate
中调用Activity#setContentView
,完成了什么工作?Activity#onResume
之后完成屏幕渲染的,有源码的证据吗?最后又是怎么渲染的?我们给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
中都做了什么。
在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
,不过现在还没有放入。
回到PhoneWindow#setContentView
方法,在没有转场动画的情况下,它的第二步是调用LayoutInflater#inflate
mLayoutInflater.inflate(layoutResID, mContentParent); 复制代码
在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
的创建过程很有意思,这里想多说一下,如果嫌太长可以跳过这一节。
我们带着一个问题往下看:为什么Context
与LayoutInflater
是一对一的关系?我们从PhoneWindow
的构造方法看起,这里获取了LayoutInflater
实例:
public PhoneWindow(Context context) { super(context); mLayoutInflater = LayoutInflater.from(context); } 复制代码
接着看LayoutInflater#from
:
public static LayoutInflater from(Context context) { LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); if (LayoutInflater == null) { throw new AssertionError("LayoutInflater not found."); } return LayoutInflater; } 复制代码
我们可以看到其中调用了Context#getSystemService
,这里传入的是Activity
的Context
,而从ActivityThread#performLaunchActivity
中我们知道,这里的Context
引用的实际上就是ContextImpl
对象,我们继续看ContextImpl#getSystemService
方法:
@Override public Object getSystemService(String name) { return SystemServiceRegistry.getSystemService(this, name); } 复制代码
这里调用了SystemServiceRegistry#getSystemService
:
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS = new HashMap<>(); public static Object getSystemService(ContextImpl ctx, String name) { ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name); return fetcher != null ? fetcher.getService(ctx) : null; } 复制代码
敲黑板了哈🧐,记住上面方法中使用的fetcher.getService(ctx)
,还有SYSTEM_SERVICE_FETCHERS
,下面要考。
下面简述下SystemServiceRegistry
是如何是如何获取各个“Service”的:
比如:
static { ...... registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class, new CachedServiceFetcher<LayoutInflater>() { @Override public LayoutInflater createService(ContextImpl ctx) { return new PhoneLayoutInflater(ctx.getOuterContext()); }}); ...... } private static <T> void registerService(String serviceName, Class<T> serviceClass, ServiceFetcher<T> serviceFetcher) { ...... SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher); } 复制代码
可以看到Fecther
被加到了SYSTEM_SERVICE_FETCHERS
中。这里的“Service”还有很多,包括ActivityManager
、BluetoothManager
、BatteryManager
等等。
每创建一个CachedServiceFetcher
,SystemServiceRegistry
中的一个静态变量都会+1:
private static int sServiceCacheSize; 复制代码
CachedServiceFetcher
继承了Fetcher
,实现了Fetcher#getService
,对“Service”进行了懒创建,即首次调用才创建,又声明了createService
抽象方法,该方法用于在在静态代码块中实现特定的创建功能,参考第1点中的代码。因为是静态代码块中创建的,所以一种“Service”对应一个具体的CachedServiceFetcher
对象,并且CachedServiceFetcher
会记录当前值作为下标,这个下标有什么用呢,看下一点。
在调用fetcher.getService(ctx)
的时候,CachedServiceFetcher
会通过ContextImp
中的一个非静态成员来获取“Service”的对象:
// The system service cache for the system services that are cached per-ContextImpl. final Object[] mServiceCache = SystemServiceRegistry.createServiceCache(); 复制代码
而SystemServiceRegistry#createServiceCache
是利用了SystemServiceRegistry
的静态成员sServiceCacheSize
进行初始化(就是上面那个会+1的变量):
/** * Creates an array which is used to cache per-Context service instances. */ public static Object[] createServiceCache() { return new Object[sServiceCacheSize]; } 复制代码
这下明白了:静态代码块创建了多个“Service”,与此同时静态的sServiceCacheSize
记录了“Service”的数量,CachedServiceFetcher
记录了“Service”的下标,在每个ContextImpl
被创建的时候,非静态的mServiceCache
就根据sServiceCacheSize
进行初始化,在我们调用Context#getSystemService
的时候,如果当前Context
的mServiceCache
没有对应的“Service”,就创建一个,放到CachedServiceFetcher
事先记录好的下标下。
所以,一个ContextImpl
对象会持有一套“Service”,保存在mServiceCache
中。当然,如果没有“get”过这个“Service”,它就不会被创建(所以我从来都是只用Application
的Context
来“getService”,这样各种“Service”就只有一个实例,减少对象数目😂)。总结一下:
上面我们提到,在LayoutInflater#inflate
方法中,XML
被转化为View
的对象并形成View
树,该方法中首先将XML
转换为了XmlResourceParser
对象:
final XmlResourceParser parser = res.getLayout(resource); 复制代码
接着继续调用LayoutInflater#inflate
的重载方法,这里面使用了 循环+递归 的方式实现了深度遍历XML
元素,调用了LayoutInflater#createViewFromTag
方法创建各个View
,其中会首先使用各个工厂类(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
可是会触发整个绘制过程的呀。嗯,我们接着看下一个问题。
这里的关键就是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
的最终重载方法里有重要的三步:
createViewFromTag
创建了根布局(XML
最外层View
)。rInflateChildren
方法构建子布局(子布局会使用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); } ...... } 复制代码
以上过程会调用到ViewGroup#addView
方法:
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); } 复制代码
这其中有三个方法:
mParent
对象调用了ViewParent#requestLayout
。if (mParent != null && !mParent.isLayoutRequested()) { mParent.requestLayout(); } 复制代码
mParent
对向调用了ViewParent#invalidateChild
。final ViewParent p = mParent; if (p != null && ai != null && l < r && t < b) { ...... p.invalidateChild(this, damage); } 复制代码
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
树,却没有进行绘制~~
在上面的叙述中有说到如何调用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
方法中发送了消息,并将任务加入队列:
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); } } } 复制代码
这里实现了每隔一段固定时间进行一次绘制的逻辑,最终需要绘制时会发送异步消息。这里的消息是MSG_DO_SCHEDULE_CALLBACK
,mHandler
是FrameHandler
,收到消息后执行Choreographer#doScheduleCallback
方法,进过一系列方法后调用到native
方法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); } } } 复制代码
到这里,我们所写的XML
文件就真正绘制出来啦~ 😵 😵 😵
好了,第二个问题,源码证据,如上所述,最后是怎么渲染的,就是native
层的工作了,大家可以去了解下双缓冲机制,可以去看 深入浅出Android屏幕刷新原理,这篇写的不错。
写的有点长,有不妥的地方的话希望能指出,谢谢。