android 源码 —— setContentView() 源码浏览

setContentView() 源码浏览

目录

setContentView() 源码浏览

PS:Window 的创建

总结


 


大家都知道,我们设置布局的时候,直接调用 setContentView(R.layout.xxx) 就可以了,那么,今天,我们就来看一下 setContentView() 中的源码,看看里面都做了些什么。

源码版本:android-27

从 setContentView(R.layout.xxx) 点击进入源码:

    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

我们继续点击 setContentView(layoutResID)

   /**
     * Should be called instead of {@link Activity#setContentView(int)}}
     */
    public abstract void setContentView(@LayoutRes int resId);

注意看上面的注释,提示我们直接点到 Activity 中的 setContentView(int) 中:

    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
    
    // 通过 getWindow() 获取 PhoneWindow
    public Window getWindow() {
        return mWindow;
    }

看过源码的人应该都知道, Window 是一个抽象类,而 Window 的实现类是 PhoneWindow,我们就需要在 PhoneWindow 中找到 setContentView  方法。

PS:Window 的创建

这里顺便看一下 Window 在哪里创建的:

我在 https://blog.csdn.net/xiayu54/article/details/104920102 中浏览过 activity 的启动流程,最后发送 LAUNCH_ACTIVITY 消息后进入了 handleLaunchActivity() 方法,而  handleLaunchActivity() 方法中又调用 performLaunchActivity() ,我们在这个方法中会调用 activity 生命周期的 onCreate 和 onStart ,创建 Window 的 activity.attach() 也是在这个方法中调用,而且还是在 onCreate 之前。我们来看一下这个 attach 方法:

    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {

             attachBaseContext(context);

            mFragments.attachHost(null /*parent*/);
            // 在这里创建了 mWindow,并且将 mWindow 实例化为一个 PhoneWindow 对象
            // 这也就解释了,我们为什么要去 PhoneWindow 中查找 setContentView() 方法
            mWindow = new PhoneWindow(this, window, activityConfigCallback);
            ......
     }
            

 我们继续看setContentView,接着我们进入 PhoneWindow 中的 setContentView() 方法:

    @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.
        // 第一次加载时,mContentParent  为 null
        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 {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

第一次加载进来时,mContentParent 为 null,这个 mContentParent 是什么呢?

它是一个 ViewGroup ,用来包裹我们的布局,当我们的布局没有添加到 mContentView 中时,MContentView 就为 null ,这时就会调用 installDecor() 方法进行初始化处理,将我们 setContentView 里添加的布局 View 加载到mContentParent里面去。

然后我们继续看看 installDecor() 方法:

 private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            // 第一次加载 mDecor 为null,初始化了一个 DecorView
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            // 设置mDecor为整个 Activity 窗口的根节点,这里可以看出窗口根节点为一个 DecorView
            mDecor.setIsRootNamespace(true);

            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            // 接下来,我们需要看这个,mContentParent 为null,调用 generateLayout 
            mContentParent = generateLayout(mDecor);
            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();

            // 下面的代码就是一些各种样式的处理
            ...
        }
    }

然后,我们看一下 generateLayout() 方法,简单来说,这个方法就是 帮我们找到相对应的布局,添加到 DecorView 中,再添加到 mContentParent 中:

protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        // 下面一连串的if就是根据你设置的 theme 来判断,加载相应的默认布局
        TypedArray a = getWindowStyle();

        if (false) {
            System.out.println("From style:");
            String s = "Attrs:";
            for (int i = 0; i < R.styleable.Window.length; i++) {
                s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
                        + a.getString(i);
            }
            System.out.println(s);
        }
        ......
      
        // 这里,如果我们在 theme 中设置了 window_windowNoTitle
        // 就会调用到,其他判断方法也一样
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }

        // 是否设置了全屏
        if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
            setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
        }
        ......
        
        final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE);
                final int targetSdk = context.getApplicationInfo().targetSdkVersion;
        final boolean targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB;
        final boolean targetPreIcs = targetSdk < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
        final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
        final boolean targetHcNeedsOptions = context.getResources().getBoolean(
                R.bool.target_honeycomb_needs_options_menu);
        final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE);
        // 根据当前的 SDK 版本,判断是否需要 menukey 菜单栏
        if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar))     
        {
            setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE);
        } else {
            setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_FALSE);
        }

        WindowManager.LayoutParams params = getAttributes();
        ......
        // 通过a中设置的属性,设置  params.softInputMode 软键盘的模式
        if (!hasSoftInputMode()) {
            params.softInputMode = a.getInt(
                    R.styleable.Window_windowSoftInputMode,
                    params.softInputMode);
        }

        ......
        // 在params.windowAnimations 中记录 WindowAnimationStyle,加载动画
        if (params.windowAnimations == 0) {
            params.windowAnimations = a.getResourceId(
                    R.styleable.Window_windowAnimationStyle, 0);
        }
        ......

        // Inflate the window decor.
        // 添加布局到 DecorView 中, DecorView 是继承 FrameLayout 的,它本身也是一个        
        // ViewGroup,而我们前面调用 generateDecor 创建它的时候,只是调用了new DecorView
        // 这个时候里面没有什么东西,下面的步骤 则是根据用户在 styles 中设置的 Theme 和在代码中          
        // requesetFeature() 设置的 Features 来创建相应的默认布局主题
        int layoutResource;
        int features = getLocalFeatures();

        // System.out.println("Features: 0x" + Integer.toHexString(features));
        // 一系列的 if 判断,加载对应的默认布局
        ......

        mDecor.startChanging();
        // //将layoutResource文件加载到DecorView中 
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        // 这里的 ID_ANDROID_CONTENT 就是的 ContentView 的根布局
        // Activity 中 setContentView(layoutResID) 中的 layoutResID 最终作为子View添加到    
        // ContentView 中
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        ......
        // 给DecView设置background、Drawable、color等一系列的参数
        ......
        mDecor.finishChanging();

        return contentParent;
    }

为什么说 ID_ANDROID_CONTENT 是ContentView 是根布局,我们可以点击去看:

    /**
     * The ID that the main layout in the XML layout file should have.
     */
    // 翻译一下就是:XML 布局文件中的主布局应该具有的 ID
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

可以看出我们是根据 layoutResource 通过 inflater 得到一个布局,然后把这个布局添加到 DecorView,根据这个得到的布局存在的 id,得到我们需要的 mContentParent。

@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.
        // 第一次加载时,mContentParent  为 null
        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 {
            // 最后调用 inflate
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

最后,我们通过 inflate 将我们写的布局,加载到 mContentParent 中。

总结

总的来说就是:Activity.setContentView(int resId) 中调用了 PhoneWindow.setContentView(int resId),在这个方法中,如果是第一次调用,那么需要初始化 DecorView 和初始化 mContentParent;否则,就清空 mContentParent 中的内容。然后将资源文件通过 LayoutInflater 的 inflate 方法来将其转换为 View,并将其加载到 mContentParent 中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值