由setContentView()方法引发的思考

Android的setContentView()方法咱们平时用不少,可是有多少人会点进setContentView()方法里面看看它的源码到底是何方神圣呢,今天我就来看看从这个方法里面究竟涉及到多少未知的知识。android

public class ViewActivity extends Activity {


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view);
    }
}
复制代码

怀着好奇心我点下了setContentView()这个方法去寻根索源:bash

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
复制代码

getWindow().setContentView(layoutResID)这是什么鬼,而后点进去看看:app

个人天,居然看到setContentView()是一个叫Window类的抽象方法,Window相信每一个人都听过,可是对于Android的Window相信不是全部人都了解,我也是同样,而后我带着问题翻阅了书本。

Windowide

摘自来自《Android开发艺术探索》的解释:布局

Window表示一个窗口的概念,在平常开发中直接接触Window的机会并很少,可是在某些特殊时候咱们须要在桌面上显示一个相似悬浮窗的东西,那么这种效果就须要用到Window来实现。Window是一个抽象类,他的具体实现是PhoneWindow。建立一个Window是很简单的事,只须要经过WindowManager便可完成。WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。Android全部的视图都是经过Window来呈现的,不论是Activity、Dialog仍是Toast,他们的视图实际上都是附加在Window上的,所以Window其实是View的直接管理者。post

IPC:Inter-Process Communication的缩写,含义为进程间通讯或者跨进程通讯,是指两个进程之间进行数据交换的过程。ui

看了一大轮的文字概念,我就想睡觉了。可是看了那么久,总算几个关键词PhoneWindowWindowManagerWindowManagerService。上面讲到PhoneWindow是Window的实现类,那么咱们先去看看PhoneWindow吧。this

@Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        ...
    }
复制代码

在PhoneWindow确实找到了setContentView()方法的具体实现。 mContentParent是什么?spa

ViewGroup mContentParent;
复制代码

暂时还不知道它是什么,那咱们当它是null吧,进入installDecor()方法看看:code

private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            ...
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();

            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);

            if (decorContentParent != null) {
                mDecorContentParent = decorContentParent;
                mDecorContentParent.setWindowCallback(getCallback());
                if (mDecorContentParent.getTitle() == null) {
                    mDecorContentParent.setWindowTitle(mTitle);
                }

                ...
                下面省略了一大堆UiOptions,setIcon,Transition的方法
            } else {
                ...
            }

            
    }
复制代码

mDecor是什么?

private DecorView mDecor;
复制代码

DecorView是什么? 书本是这样写的: ViewRoot对应于ViewRootImpl类,它是链接WindowManagerDecorView的纽带,View的三大流程(onMeasure(),onLayout(),onDraw())均是经过ViewRoot来完成的。在ActivityThread中,当Activity对象被建立完毕后,会将DecorView添加到Window中,同时会建立ViewRootImpl对象,并将ViewRootImpl对象和DecorView创建关联

我真的醉了,越翻越多本身不懂的概念出来:ViewRoot,ViewRootImpl,如今暂且作个笔记吧,先无论了。先看看咱们找到的线索:

当Activity对象被建立完毕后,会将DecorView添加到Window中.

这就是咱们要找的东西。DecorView原来是这样用的。

回到installDecor()中,当mDecor为空时,调用generateDecor(-1)方法:

protected DecorView generateDecor(int featureId) {
        ...
        return new DecorView(context, featureId, this, getAttributes());
    }
复制代码

这里建立了一个DecorView了,咱们发现DecorView实际上是一个FrameLayout,再回到installDecor(),这时候咱们知道mContentParent仍然为null,那么进入generateLayout(mDecor)方法:

protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        //根据当前设置的主题来加载默认布局
        TypedArray a = getWindowStyle();
        ...
        设置各类各样的属性
        ...
        
        //若是你在theme中设置了window_windowNoTitle,则这里会调用到,其余方法同理,
        //这里是根据你在theme中的设置去设置的
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            
            requestFeature(FEATURE_ACTION_BAR);
        }
       
        //是否有设置全屏
        if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
            setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
        }

        ...

        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } //省略其余判断方法
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }

        mDecor.startChanging();
        //选择对应布局建立添加到DecorView中
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        ...
        return contentParent;
    }
复制代码

首先generateLayout会根据当前用户设置的主题去设置对应的Feature,接着,根据对应的Feature来选择加载对应的布局文件,(Window.FEATURE_NO_TITLE)接下来经过getLocalFeatures来获取你设置的feature,进而选择加载对应的布局,这也就是为何咱们要在setContentView以前调用requesetFeature的缘由。

public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
复制代码

咱们还能看到contentParent实际上是一个叫com.android.internal.R.id.content的布局,最后添加到DecorView上。generateLayout()方法最后返回的就是contentParent。好了,installDecor()方法走完了,建立了DecorView和在上面设置了一大堆属性,并建立带来了一个mContentParent,再回到PhoneWindow的setContentView()中

@Override
    public void setContentView(int layoutResID) {
        
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            //把mContentParent加载到mLayoutInflater中,
            //而mLayoutInflater在上面generateLayout(DecorView decor)方法中
            //早已加载到DecorView中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            //回调通知表示完成界面改变
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }
复制代码

此时已经建立完DecorView而且获取到mContentParent,接着就是将你setContentView的内容添加到mContentParent中,也就是

mLayoutInflater.inflate(layoutResID, mContentParent);
 或者
 mContentParent.addView(view, params);
复制代码

来到这里该总结一下了:

真是千言万语都在这图中了,网上盗的图真是万能的,个人总结都在这个图中了。 我之前一直不懂为何setContentView()叫setContentView而不叫setView呢,那是由于咱们所建立的布局实际上是Activity里面的PhoneWindow建立出来的DecorView里面的ContentView来的而已。一开始觉得你是老大,如今才发现你是个小弟大概就是这种感受吧。

等等,虽然知道setContentView()方法是怎么来的,可是在看它的源码中,咱们还发现了好几个疑问:WindowManagerViewRootViewRootImplPhoneWindowWindowManagerService。他们几个的关系又是怎么个错综复杂呢?拿着这些线索,咱们下一篇文章再来探个究竟吧。

个人掘金: juejin.im/user/594e8e…

个人简书: www.jianshu.com/u/b538ca57f…

相关文章
相关标签/搜索