之前零散写了一些,这篇算是总集篇。大概涉及到
- setContentView()
- SubDecor、Decorview、ContentView
- PhoneWindow、WindowManager
- ViewRootImpl、Choreographer
- Toast.show()、Dialog.show()
- View.invalidate()、View.requestLayout()
- View.post()
新写一个Activity会调用到setContentView()
方法设置布局,那就以此作为切入点。
Activity.setContentView()
->AppCompatDelegateImpl.setContentView()
public void setContentView(int resId) {
//创建SubDecor
ensureSubDecor();
//id为content的FrameLayout
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
//移除content上所有view
contentParent.removeAllViews();
//加载xml布局到content
LayoutInflater.from(mContext).inflate(resId, contentParent);
//回调
mOriginalWindowCallback.onContentChanged();
}
创建SubDecor这个ViewGroup(SubDecor包含ActionBar、Toolbar、ContentView等),并将SubDecor添加到window的Decorview,Decorview就是顶层View了;找到SubDecor上id为content的FrameLayout,加载xml布局反射创建View树并添加到content。
ensureSubDecor()
内部调用createSubDecor()
,createSubDecor()中调用mWindow.setContentView(subDecor)将subDecor添加到Window的Decorview。
Window大家都知道,其实现类是PhoneWindow
,在Activity.attach()方法中初始化。而Activity.attach()在ActivityThread的performLaunchActivity()方法中调用,performLaunchActivity()中创建了Activity实例,加载资源,调用activity.attach()然后通过Instrumentation类回调onCreate()生命周期。当然这篇也不是分析Activity启动流程之类的文章,只需关注PhoneWindow。
Activity.attach()
final void attach(...){
mWindow = new PhoneWindow(this, window, activityConfigCallback);
}
PhoneWindow.setContentView()
@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
}
mContentParent.addView(view, params);
}
//mContentParent
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT)
//Window.findViewById(int id)
public <T extends View> T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
创建DecorView,将SubDecor添加到DecorView。installDecor()方法中可以看到mContentParent是DecorView中id为content的ViewGroup,那这玩意是平常我们所说的ContentView吗?其实不是,刚看到这里我也有点懵逼。如果这玩意是ContentView,那SubDecor已经添加进了ContentView,View树再add进来岂不是会覆盖SubDecor中的Toolbar、Actionbar?实际上ContentView是SubDecor的子View,继续看下去。
AppCompatDelegateImpl.createSubDecor()
随便挑一个创建SubDecor的分支
subDecor = (ViewGroup) LayoutInflater.from(themedContext)
.inflate(R.layout.abc_screen_toolbar, null);
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
while (windowContentView.getChildCount() > 0) {
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
windowContentView.setId(View.NO_ID);
contentView.setId(android.R.id.content);
精简一下很明显了。DecorView中id为content的windowContentView重置为NO_ID
,SubDecor中id为action_bar_activity_content的contentView设置为android.R.id.content
;并将原DecorView中windowContentView下的子View剪切到SubDecor的ContentView。
abc_screen_toolbar.xml
<androidx.appcompat.widget.ActionBarOverlayLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/decor_content_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<include layout="@layout/abc_screen_content_include"/>
...
</androidx.appcompat.widget.ActionBarOverlayLayout>
abc_screen_content_include.xml
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.appcompat.widget.ContentFrameLayout
android:id="@id/action_bar_activity_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</merge>
所以SubDecor是ActionBarOverlayLayout,ContentView是SubDecor中的ContentFrameLayout
setContentView()看完了,View树何时开始绘制真正显示出来呢?老生常谈的是onResume()之后,那就从onResume()生命周期的调用处开始看。
ActivityThread.handleResumeActivity()
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
//onResume回调
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
wm.addView(decor, l);
r.activity.makeVisible();
}
Activity.makeVisible()
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
熟悉的WindowManager.addView()。WindowManager的实现类是WindowManagerImpl,其通过三个接口方法addView()、updateViewLayout()、removeView()来管理View,也就是说Window是View的管理者。
WindowManager.addView()
调用到WindowManagerGlobal.addView()
ViewRootImpl root;
root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView, userId);
ViewRootImpl.setView()
public void setView(...){
......
requestLayout();
}
public void requestLayout() {
......
scheduleTraversals();
}
void scheduleTraversals() {
......
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
mChoreographer.postCallback()
也就是执行mTraversalRunnable这个Runnable
ViewRootImpl.mTraversalRunnable
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
ViewRootImpl.doTraversal()
void doTraversal() {
......
performTraversals();
}
private void performTraversals() {
//绘制流程
performMeasure();
performLayout();
performDraw();
}
WindowManager.addView()
最终走到ViewRootImpl.performTraversals()
方法,内部依次调用performMeasure()、performLayout()、performDraw(),从DecorView开始遍历View树对应调用View.measure()、View.layout()、View.draw()直到View树完成绘制流程。可以下结论,ViewRootImpl接管了绘制流程。
这里还有个问题,mTraversalRunnable何时执行run()方法呢,下面回看Choreographer.postCallback()
,经过一些列调用,调用到内部类FrameDisplayEventReceiver.scheduleVsync()
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);
}
}
nativeScheduleVsync(mReceiverPtr)调用native方法发出VSync信号,回调到FrameDisplayEventReceiver.onVsync()
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
......
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
......
doFrame(mTimestampNanos, mFrame);
}
void doFrame(long frameTimeNanos, int frame) {
......
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
}
doFrame()就是执行mTraversalRunnable走绘制流程了,也就是说VSync垂直同步信号到来时会执行绘制流程刷新UI,按60帧算每16ms系统都会发出VSYNC信号。
简单总结下View树如何显示:在Activity的onResume()生命周期之后通过WindowManager.addView(),初始化ViewRootImpl,等待VSync垂直同步信号到来,调用到ViewRootImpl.performTraversals()开启View树绘制流程。
Activity是这个流程,那其它的View像Toast、Dialog呢?当然也是一样,Toast.show()、Dialog.show()都会调用到WindowManager.addView(),至于之后的流程就完全和上述一致了。
Toast稍微麻烦点,Toast.show()通过binder机制获取NMS代理对象,调用到NMS.enqueueToast()将Toast内部类TN做为回调对象传入。NMS经过一系列调用最终回调到TN.show()显示Toast,并在显示Duration时长后回调TN.hide()取消Toast。
TN.show()
final Handler mHandler;
public void show(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}
mHandler = new Handler(looper, null) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW: {
IBinder token = (IBinder) msg.obj;
handleShow(token);
break;
}
......
}
}
};
}
public void handleShow(IBinder windowToken) {
......
mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
mHorizontalMargin, mVerticalMargin,
new CallbackBinder(getCallbacks(), mHandler));
}
}
ToastPresenter.show()
public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,
......
try {
//重点
mWindowManager.addView(mView, mParams);
} catch (WindowManager.BadTokenException e) {
Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);
return;
}
}
Dialog.show()
public void show() {
......
//DecorView
mDecor = mWindow.getDecorView();
//重点
mWindowManager.addView(mDecor, l);
}
依然是WindowManager.addView()。下面看改变View的属性时调用的View.invalidate()
、改变View的大小时调用的View.requestLayout()
。
View.invalidate()
public void invalidate() {
invalidate(true);
}
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
final ViewParent p = mParent;
p.invalidateChild(this, damage);
}
/**
* The parent this view is attached to.
* {@hide}
*
* @see #getParent()
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
protected ViewParent mParent;
ViewParent也就是父View,往上调用直到ViewRootImpl.invalidateChildInParent()
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
......
invalidateRectOnScreen(dirty);
}
private void invalidateRectOnScreen(Rect dirty) {
......
scheduleTraversals();
}
void scheduleTraversals() {
......
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
流程又走到了熟悉的地方,Choreographer.postCallback(),传入mTraversalRunnable,等待Vsync信号回调mTraversalRunnable.run()走绘制流程。
View.requestLayout()
public void requestLayout() {
......
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
}
ViewRootImpl.requestLayout()
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
仍然是熟悉的流程,这里看个大概,实际调用链内部代码还是有些复杂的;需知刷新UI最终都要走ViewRootImpl.performTraversals()
。
View.post()
由前面流程分析可知View树在onResume()之后才开始走绘制流程,那我们经常使用的View.post()在onCreate()中如何获取到View的宽高属性呢?其实思路很简单,把post传入的Runnable保存下来,等待View树绘制完毕再回调run()方法,具体调用链就不再跟了。