关于android:Android中Activity的setContentView方法分析

39次阅读

共计 7491 个字符,预计需要花费 19 分钟才能阅读完成。

PS:本文系转载文章,浏览原文可读性会更好,文章开端有原文链接

ps:源码是基于 android api 27 来剖析的

这一篇咱们剖析 Activity 的 setContentView 办法到底做了什么事件,有的读者可能心存疑虑,文章有的中央看不懂怎么办,之前我看文章的时候也是有这样的疑虑,目前我采取的方法有 2 种:(1)看不懂的中央能够先跳过,看完本篇文章后再 google 一下看不懂的中央;(2)看不懂的中央先 google 一下,再持续把这篇文章往下看。好了,言归正传,咱们来看 Activity 的 setContentView 办法;

public void setContentView(@LayoutRes int layoutResID) {

    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();

}

这里的 getWindow 办法拿到的是 Window 对象,Window 是显示顶层窗口的外观,对一些 findViewById、事件散发等根底的行为进行封装,每一个 Window 都会被增加到 WindowManager 外面,Window 惟一的实现类是 PhoneWindow,所以 getWindow 办法拿到的是 Window 对象实质是 PhoneWindow 对象;如何晓得 Window 惟一的实现类是 PhoneWindow 呢?能够从 Android 中 View 事件的散发第一篇这里找到答案,咱们来看 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.
    if (mContentParent == null) {

        //1、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 {

        //2、mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Window.Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {

        //3、cb.onContentChanged();}

    //4、mContentParentExplicitlySet = true;
}

正文 3 示意回调 Activity 的 onContentChanged 办法;正文 4 示意曾经设置过布局,mContentParentExplicitlySet = false 的时候曾经调用过 PhoneWindow 的 requestFeature 办法了,mContentParentExplicitlySet = true 的时候不能够再调用 PhoneWindow 的 requestFeature 办法,咱们看 PhoneWindow 的 requestFeature 办法;

@Override
public boolean requestFeature(int featureId) {if (mContentParentExplicitlySet) {throw new AndroidRuntimeException("requestFeature() must be called before adding content");
    }
    ......
}

真的像下面所说的那样,正文 1 示意实例化 DecorView 并装置,咱们看一下 installDecor 办法具体实现;

private void installDecor() {

    mForceDecorInstall = false;
    if (mDecor == null) {
        
        //5、mDecor = generateDecor(-1);
        ......
    } else {mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        
        //6、mContentParent = generateLayout(mDecor);
        ......
    }

}

正文 5 中的 mDecor 是一个 DecorView 对象,也是咱们 Activity 最顶层的视图,它的父类是 FrameLayout,咱们来看看 PhoneWindow 的 generateDecor 办法是如何创立 DecorView 对象的;

protected DecorView generateDecor(int featureId) {

    ......
    return new DecorView(context, featureId, this, getAttributes());

}

间接 new 一个 DecorView 并将它返回,咱们回到 installDecor 办法正文 6 的代码,也就是 PhoneWindow 的 generateLayout 办法;

protected ViewGroup generateLayout(DecorView decor) {

    //7、TypedArray a = getWindowStyle();
    ......

    //8、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);
    }

    //9、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);
    }
    ......
    //10、mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false);
    ......
    //11、int layoutResource;

    //12、int features = getLocalFeatures();
    // System.out.println("Features: 0x" + Integer.toHexString(features));
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {......} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {......} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
        ......
    } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {......} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {......} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {layoutResource = R.layout.screen_simple_overlay_action_mode;} else {
        // Embedded, so no decoration is needed.
        //13、layoutResource = R.layout.screen_simple;
        // System.out.println("Simple!");
    }

    mDecor.startChanging();

    //14、mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    ......
    return contentParent;

}

正文 7 示意获取以后 Window 的 Style 属性;正文 8 示意是否是悬浮类型的 Window;正文 9 示意是否须要标题栏,前面省略了很多 a.getBoolean 的 if 语句,而 if 里的 requestFeature 办法调用其实是对 Window 的一些状态进行设置,requestFeature 办法必须在 Activity 的内容布局加载进去之前先被调用;正文 10 示意 Window 是否通明的,默认状况下不是通明的;正文 11 示意 DecorView 要加载 xml 文件的 id;正文 12 示意获取 Window 的 Feature 属性,前面有很多 features 的 if 判断,其实是依据 features 失去 DecorView 要加载相应的 xml 文件的 id;正文 13 只是 DecorView 要加载 xml 文件时的其中一种,咱们来看看 screen_simple.xml 的布局;

<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”

android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
          android:inflatedId="@+id/action_mode_bar"
          android:layout="@layout/action_mode_bar"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:theme="?attr/actionBarTheme" />
<FrameLayout
     android:id="@android:id/content"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:foregroundInsidePadding="false"
     android:foregroundGravity="fill_horizontal|top"
     android:foreground="?android:attr/windowContentOverlay" />

</LinearLayout>

咱们来看一下正文 14 的代码,也就是 DecorView 的 onResourcesLoaded 办法;

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {

    ......
    mDecorCaptionView = createDecorCaptionView(inflater);
    final View root = inflater.inflate(layoutResource, null);

    //15、if (mDecorCaptionView != null) {if (mDecorCaptionView.getParent() == null) {
            addView(mDecorCaptionView,
                    new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mDecorCaptionView.addView(root,
                new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));

        //16、} else {

        // Put it below the color views.
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    ......

}

正文 15 的代码示意如果有题目,而后再判断 mDecorCaptionView.getParent() 是否为空,如果为空就把 mDecorCaptionView 增加到 DecorView 里,如果不为空,那么 mDecorCaptionView 曾经增加过到 DecorView 里,最初间接将 root 增加到 mDecorCaptionView 里;正文 16 示意无标题,就将 root 间接增加到 DecorView 里。如何判断正文 15 的蓝色形容的文字是否是正确的呢?咱们能够看 创立 mDecorCaptionView 的 DecorView.createDecorCaptionView 办法;

private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) {

    DecorCaptionView decorCaptionView = null;
    for (int i = getChildCount() - 1; i >= 0 && decorCaptionView == null; i--) {View view = getChildAt(i);
        if (view instanceof DecorCaptionView) {
            // The decor was most likely saved from a relaunch - so reuse it.
            //17、decorCaptionView = (DecorCaptionView) view;
            removeViewAt(i);
        }
    }
    ......
    if (!mWindow.isFloating() && isApplication && StackId.hasWindowDecor(mStackId)) {
        // Dependent on the brightness of the used title we either use the
        // dark or the light button frame.
        if (decorCaptionView == null) {
            
            //18、decorCaptionView = inflateDecorCaptionView(inflater);
        }
        decorCaptionView.setPhoneWindow(mWindow, true /*showDecor*/);
    } else {decorCaptionView = null;}
    ......
    return decorCaptionView;

}

正文 17 中将 View 强制为 DecorCaptionView,此时的 View 正是 DecorView 的子 View,证实下面所说的 mDecorCaptionView 曾经增加过到 DecorView 里;看正文 18 的代码,也就是 DecorView 的 inflateDecorCaptionView 办法;

private DecorCaptionView inflateDecorCaptionView(LayoutInflater inflater) {

    final Context context = getContext();
    // We make a copy of the inflater, so it has the right context associated with it.
    inflater = inflater.from(context);
    final DecorCaptionView view = (DecorCaptionView) inflater.inflate(R.layout.decor_caption,
            null);
    setDecorCaptionShade(context, view);
    return view;

}

看 inflateDecorCaptionView 办法中的 DecorCaptionView 创立,传入一个参数 null,这里创立的 DecorCaptionView 对象的 getParent() 就为空了。

假如 DecorView 要加载的 xml 文件是 screen_simple.xml,那么咱们回过头来看看 DecorView 的 installDecor 办法中的正文 6 代码,发现 mContentParent 就是 screen_simple.xml 中 id 为 content 的 FrameLayout;咱们再回看 PhoneWindow 的 setContentView 办法中的正文 2 的代码,发现咱们 Activity 设置的内容布局文件是增加到了 screen_simple.xml 中 id 为 content 的 FrameLayout;对于增加 Activity 设置的内容布局进 DecorView 之后的残缺布局构造,我就不再画进去了,能够看看意识 Android 中的 ViewRootImpl 和 DecorView 这篇文章的开端,会有图。

正文完
 0