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

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

Activity启动流程源码分析

简单分析Binder工作机制

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

Android窗口机制之由setContentView引发的Window,PhoneWindow,DecorView源码理解_第1张图片
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的时序图:


Android窗口机制之由setContentView引发的Window,PhoneWindow,DecorView源码理解_第2张图片
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的结构

Android窗口机制之由setContentView引发的Window,PhoneWindow,DecorView源码理解_第3张图片
view结构图.png

你可能感兴趣的:(Android窗口机制之由setContentView引发的Window,PhoneWindow,DecorView源码理解)