由setContentView()方法引起的思考

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

public class ViewActivity extends Activity {


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

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

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

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

《由setContentView()方法引起的思考》 image

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

Window

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

Window表示一个窗口的概念,在日常开发中直接接触Window的机会并不多,但是在某些特殊时候我们需要在桌面上显示一个类似悬浮窗的东西,那么这种效果就需要用到Window来实现。Window是一个抽象类,他的具体实现是PhoneWindow。创建一个Window是很简单的事,只需要通过WindowManager即可完成。WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。Android所有的视图都是通过Window来呈现的,不管是Activity、Dialog还是Toast,他们的视图实际上都是附加在Window上的,因此Window实际上是View的直接管理者。

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

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

@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是什么?

ViewGroup mContentParent;

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

 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()方法引起的思考》 image

真是千言万语都在这图中了,网上盗的图真是万能的,我的总结都在这个图中了。
我以前一直不懂为什么setContentView()叫setContentView而不叫setView呢,那是因为我们所创建的布局其实是Activity里面的PhoneWindow创建出来的DecorView里面的ContentView来的而已。一开始以为你是老大,现在才发现你是个小弟大概就是这种感觉吧。

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

我的掘金:
https://juejin.im/user/594e8e9a5188250d7b4cd875/posts

我的简书:
https://www.jianshu.com/u/b538ca57f640

    原文作者:19snow93
    原文地址: https://www.jianshu.com/p/3f135542e41c
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞