android activity setContentView()方法执行流程分析

/ android / 没有评论 / 1098浏览

用到的代码文件

frameworks/base/core/java/:
android/app/Activity.java
com/android/internal/policy/PhoneWindow.java

代码执行流程图

activity setContentView 查看原图

setContentView()方法执行流程

下面的代码基于android O(api 27)。

本文基于MTK(MediaTek)平台基线。

首先从Activity类中的setContentView()方法开始执行。

/**
 * Set the activity content from a layout resource.  
 * The resource will be inflated, adding all top-level 
 * views to the activity.
 *
 * @param layoutResID Resource ID to be inflated.
 */
public void setContentView(@LayoutRes int layoutResID) {
    //这里的window对象就是PhoneWindow
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

下面进入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
      * 由于当前activity是新创建的,所以mContentParent为null
      */
    log("mContentParent: "+mContentParent);
    if (mContentParent == null) {
        /*
         * 进入这里
         */
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {// -> false
        log("mContentParent.removeAllViews();");
        mContentParent.removeAllViews();
    }

    /*
     * mContentParent就是我们在DecorView中加载的根布局中的id/content
     * 比如screen_simple.xml中的    
     *   <FrameLayout android:id="@android:id/content" ../>
     */
    log("mContentParent: "+mContentParent.getClass().getName());
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {// -> false
        final Scene newScene = Scene.getSceneForLayout(mContentParent,
                layoutResID,getContext());
        transitionTo(newScene);
    } else {// -> get in
        /*
         * 初始化加载当前activity设置的布局
         */
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    /*
     * 如果当前有设置Callback,那么就回调
     * 如果要设置回调一定要在setContentView()方法调用之前设置
     */
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

下面进入PhoneWindow类中的installDecor()方法。

private void installDecor() {
    mForceDecorInstall = false;
    /*
     * 若一个activity是初始化新建的那么当前的DecorView就为null
     */
    if (mDecor == null) {// -> true
        /*
          * 新创建一个DecorView
          */
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(
                ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted 
                && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
        mDecor.setWindow(this);
    }
    log("mContentParent: "+mContentParent);
    if (mContentParent == null) {// -> true
        /*
          * 获取当前界面根布局中的mContentParent
          * mContentParent其实质就是根布局中的
          *         android:id="@android:id/content"
          *
          * 在R.id.title()方法中会根据不同的情况去加载不同的根布局
          */
        mContentParent = generateLayout(mDecor);

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

        /*
          * 这个decorContentParent是针对根布局的一种情况处理
          * 比如 screen_simple.xml
          * frameworks/base/core/res/res/layout/screen_simple.xml
          *
          * 在screen_simple.xml中没有
          *         R.id.decor_content_parent
          *         R.id.title
          */
        final DecorContentParent decorContentParent = 
                (DecorContentParent) mDecor.findViewById(
                R.id.decor_content_parent);
        if (decorContentParent != null) { // -> false

            ......(省略一大段代码)

        } else {
            mTitleView = findViewById(R.id.title);
            if (mTitleView != null) { // -> false
                if ((getLocalFeatures() 
                        & (1 << FEATURE_NO_TITLE)) != 0) {
                    final View titleContainer = 
                            findViewById(R.id.title_container);
                    if (titleContainer != null) {
                        titleContainer.setVisibility(View.GONE);
                    } else {
                        mTitleView.setVisibility(View.GONE);
                    }
                    mContentParent.setForeground(null);
                } else {
                    mTitleView.setText(mTitle);
                }
            }
        }

        if (mDecor.getBackground() == null 
                  && mBackgroundFallbackResource != 0) {
            mDecor.setBackgroundFallback(mBackgroundFallbackResource);
        }

        // Only inflate or create a new TransitionManager if
        // the caller hasn't already set a custom one.
        if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {// -> false

            ......(省略一大段代码)

        }
    }
}

下面进入PhoneWindow类中的generateDecor()方法。

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;
    /*
      * mUseDecorContext = true;
      * 在PhoneWindow的构造方法中被设置
      *
      * Only main activity windows use decor context, all the
      * other windows depend on whatever
      * context that was given to them.
      */
    if (mUseDecorContext) {// -> true
        Context applicationContext = 
                getContext().getApplicationContext();
        if (applicationContext == null) {// -> false
            context = getContext();
        } else {// -> get in
            context = new DecorContext(applicationContext,
                    getContext().getResources());
            if (mTheme != -1) {// -> true
                context.setTheme(mTheme);
            }
        }
    } else {// -> false
        context = getContext();
    }
    // context for decor:
    //      com.android.internal.policy.DecorContext
    log("context for decor: " 
            + context.getClass().getName());
    return new DecorView(context, featureId, 
            this, getAttributes());
}

下面进入PhoneWindow类中的generateLayout()方法。

protected ViewGroup generateLayout(DecorView decor) {
    /*
     * 解析style相关属性
     */
    // Apply data from current theme.
    TypedArray a = getWindowStyle();

    ......

    /*
     * 设置输入法参数
     */
    if (!hasSoftInputMode()) {
        log("set soft input mode");
        params.softInputMode = a.getInt(
                R.styleable.Window_windowSoftInputMode,
                params.softInputMode);
    }

    ......
    /*
     * 进行一系列的判断
     * 确定最终要使用的根布局
     */
    // 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_ACTION_MODE_OVERLAY)) != 0) {
        layoutResource = R.layout.screen_simple_overlay_action_mode;
    } else {
        log("use screen_simple layout");
        // Embedded, so no decoration is needed.
        layoutResource = R.layout.screen_simple;
        // System.out.println("Simple!");
    }

    mDecor.startChanging();
    /*
     * 解析根布局,并add到DecorView中去
     */
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    /*
     * 这个contentParent就是存放Activity中setContentView()方法
     * 设置的布局
     */
    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) {
        ProgressBar 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) {// -> true
        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;
}

至此在Activity中调用setContentView()方法的流程就执行完了。
主要是进行一些style方面参数的设置和布局的解析。

扩展

setContentView()方法在onCreate()中被调用,那么onCreate()方法什么时候被调用呢?
You can find answer in this article "android Activity启动流程(6)".