setContentView源码理解

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的布局长什么样:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
          android:inflatedId="@+id/action_mode_bar"
          android:layout="@layout/action_mode_bar"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:theme="?attr/actionBarTheme" />
<FrameLayout
     android:id="@android:id/content"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:foregroundInsidePadding="false"
     android:foregroundGravity="fill_horizontal|top"
     android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

当然根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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容