Activity中布局文件加载流程

本文深入剖析Android 8.1中Activity的View绘制流程,从setContentView开始,详细讲解如何加载布局资源layoutResID。内容涉及Activity、PhoneWindow、DecorView及其相互关系,阐述DecorView作为Activity根View的角色,以及如何根据Theme加载布局。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

本篇文章从源码(基于Android 8.1)角度,由Activity中的setContentView开始切入分析Android中Activity的View的绘制流程,侧重于layoutResID加载流程的分析,对
在进行实际的分析之前,我们先来看下面这张图:
这里写图片描述

一、基本关系

在这里先把Activity作为Android手机与用户交互视图中顶级的组件。
Window作为Activity中的一个具体对窗口的管理类,但是Window是一个抽象类,它提供了绘制窗口的一组通用API,真正执行各项操作的是它的子类PhoneWindow实体类。PhoneWindow中再次将用户可视的View的绘制等工作分离出,并且把绘制工作交给它的一个私有成员类DecorView;所以就可视View而言DecoView是Activity的根View;
DecorView继承自FrameLayout,在内部根据用户设置的Activity主题对FrameLayout进行修饰,为用户提供给定Theme下的布局样式。如上图所示,一般情况下,DecorView中包含一个用于显示Activity标题的TitleView和一个用于显示内容的ContentView。

一、开工
1.Activity.setContentView(int layoutResID)

同样是从Activity的setContentView()出发;
在Activity中setContentView()源码:

  public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);//获取Window对象,并且将layoutResID交给Window进一步处理;
        initWindowDecorActionBar();//初始化DecorView 和ActionBar
    }
    
	public Window getWindow() {
        return mWindow;
    }

 final void attach(//省略若干参数) {
       //这里省略若干行代码……
        mWindow = new PhoneWindow(this, window, activityConfigCallback);//mWindow 实例
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
       //这里省略若干行代码……
    }

根据上述源码中的得到:在Activity中主要干了两件事: 将layoutResID传递给交给Window,并且交给Window中的setContentView(layoutResID)继续处理,然后初始化DecorView 和ActionBar;

跟踪代码我们发现Window一个abstract类,在Activity中的attach()中new了一个PhoneWindow对象;所以PhoneWindow才只是getWindow().setContentView(layoutResID)的具体是执行者;

2.PhoneWindow.setContentView()

继续跟踪代码;
PhoneWindow.setContentView源码;

public void setContentView(int layoutResID) {
        if (this.mContentParent == null) {
           //初识化参数: 1 创建DecorView(FrameLayout)对象。; 2 初识化mContentParent;下面贴出源码
            this.installDecor();
        } else if (!this.hasFeature(12)) {
        //如果内容已经加载过(不是第一次加载),并且不需要动画,removeAllViews()
            this.mContentParent.removeAllViews();
        }
        if (this.hasFeature(12)) {
        //  5.0专场动画
            Scene newScene = Scene.getSceneForLayout(this.mContentParent, layoutResID, this.getContext());
            this.transitionTo(newScene);
        } else {
		      //加载布局的核心方法;将Activity的ContenView的xml文件inflate入父布局mContentParent;
            this.mLayoutInflater.inflate(layoutResID, this.mContentParent);
        }
        this.mContentParent.requestApplyInsets();
        android.view.Window.Callback cb = this.getCallback();
        if (cb != null && !this.isDestroyed()) {
            cb.onContentChanged();
        }

        this.mContentParentExplicitlySet = true;
    }

在PhoneWindow.setContentView中首先是通过installDecor()方法初识化了DecorView,然后将activity中xml的布局文件作为mContentParent的子VIew添加都DecorView中;其中mContentParent就是在installDecor()中一同初始化的DecorView根布局;

进一步跟踪installDecor()代码;

  private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            //实例化DecorView
            mDecor = generateDecor(-1);
            //设置mDecor中的子View的聚焦性,该方法决定了mDecor与其中包含的子View之间关于焦点获取的关系
            //FOCUS_AFTER_DESCENDANTS表示只有当mDecor的子View都不愿意获取焦点时,才让mDecor获取焦点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) {
            //DecorView的父布局xml文件
            mContentParent = generateLayout(mDecor);
            …………
            }
             …………
            …………
}

在installDecor()中显然是在generateDecor()函数中先实例化DecorView对象,然后调用generateDecor()根据开发者设置的Theme选择对应的xml,然后通知DecorView加载xml,生成得出DecorView根布局即实例化了PhoneWindow.setContenView中的mContentParent对象,然后回到setContenView中将Activity中传入的xml布局作为字View添加到DecorView布局的ContentView布局中;

3.PhoneWindow.generateDecor(DecorView decor);
  protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext().getResources());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }
4.PhoneWindow.generateLayout(DecorView decor)
  protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
		// 应用当前theme的数据,下面一连串的if就是根据你设置的theme来判断当前加载的形式的
        TypedArray a = getWindowStyle();
        mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
        //悬浮悬浮窗属性
        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
                //是否为悬浮
                 if (mIsFloating) {
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(0, flagsToUpdate);
        } else {
           //非悬浮 setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
        }
        
		......
        ......
        //省略多行读style属性对Window设置的
        //包括windowNoTitle、windowFullscreen、windowTranslucentStatus
         ......

		//根据targetSdkVersion来判断是否需要加载菜单栏
        final Context context = getContext();
        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);
		......
		//软键盘的参数
        ......
        //加载动画
        ......
      /**
         * 
         * 根据开者在styles中设置的Theme和在代码中requesetFeature()设置的 Features 选择对应的xml布局文件layoutResource
         * 将layoutResource布局文件inflate到DecorView中
         */
        // Inflate the window decor.
        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;
            setCloseOnSwipeEnabled(true);
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            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中 
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//这里的ID_ANDROID_CONTENT就是上图中的ContentView的根布局;
        //Activity 中setContentView(layoutResID)中的layoutResID最终作为子View添加到ContentView中的;
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
  
        ......
        //给DecView设置背景、Drawable、color等一系列的参数
         ......
        mDecor.finishChanging();
        return contentParent;
    }

最后记录一下PhoneWindow源码的路径:AS的SDK路径下:\sources\android-27\com\android\internal\policy;
DecorView中xml文件路径,AS的SDK路径下:\platforms\android-27\data\res\layout

Android 从setContentView谈Activity界面的加载过程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值