Android窗口机制之由setContentView引发的Window,PhoneWindow,DecorView源码理解

Activity启动流程源码分析

简单分析Binder工作机制

由上一篇文章Activity启动流程源码分析,Activity启动完成最终调用了ActivityThread.handleLaunchActivity->ActivityThread.performLaunchActivity然后回调Activity.onCreate,所以,接着由setContentView引出的Window,PhoneWindow,DecorView源码理解,最近也看了好多相关的文章,记录自己的见解:

window.png

从上面的类图看:

  • 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

更多相关文章

  1. Android完全关闭应用程序
  2. Android(安卓)MediaPlayer 字幕同步
  3. Android(安卓)8.1 来电默认全屏显示 如何修改
  4. Android(安卓)Studio finish()方法的使用与解决app点击“返回”,
  5. android第一天(小有成就,散分)
  6. Android(安卓)EventBus 架构设计
  7. My Android成长之路(二)——【JSON】
  8. [Android] 获取WebView的页面标题(Title)-----WebChromeClient.o
  9. Android(安卓)界面布局

随机推荐

  1. 安卓模拟器Android(安卓)studio中VT-x is
  2. Android(安卓)Camera框架
  3. Android使用百度地图SDK
  4. Android(安卓)- 如何判断当前线程是否是
  5. Android消息传递机制浅析
  6. android AsyncTask相关的一些面试题目
  7. android studio - swiperefreshlayout注
  8. Android(安卓)error: "/usr/bin/ld: cann
  9. Android(安卓)串口通讯集成
  10. ColorFilter初探一