Android窗口机制之由setContentView引发的Window,PhoneWindow,DecorView源码理解
Android窗口机制之由setContentView引发的Window,PhoneWindow,DecorView源码理解
Activity启动流程源码分析
简单分析Binder工作机制
window.png由上一篇文章Activity启动流程源码分析,Activity启动完成最终调用了ActivityThread.handleLaunchActivity->ActivityThread.performLaunchActivity然后回调Activity.onCreate,所以,接着由setContentView引出的Window,PhoneWindow,DecorView源码理解,最近也看了好多相关的文章,记录自己的见解:
从上面的类图看:
Window是个抽象类,定义一些顶层窗口的行为策略,而Window的实现类是PhoneWindow;
DecorView其实是整个Activity窗口的装饰类吧,继承FrameLayout;
PhoneWindow会持有一个DecorView和mContentParent,mContentParent是DecorView的直接ViewGroup的contentView,即:用来显示的主要内容,不包括title之类的。
第一次画图,不是很规范,将就着看吧,我们从Activity的setContentView方法开始:
public void setContentView(@LayoutRes int layoutResID) { Window window = getWindow(); window.setContentView(layoutResID); initWindowDecorActionBar();}
Activity的setContentView方法,通过layoutResID设置Activity的主体内容,不包括标题栏状态栏,这个资源将被填充至activity的顶层view也就是DecorView的contentView中,而这个方法会直接调用Window.setContentView方法,而Window是个抽象类,所以我们看Window的实现类PhoneWindow的setContentView方法:
public void setContentView(int layoutResID) { if (mContentParent == null) { //创建DecorView,并内容布局赋值到PhoneWindow的mContentParent上 //也就是说mContentParent是DecorView的直接子View,然后通过将layoutResID的布局有填充的mContentParent上 installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } /** * 经过上面的步骤我们已经获取了DecorView中contectView,那么接下来就是就要将传进来的layoutResID填充contentView的布局了 */ //是否有过度动画 if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { //mContentParent是DecorView的中contentView,就是DecorView的孙子,将layoutResID填充到mContentParent中 mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { //回调通知表示完成界面加载 cb.onContentChanged(); } mContentParentExplicitlySet = true;}
整个流程就是先判断mContentParent是否为null,就调用installDecor方法去初始化DecorView,mContentParent是DecorView的内容ViewGroup,如果mContentParent填充完就把layoutResID调用inflate方法将layoutResID的布局填充至mContentParent,刚刚说了installDecor初始化DecorView,我们来看看installDecor方法。
private void installDecor() { mForceDecorInstall = false; if (mDecor == null) { //调用该方法创建new一个DecorView mDecor = generateDecor(-1); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } else { //DecorView持有PhoneWindow mDecor.setWindow(this); } //一开始DecorView未加载到mContentParent,所以此时mContentParent=null if (mContentParent == null) { //该方法将mDecorView添加到Window上绑定布局 /** * Decorview需要填充自己的直接布局有可能是LinearLayout或者FrameLayout, * 然后通过ID_ANDROID_CONTENT获取contentView,最后返回,将contentview复制给phonewindow的mContentParent */ mContentParent = generateLayout(mDecor);// 省略一万行代码................}
有installDecor方法内容较多,省略了一些,在installDecor方法中,如果mDecor 为null的话就会调用generateDecor方法创建一个DecorView并返回,上面说到mContentParent 是DecorView的内容ViewGroup,所以在如果mContentParent 为null的话将调用generateLayout方法生成mContentParent ,接下来就重点看看generateLayout方法,因为setContentView的大部分内容都在这里做了, Decorview需要填充自己的内容布局有可能是LinearLayout或者FrameLayout,最后返回,将contentview复制给phonewindow的mContentParent然后通过ID_ANDROID_CONTENT获取contentView,代码有点多。
protected ViewGroup generateLayout(DecorView decor) { //省略............很多代码就看DecorView的布局选择填充............ //填充窗口的装饰 // Inflate the window decor. int layoutResource; //根据features选择填充布局 int features = getLocalFeatures(); if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { layoutResource = R.layout.screen_swipe_dismiss; setCloseOnSwipeEnabled(true); } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { //悬浮FloatButton if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleIconsDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = R.layout.screen_title_icons; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); // System.out.println("Title Icons!"); } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 && (features & (1 << FEATURE_ACTION_BAR)) == 0) { // Special case for a window with only a progress bar (and title). // XXX Need to have a no-title version of embedded windows. layoutResource = R.layout.screen_progress; // System.out.println("Progress!"); } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { // Special case for a window with a custom title. // If the window is floating, we need a dialog layout if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogCustomTitleDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = R.layout.screen_custom_title; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { // If no other features and not embedded, only need a title. // If the window is floating, we need a dialog layout if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleDecorLayout, res, true); layoutResource = res.resourceId; } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) { layoutResource = a.getResourceId( R.styleable.Window_windowActionBarFullscreenDecorLayout, R.layout.screen_action_bar); } else { layoutResource = R.layout.screen_title; } // System.out.println("Title!"); } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) { layoutResource = R.layout.screen_simple_overlay_action_mode; } else { // Embedded, so no decoration is needed. layoutResource = R.layout.screen_simple; // System.out.println("Simple!"); } mDecor.startChanging(); //layoutResource Decorview需要填充自己的内容布局有可能是LinearLayout或者FrameLayout,然后将布局addView给自己 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); //获取真正的contenview ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { ProgrsetyessBar progress = getCircularProgressBar(false); if (progress != null) { progress.setIndeterminate(true); } } if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { registerSwipeCallbacks(contentParent); } // Remaining setup -- of background and title -- that only applies // to top-level windows. if (getContainer() == null) { final Drawable background; if (mBackgroundResource != 0) { background = getContext().getDrawable(mBackgroundResource); } else { background = mBackgroundDrawable; } mDecor.setWindowBackground(background); final Drawable frame; if (mFrameResource != 0) { frame = getContext().getDrawable(mFrameResource); } else { frame = null; } mDecor.setWindowFrame(frame); mDecor.setElevation(mElevation); mDecor.setClipToOutline(mClipToOutline); if (mTitle != null) { setTitle(mTitle); } if (mTitleColor == 0) { mTitleColor = mTextColor; } setTitleColor(mTitleColor); } mDecor.finishChanging(); return contentParent;}
别看代码多实际上是根据features选择填充布局,这就是为什么我们要在setContentCiew之前调用requestWindowFeature的原因,先看看DecorView的直接子ViewGroup的布局长什么样:
当然根ViewGroup在不仅仅是LinearLayout ,还有FrameLayout和其他的,这些没什么好说的,就这说填充DecorView的布局吧, Decorview需要填充自己的内容布局有可能是LinearLayout或者FrameLayout,然后调DecorViewon的ResourcesLoaded方法将选择的布局addView给DecorView自己,然后通过findViewById方法找到contentParent,并返回赋值给PhoneWindow的mContentParent,接着回到PhoneWindow的setContentView中将我们从Activity的setContentView传进来的layoutResID,填充到mContentParent,也就是DecorView的内容ViewGroup中,整个流程就完成了布局的填充。
最后看一下setContentView的时序图:
activity.setcontentview.png
小结
- Window是一个抽象类,提供了各种窗口操作的方法,比如设置背景标题ContentView等等;
- PhoneWindow则是Window的唯一实现类,它里面实现了Window各种各种方法,添加背景主题ContentView等方法,内部通过DecorView来添加顶级视图
每一个Activity上面都有一个Window,可以通过getWindow获取; - DecorView,顶级视图,继承与FramentLayout,setContentView则是添加在它里面的@id/content里
- setContentView里面创建了DecorView,根据Theme,Feature添加了对应的布局文件,当setContentView设置显示后会回调Activity的onContentChanged方法;
最后看看window的结构
view结构图.png更多相关文章
- Android完全关闭应用程序
- Android(安卓)MediaPlayer 字幕同步
- Android(安卓)8.1 来电默认全屏显示 如何修改
- Android(安卓)Studio finish()方法的使用与解决app点击“返回”,
- android第一天(小有成就,散分)
- Android(安卓)EventBus 架构设计
- My Android成长之路(二)——【JSON】
- [Android] 获取WebView的页面标题(Title)-----WebChromeClient.o
- Android(安卓)界面布局