面试 100% 完全掌握:重新认识 View 的绘制流程

  1. 在接收系统事件的时候,调用scheduleTraversals 绘制view树

WindowMangerGlobal 最终调用的其实都是ViewRootImpl方法。ViewRootImpl在addView关联号DecorView后,还调用了setView方法进行初始化,接收垂直同步脉冲信息,代码如下:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,

int userId) {

mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);

// Schedule the first layout -before- adding to the window

// manager, to make sure we do the relayout before receiving

// any other events from the system.

requestLayout();

try{

res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,

getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,

mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,

mAttachInfo.mDisplayCutout, inputChannel,

}

}

在初始化的最后,通过WindowSession 调用addToDisplayAsUser添加了window到屏幕显示中。

三. 附加contentView到界面

===================

当我们启动activity,将我们写的xml布局文件显示在屏幕上,其中经历了那些过程呢?我们要在界面上展示内容,有如下几个步骤:

  1. 启动activity,在performLaunchActivity的时候创建Activity并且attach和调用onCreate方法

  2. 在attach的时候,创建PhoneWindow实例并持有mWindow引用

  3. 调用setContentView 以附加内容到windows中

  4. 通过确认decorView 以及 subDecorView存在,创建DecorViewsubDecorView

  5. 添加ContentViewdecorView树中的 R.id.content节点

  6. handleResumeActivity的时候,调用WindowManager.addView。关联ViewViewRootImpl,后续便可以绘制。

3.1 创建PhoneWindow


我们先看启动activity的方法,ActivityThread#performLaunchAcivity。 从该方法源码中可知,启动activity的方法流程如下:

  1. 创建Activity实例 ,在Instrumentation#newActivity完成

  2. 创建PhoneWindows附加到Activity。在Activity#attachAcitivity完成

  3. 调用Activity的onCreate生命周期,代码是Instrumentation#callActivityOnCreate

  4. onCreate中执行用户自定义的代码,比如setContentView

所以可知,在activity准备启动的时候,就已经完成了PhoneWindows实例的创建。而接下来就执行到了我们在Activity#onCreate中调用setContentView方法设置的自定义布局。

3.2 setContentView的本质


activity在启动之后,我们通常在onCreate调用setContentView中设置自己的布局文件。我们来具体看看setContentView做了什么。 setContentView方法本质其实是向android.R.id.content添加自己。 我们看AppCompatDelegateImpl#setContentView

@Override

public void setContentView(View v, ViewGroup.LayoutParams lp) {

///确认好 window decorView 以及 subDecorView

ensureSubDecor();

//向 android.R.id.content 添contentView

ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);

contentParent.removeAllViews();

contentParent.addView(v, lp);

mAppCompatWindowCallback.getWrapped().onContentChanged();

}

这一块代码关键在于向id为android.R.id.content的子view中添加contentView。 addView的过程自然会触发布局的重新渲染。 关键之处还是在于ensureSubDecor()方法中对于decoView 以及subDecorView的实例化创建工作。

3.3 确认window ,decorView 以及 subDecorView


先看看AppCompatDelegateImpl#ensureSubDecor()的主要实现:

private void ensureSubDecor() {

if (!mSubDecorInstalled) {

mSubDecor = createSubDecor();

}

}

private ViewGroup createSubDecor() {

// Now let’s make sure that the Window has installed its decor by retrieving it

ensureWindow();

mWindow.getDecorView();

final LayoutInflater inflater = LayoutInflater.from(mContext);

ViewGroup subDecor = null;

//省略其他样式subDecor布局的实例化

//包含 actionBar floatTitle ActionMode等样式

subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);

//省略状态栏适配代码

//省略actionBar布局替换代码

mWindow.setContentView(subDecor);

return subDecor;

}

代码很长,上面是经过省略之后的主要代码。可以看到代码逻辑很清晰:

  • 步骤一:确认window并attach(设置背景等操作)

  • 步骤二:获取DecorView,因为是第一次调用所以会installDecor(创建DecorView和Window#ContentLyout)

  • 步骤三:从xml中实例化出subDecor布局

  • 步骤四:设置内容布局: mWindow.setContentView(subDecor);

3.4 初始化 installDecor


关键两处代码是Window#installDecor 和 Window#setContentView。 先看一下Window#installDecor的代码:

private void installDecor() {

mForceDecorInstall = false;

mDecor = generateDecor(-1);

if (mContentParent == null) {

//R.id.content

mContentParent = generateLayout(mDecor);

final decorContentParent = (DecorContentParent) mDecor.findViewById(

R.id.decor_content_parent);

if (decorContentParent != null) {

//…省略一些decorContentParent的处理

} else {

mTitleView = findViewById(R.id.title);

final View titleContainer = findViewById(R.id.title_container);

///省略设置mTitle 设置标题容器显示隐藏

}

//设置decor背景

//省略activity各种动画的实例化

}

}

这一块除了一些标题。动画的初始化之外,最为关键的就是

  • 通过generateDecor()生成了DecorView

  • 以及通过generateLayout()获取了ContentLayout

  • 获取windowStyle的各种属性,并设置Features和WindowManager.LayoutParams.flags等

  • 如果window是顶层容器,获取背景资源等信息

  • 获取各种默认布局实例化( R.layout.screen_simple等),加到DecorView中。和AppComptDelegateImpl#createSubDecor创建的subDecor类似。

  • 获取com.android.internal.R.id.content 布局,并返回为ContentLayout

接下来再看Window#setContentView了:

@Override

public void setContentView(View view, ViewGroup.LayoutParams params) {

// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window

// decor, when theme attributes and the like are crystalized. Do not check the feature

// before this happens.

if (mContentParent == null) {

installDecor();

} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {

mContentParent.removeAllViews();

}

if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {

view.setLayoutParams(params);

final Scene newScene = new Scene(mContentParent, view);

transitionTo(newScene);

} else {

mContentParent.addView(view, params);

}

mContentParent.requestApplyInsets();

final Callback cb = getCallback();

if (cb != null && !isDestroyed()) {

cb.onContentChanged();

}

mContentParentExplicitlySet = true;

}

关键代码很简单,就是往mContentParent中添加view。而从上文可知,mContentParent就是andorid.R.id.content的布局。

3.5 小结:


分析得知,xml 编写layout布局到展示布局在界面上,经历了这么个流程:

  1. 启动activity

  2. 创建PhoneWindow

  3. 设置布局setContentView

  4. 确认subDecorView的初始化

  5. 初始化生成DecorView

  6. Window中 创建DecorView

  7. Window中 创建样例到代码布局作为DecorView的子布局(比如R.layout.smple)

  8. 返回 com.android.internal.R.id.content 作为ContentPrent

  9. Window中 处理DecorContentParent 布局,或者处理标题等内容

  10. 实例化subDecorView,如R.layout.abc_screen_simple

  11. 设置 subDecorView到Window的ContentPrent

  12. 添加实例化的Layout 到android.R.id.content

  13. addView的时候调用 requestLayout(); invalidate(true);

  14. requestLayout遍历View树到DecorView,调用ViewRootImpl#requestLayoutDuringLayout

最后

下面是辛苦给大家整理的学习路线


《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
reen_simple

  1. 设置 subDecorView到Window的ContentPrent

  2. 添加实例化的Layout 到android.R.id.content

  3. addView的时候调用 requestLayout(); invalidate(true);

  4. requestLayout遍历View树到DecorView,调用ViewRootImpl#requestLayoutDuringLayout

最后

下面是辛苦给大家整理的学习路线

[外链图片转存中…(img-vc7OY34M-1715426456021)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值