SeniorUI01_UI绘制流程分析(源码级分析)

[Android高级UI总结目录]

SeniorUI02_UI绘制流程分析运用(底部动画弹出显示框)

SeniorUI03_UI绘制流程分析二(View的绘制过程)

 

源码级分析:读源码一定要有线索:要带着自己的疑问来读源码(围绕目标找方法)

#1 从setContenView开始,了解view的加载过程

疑问:setContentView到底做了些什么,为什么调用后就可以显示出我们想要的布局页面?

 

一、setContentView -> getWindow().setContentView()的方法;

Window:显示顶层,抽象类,包括一些行为的封装,如setContentView、dispatch...,window的实现类是需要添加到windowManager里面来的,window提供标准UI背景、标题区域、默认key事件处理等。window仅有一个实现类-phoneWindow。

 

二、phoneWindow的setContentView 方法

首先对mContentParent判null,(mContentParent是ViewGroup容器,Activity要显示的内容要放到这里面来,要么是DecorView本身, 要么是DecorView的Child)DecorView是Window的根节点,它继承FrameLayout;

紧接着,对PhoneWindow的setContentView方法往下看,

当mContentParent不为null(并且!hasFeature

(FEATURE_CONTENT_TRANSITIONS)时移除mContentView的所有View;

执行一些动画(如Activity的转场动画);

然后,mLayoutInflater.inflate(layoutResID,mContentParent),将layout添加到mContentParent容器中来(inflater里面做了什么样的操作?为什么xml生成对应的View)

 

返回来,查看mContentParent(DecorView或其孩子)为空时的初始化操作:installDecor():

1)先new Decor,然后generateLayout(mDecor)并且返回值赋给mContentParent;

 

generateLayout方法执行内容:

首先,获取mWindowStyle(com.android.internal.R.styleable.Window),我们在xml中设置一些window的style属性,都会在这个方法中加载进来。

其次,查看window是否是windowFloating的(浮窗类型的,如dialog);

然后,style是否是requestFeature(FEATURE_NO_TITLE)方法,对Feature的一些状态位进行设置;紧接着,根据不同的Feature去加载不同的DecorView的xml布局:

如一个简单的xml布局:screen_simple.xml

xml中的id为content是显示真正布局的地方。然后,

mDecor.startChanging();

mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);加载DecorView的布局,同时

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

(ID_ANDROID_CONTENT就是xml中对应的id:content)

最终,将这个contentParent 返回,也就是说mContentParent最终代表的是DecorView中的一个id为content的FrameLayout。

 

Activity加载UI-类图关系和视图结构:

 

11

 

2)PhoneWindow是什么东西?window和它是什么关系?

每一个Activity都有一个显示UI的窗口,phoneWindow是window的唯一实现类。

 

3)DecorView是干什么用的?和我们的布局又有什么关系?

窗口底层的一个View;我们的布局其实是加载到DecorView下面的一个FrameLayout中去的。

 

4)RequestFeature为什么要在setContentView之前调用?

requestFeature后,加载windowDecor时候,要去拿requestFeature设置的Feature属性,根据拿到的属性,加载不同的Decor布局的xml;即在setContentView的时候,就要根据Feature属性,去拿不同的布局文件

 

 

#2 LayoutInflater到底怎么把xml添加到DecorView里面来?

include为什么不能作为xml资源布局的根节点?

merge为什么可以作为xml资源布局的根节点?

 

(1)从上面的mLayoutInflater.inflate(layoutResID,mContentParent)开始

查看inflate方法,它调用的是另外的inflate方法

11

(2)该方法去拿xml解析器:XmlResourceParser;再去调用重载的inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) 方法:

首先,获取xml的属性,然后将传进来的contentParent赋值给result

11

找到根节点,首先进行判断:是否是merge标签,(root 为空,并且attachToRoot为false时抛出异常:merge必须要添加到ViewGroup里面的)

当是常见节点时,首先会createViewFromTag生成一个根节点;再调用root(id为content的FrameLayout)的generateLayoutParams方法把对应的属性解析出来,设置到对应的child上来。

解析完根节点之后,继续解析里面的child

// Inflate all children under temp against its context.

rInflateChildren(parser, temp, attrs, true);

rInflateChildren还是调用的rInflate方法(为merge的时候也会调用),rInflate方法在解析merge时finisihInflate参数传false,非merge时传true,最终该参数控制是否执行parent.onFinishInflate方法。rInflate方法是一个遍历解析的过程,在解析的过程中发现如果include标签里面getDepth() == 0时抛出异常:

if(TAG_INCLUDE.equals(name)) {

if(parser.getDepth() ==0) {

throw newInflateException("<include /> cannot be the root element");

}

parseInclude(parser, context, parent, attrs);

}

如果解析到merge时,会抛出merge必须为根节点的异常

if(TAG_MERGE.equals(name)) {

throw newInflateException("<merge /> must be the root element");

}

这就回答了刚开始提出的两个问题

 

最后,他是ViewGroup是,再去遍历出来,添加到ViewGroup中去,(这就是深度优先遍历ViewTree的过程):

else{

finalView view = createViewFromTag(parent, name, context, attrs);

finalViewGroup viewGroup = (ViewGroup) parent;

finalViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);

rInflateChildren(parser, view, attrs,true);

viewGroup.addView(view, params);

}

 

layout的解析过程:

11

 

#3 小结:

每一个Activity都关联了一个Window对象,这个window是用来描述应用程序的窗体口;每一个应用程序的窗口又包含了一个DecorView,DecorView用来加载View的视图--xml布局。

 

上述是创建DecorView的过程,那么DecorView如何添加到Window上呢?

 

Extra:

AppCompatActivity在setContentView时,与在Activity中有什么差别?

其setContentView方法调用的是getDelegate().setContentView(代理:代理了一些封装兼容的类);

mDelegate = AppCompatDelegate.create(this, this)

 

AppCompatDelegate是一个抽象类,其实现类有很多:

 

private static AppCompatDelegate create(Context context, Window window,

AppCompatCallback callback) {

final int sdk = Build.VERSION.SDK_INT;

if (BuildCompat.isAtLeastN()) {

return new AppCompatDelegateImplN(context, window, callback);

} else if (sdk >= 23) {

return new AppCompatDelegateImplV23(context, window, callback);

} else if (sdk >= 14) {

return new AppCompatDelegateImplV14(context, window, callback);

} else if (sdk >= 11) {

return new AppCompatDelegateImplV11(context, window, callback);

} else {

return new AppCompatDelegateImplV9(context, window, callback);

}

}

AppCompatDelegateImplV9继承了其setContentView方法:

@Override

public void setContentView(View v) {

ensureSubDecor();

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

contentParent.removeAllViews();

contentParent.addView(v);

mOriginalWindowCallback.onContentChanged();

}

 

SubDecor与Decor本质上是一样的,只是做了兼容;

final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(

R.id.action_bar_activity_content);

 

final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);

if (windowContentView != null) {

// There might be Views already added to the Window's content view so we need to

// migrate them to our content view

while (windowContentView.getChildCount() > 0) {

final View child = windowContentView.getChildAt(0);

windowContentView.removeViewAt(0);

contentView.addView(child);

}

 

// Change our content FrameLayout to use the android.R.id.content id.

// Useful for fragments.

windowContentView.setId(View.NO_ID);

contentView.setId(android.R.id.content);

//做了替换,为了做兼容,封装了过程,对外提供了统一的id(content)

 

// The decorContent may have a foreground drawable set (windowContentOverlay).

// Remove this as we handle it ourselves

if (windowContentView instanceof FrameLayout) {

((FrameLayout) windowContentView).setForeground(null);

}

}

11

Activity的View布局结构_HierarchyView:

11

 

#4 DecorView如何添加到Window

(1)首先,要知道DecorView的添加,得先知道Activity的启动过程;Activity通过ActivityThread启动;

->handleLaunchActivity

-> performLaunchActivity:通过反射拿到Activity,调用Activity的attach方法;phonewindow在Activity的attach方法里面进行初始化的,初始化完成后调用callActivityOnCreate->activity.performCreate;Activity的生命周期执行过程、调用顺序都是在ActivityThread中依次进行的;

->handleResumeActivity

->获取window:r.window = r.acitivity.getWindow(),

->拿到Decor:View decor = r.window.getDecorView();拿到windowManger:ViewManger wm = a.getWindowManger();将Decor添加到windowManger中来(addView),windowManger是一个实现类,其实现类windowMangerImple的addView方法调用的又是WindowMangerGlobal的addView方法—> ViewRootImpl的setView方法,传入DecorView,调用了他的requestLayout方法,然后将DecorView

添加显示出来:

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

getHostVisibility(), mDisplay.getDisplayId(),

mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,

mAttachInfo.mOutsets, mInputChannel);

 

(2)然后,继续向下执行,又调用了一个关键方法:view.assignParent(this);

 

//view是DecorView,给view设置parent,ViewRootImpl将自己设置为DecorView的parent,这也就是为什么View再用requestLayout方法的时候最终会走到ViewRootImpl的requestLayout

 

DecorView添加至窗口的过程:

11

 

 

View的requestLayout方法会发起重新绘制?

View的requestLayout会一直调用parent的requestLayout,最终调用到DecorView的requestLayout方法,而DecorView的parent是ViewRootImpl(ViewRootImpl的setView中,通过调用 view.assignParent(this)设置),所有最终调用ViewRootImpl的requestLayout发起绘制流程:scheduleTraversals()->runnable->performTraversals:UI的遍历绘制

SeniorUI02_UI绘制流程分析运用(底部动画弹出显示框)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值