1. AppCompatDelegate 的 setContentView()

分析 Android 中的 View,我们先从进入应用的看到的的一个 View 入手,第一个 View 就是 通过 setContentView() 这个方法进行加载的。我们来看 setContentView() 的源码:

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

AppCompatActivity 中的 setContentView() 又调用了 AppCompatDelegate 中的 setContentView() 方法,那 AppCompatDelegate 是做什么的呢?

AppCompat 出现在 v7 包,它的作用是让 API 等级在 7 之上的设备也能使用 ActionBar,在 v7:21 之后,AppCompat 可以让 API 在 7 之上的设备使用 MD、ToolBar 等效果。之前的 ActionBarActivity 也被取代为 AppCompatActivity。但 AppCompatActivity 的内部回调是由 AppCompatDelegate 来实现的。

AppCompatDelegate 是一个抽象类,它的实现类是 AppCompatDelegateImpl,现在看 AppCompatDelegateImpl 中的 setContentView() 方法:

public void setContentView(int resId) {    // 创建 DecorView,DecorView 是视图中的顶级 View    this.ensureSubDecor();    // 获取 DecorView 中的 content 部分    ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);    contentParent.removeAllViews();    // 将我们编写的界面填充到 content 中    LayoutInflater.from(this.mContext).inflate(resId, contentParent);    this.mOriginalWindowCallback.onContentChanged();}

2. DecorView

在 AppCompatDelegateImpl 的 setContentView() 中,通过 ensureSubDecor() 方法为视图创建 DecorView,

private void ensureSubDecor() {    if (!this.mSubDecorInstalled) {        // DecorView 不存在,调用 createSubDecor() 创建 DecorView        this.mSubDecor = this.createSubDecor();        CharSequence title = this.getTitle();        if (!TextUtils.isEmpty(title)) {            if (this.mDecorContentParent != null) {                this.mDecorContentParent.setWindowTitle(title);            } else if (this.peekSupportActionBar() != null) {                this.peekSupportActionBar().setWindowTitle(title);            } else if (this.mTitleView != null) {                this.mTitleView.setText(title);            }        }        this.applyFixedSizeWindow();        this.onSubDecorInstalled(this.mSubDecor);        this.mSubDecorInstalled = true;        AppCompatDelegateImpl.PanelFeatureState st = this.getPanelState(0, false);        if (!this.mIsDestroyed && (st == null || st.menu == null)) {            this.invalidatePanelMenu(108);        }    }}private ViewGroup createSubDecor() {    // 获取设置的主题属性    TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);    // 如果使用的主题不是 Theme.AppCompat,或者没又继承自 Theme.AppCompat,抛出异常。    if (!a.hasValue(styleable.AppCompatTheme_windowActionBar)) {        a.recycle();        throw new IllegalStateException("You need to use a Theme.AppCompat theme (or descendant) with this activity.");    } else {        // 根据主题的属性进行设置        if (a.getBoolean(styleable.AppCompatTheme_windowNoTitle, false)) {            // 在 requestWindowFeature() 方法中            // 设置 this.mWindowNoTitle = true            this.requestWindowFeature(1);        } else if (a.getBoolean(styleable.AppCompatTheme_windowActionBar, false)) {            this.requestWindowFeature(108);        }        if (a.getBoolean(styleable.AppCompatTheme_windowActionBarOverlay, false)) {            this.requestWindowFeature(109);        }        if (a.getBoolean(styleable.AppCompatTheme_windowActionModeOverlay, false)) {            this.requestWindowFeature(10);        }            // 记录是否为浮动的主题        this.mIsFloating = a.getBoolean(styleable.AppCompatTheme_android_windowIsFloating, false);        a.recycle();        this.mWindow.getDecorView();        LayoutInflater inflater = LayoutInflater.from(this.mContext);        ViewGroup subDecor = null;                // 根据不同的设置,给 subDecor 填充内容        if (!this.mWindowNoTitle) {            if (this.mIsFloating) {                // Dialog 的主题                subDecor = (ViewGroup)inflater.inflate(layout.abc_dialog_title_material, (ViewGroup)null);                this.mHasActionBar = this.mOverlayActionBar = false;            } else if (this.mHasActionBar) {                // 添加 ActionBar                TypedValue outValue = new TypedValue();                this.mContext.getTheme().resolveAttribute(attr.actionBarTheme, outValue, true);                Object themedContext;                if (outValue.resourceId != 0) {                    themedContext = new ContextThemeWrapper(this.mContext, outValue.resourceId);                } else {                    themedContext = this.mContext;                }                subDecor = (ViewGroup)LayoutInflater.from((Context)themedContext).inflate(layout.abc_screen_toolbar, (ViewGroup)null);                this.mDecorContentParent = (DecorContentParent)subDecor.findViewById(id.decor_content_parent);                this.mDecorContentParent.setWindowCallback(this.getWindowCallback());                if (this.mOverlayActionBar) {                    this.mDecorContentParent.initFeature(109);                }                if (this.mFeatureProgress) {                    this.mDecorContentParent.initFeature(2);                }                if (this.mFeatureIndeterminateProgress) {                    this.mDecorContentParent.initFeature(5);                }            }        } else {            if (this.mOverlayActionMode) {                subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple_overlay_action_mode, (ViewGroup)null);            } else {                subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple, (ViewGroup)null);            }            if (VERSION.SDK_INT >= 21) {                ViewCompat.setOnApplyWindowInsetsListener(subDecor, new OnApplyWindowInsetsListener() {                    public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {                        int top = insets.getSystemWindowInsetTop();                        int newTop = AppCompatDelegateImpl.this.updateStatusGuard(top);                        if (top != newTop) {                            insets = insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), newTop, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());                        }                        return ViewCompat.onApplyWindowInsets(v, insets);                    }                });            } else {                ((FitWindowsViewGroup)subDecor).setOnFitSystemWindowsListener(new OnFitSystemWindowsListener() {                    public void onFitSystemWindows(Rect insets) {                        insets.top = AppCompatDelegateImpl.this.updateStatusGuard(insets.top);                    }                });            }        }        // 把 DecorView 添加到 Window 上 并且返回 DecorView        if (subDecor == null) {            throw new IllegalArgumentException("AppCompat does not support the current theme features: { windowActionBar: " + this.mHasActionBar + ", windowActionBarOverlay: " + this.mOverlayActionBar + ", android:windowIsFloating: " + this.mIsFloating + ", windowActionModeOverlay: " + this.mOverlayActionMode + ", windowNoTitle: " + this.mWindowNoTitle + " }");        } else {            if (this.mDecorContentParent == null) {                this.mTitleView = (TextView)subDecor.findViewById(id.title);            }            ViewUtils.makeOptionalFitsSystemWindows(subDecor);            ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);            ViewGroup windowContentView = (ViewGroup)this.mWindow.findViewById(16908290);            if (windowContentView != null) {                while(windowContentView.getChildCount() > 0) {                    View child = windowContentView.getChildAt(0);                    windowContentView.removeViewAt(0);                    contentView.addView(child);                }                windowContentView.setId(-1);                contentView.setId(16908290);                if (windowContentView instanceof FrameLayout) {                    ((FrameLayout)windowContentView).setForeground((Drawable)null);                }            }                        // 把 DecorView 添加到 Window 上             this.mWindow.setContentView(subDecor);            contentView.setAttachListener(new OnAttachListener() {                public void onAttachedFromWindow() {                }                public void onDetachedFromWindow() {                    AppCompatDelegateImpl.this.dismissPopups();                }            });            return subDecor;        }    }}

创建好 DecorView 之后,DecorView 会被添加到 Windows(实现类是 PhoneWindow) 中,然后返回 DecorView。
并且 DecorView 是视图的顶级容器,我们可以通过 Android Studio 的 Layout Inspector 来查看一个界面的 View Tree。

一个 DecorView 包含这一个纵向的 LinearLayout,LinearLayout 内部是一个 FrameLayout,FrameLayout 是一个包含了内容部分和标题部分的容器。

在 AppCompatDelegateImpl 中的 setContentView() 方法中还有一句:

ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);

这句代码得到的 contentParent 就是刚刚创建的 DecorView 中的 内容根部局(id/content (ContentFrameLayout))。

ContentFrameLayout 是 FrameLayout 的子类,我们编写的 xml 布局就是被添加到它的内部。

然后查看为内容根布局添加视图的过程。

3. LayoutInflater 的 inflate()

inflate() 的代码如下:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {    final Resources res = getContext().getResources();    if (DEBUG) {        Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("                + Integer.toHexString(resource) + ")");    }        // 获取解析当前布局 xml 文件的 parser 对象    final XmlResourceParser parser = res.getLayout(resource);    try {        // 调用 inflate() 方法,开始解析 xml 文件,并返回得到界面        return inflate(parser, root, attachToRoot);    } finally {        parser.close();    }}

在上述方法中,会先获取解析 xml 文件的 parser 对象,然后调用另一个 infalte() 方法进行解析。

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {    synchronized (mConstructorArgs) {        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");        final Context inflaterContext = mContext;        final AttributeSet attrs = Xml.asAttributeSet(parser);        Context lastContext = (Context) mConstructorArgs[0];        mConstructorArgs[0] = inflaterContext;        View result = root;        try {            // 通过 while 循环遍历 xml 中的节点,直到找到 root            int type;            while ((type = parser.next()) != XmlPullParser.START_TAG &&                    type != XmlPullParser.END_DOCUMENT) {                // Empty            }            if (type != XmlPullParser.START_TAG) {                throw new InflateException(parser.getPositionDescription()                        + ": No start tag found!");            }            final String name = parser.getName();            // 如果是 merge 节点,执行 rInflate() 方法,按照层次递归的去实例化 xml 文件的子项            if (TAG_MERGE.equals(name)) {                if (root == null || !attachToRoot) {                    throw new InflateException(" can be used only with a valid "                            + "ViewGroup root and attachToRoot=true");                }                rInflate(parser, root, inflaterContext, attrs, false);            } else {                // Temp is the root view that was found in the xml                // 不是 merge 节点,就通过 tag 标签创建一个 view                final View temp = createViewFromTag(root, name, inflaterContext, attrs);                ViewGroup.LayoutParams params = null;                if (root != null) {                    // Create layout params that match root, if supplied                    params = root.generateLayoutParams(attrs);                    if (!attachToRoot) {                        // Set the layout params for temp if we are not                        // attaching. (If we are, we use addView, below)                        temp.setLayoutParams(params);                    }                }                // Inflate all children under temp against its context.                rInflateChildren(parser, temp, attrs, true);                // We are supposed to attach all the views we found (int temp)                // to root. Do that now.                // 将创建的 View 添加到 root 视图中                if (root != null && attachToRoot) {                    root.addView(temp, params);                }                // Decide whether to return the root that was passed in or the                // top view found in xml.                if (root == null || !attachToRoot) {                    result = temp;                }            }        } catch (XmlPullParserException e) {            final InflateException ie = new InflateException(e.getMessage(), e);            ie.setStackTrace(EMPTY_STACK_TRACE);            throw ie;        } catch (Exception e) {            final InflateException ie = new InflateException(parser.getPositionDescription()                    + ": " + e.getMessage(), e);            ie.setStackTrace(EMPTY_STACK_TRACE);            throw ie;        } finally {            // Don't retain static reference on context.            mConstructorArgs[0] = lastContext;            mConstructorArgs[1] = null;            Trace.traceEnd(Trace.TRACE_TAG_VIEW);        }        return result;    }}

在这个 inflate() 方法中,会先寻找 xml 文件中的起始节点,如果起始节点是 merge,就执行 rInflate() 方法,如果不是 merge,就执行 createViewFromTag() 方法去创建一个新的 View,最后把 View 添加到内容根部局中。

4. createViewFromTag()

现在看 createViewFromTag() 的源码:

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,        boolean ignoreThemeAttr) {    if (name.equals("view")) {        name = attrs.getAttributeValue(null, "class");    }    // Apply a theme wrapper, if allowed and one is specified.    if (!ignoreThemeAttr) {        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);        final int themeResId = ta.getResourceId(0, 0);        if (themeResId != 0) {            context = new ContextThemeWrapper(context, themeResId);        }        ta.recycle();    }    // 如果 name 的值为 blink,返回一个 BlinkLayout    if (name.equals(TAG_1995)) {        // Let's party like it's 1995!        return new BlinkLayout(context, attrs);    }    try {        View view;        // 依次寻找合适的 Factory 对象去创建 View        if (mFactory2 != null) {            view = mFactory2.onCreateView(parent, name, context, attrs);        } else if (mFactory != null) {            view = mFactory.onCreateView(name, context, attrs);        } else {            view = null;        }        if (view == null && mPrivateFactory != null) {            view = mPrivateFactory.onCreateView(parent, name, context, attrs);        }        if (view == null) {            final Object lastContext = mConstructorArgs[0];            mConstructorArgs[0] = context;            try {                if (-1 == name.indexOf('.')) {                    view = onCreateView(parent, name, attrs);                } else {                    view = createView(name, null, attrs);                }            } finally {                mConstructorArgs[0] = lastContext;            }        }        return view;    } catch (InflateException e) {        throw e;    } catch (ClassNotFoundException e) {        final InflateException ie = new InflateException(attrs.getPositionDescription()                + ": Error inflating class " + name, e);        ie.setStackTrace(EMPTY_STACK_TRACE);        throw ie;    } catch (Exception e) {        final InflateException ie = new InflateException(attrs.getPositionDescription()                + ": Error inflating class " + name, e);        ie.setStackTrace(EMPTY_STACK_TRACE);        throw ie;    }}

我们看一看代码中的 mFactory2mFactorymPrivateFactory 是什么。

在 LayoutInflater.java 的属性中,有如下几个变量:

private Factory mFactory;private Factory2 mFactory2;private Factory2 mPrivateFactory;

Factory 是一个接口,Factory2 是继承了 Factory 的接口,它们都有个一个 onCreateView() 的方法。

5. onCreateView()

我们去看它们在 AppCompatDelegateImpl 中的实现:

public View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) {    if (this.mAppCompatViewInflater == null) {        TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);        String viewInflaterClassName = a.getString(styleable.AppCompatTheme_viewInflaterClass);        if (viewInflaterClassName != null && !AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {            try {                Class viewInflaterClass = Class.forName(viewInflaterClassName);                this.mAppCompatViewInflater = (AppCompatViewInflater)viewInflaterClass.getDeclaredConstructor().newInstance();            } catch (Throwable var8) {                Log.i("AppCompatDelegate", "Failed to instantiate custom view inflater " + viewInflaterClassName + ". Falling back to default.", var8);                this.mAppCompatViewInflater = new AppCompatViewInflater();            }        } else {                this.mAppCompatViewInflater = new AppCompatViewInflater();        }    }    boolean inheritContext = false;    if (IS_PRE_LOLLIPOP) {        inheritContext = attrs instanceof XmlPullParser ? ((XmlPullParser)attrs).getDepth() > 1 : this.shouldInheritContext((ViewParent)parent);    }    return this.mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed());}

方法的最后可以看出创建视图的工作交给了 AppCompatViewInflater 的 createView() 去完成

6. createView()

final View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {    Context originalContext = context;    if (inheritContext && parent != null) {        context = parent.getContext();    }    if (readAndroidTheme || readAppTheme) {        context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);    }    if (wrapContext) {        context = TintContextWrapper.wrap(context);    }    View view = null;    byte var12 = -1;    switch(name.hashCode()) {    case -1946472170:        if (name.equals("RatingBar")) {            var12 = 11;        }        break;    case -1455429095:        if (name.equals("CheckedTextView")) {            var12 = 8;        }        break;    case -1346021293:        if (name.equals("MultiAutoCompleteTextView")) {            var12 = 10;        }        break;    case -938935918:        if (name.equals("TextView")) {            var12 = 0;        }        break;    case -937446323:        if (name.equals("ImageButton")) {            var12 = 5;        }        break;    case -658531749:        if (name.equals("SeekBar")) {            var12 = 12;        }        break;    case -339785223:        if (name.equals("Spinner")) {            var12 = 4;        }        break;    case 776382189:        if (name.equals("RadioButton")) {            var12 = 7;        }        break;    case 1125864064:        if (name.equals("ImageView")) {            var12 = 1;        }        break;    case 1413872058:        if (name.equals("AutoCompleteTextView")) {            var12 = 9;        }        break;    case 1601505219:        if (name.equals("CheckBox")) {            var12 = 6;        }        break;    case 1666676343:        if (name.equals("EditText")) {            var12 = 3;        }        break;    case 2001146706:        if (name.equals("Button")) {            var12 = 2;        }    }    switch(var12) {    case 0:        view = this.createTextView(context, attrs);        this.verifyNotNull((View)view, name);        break;    case 1:        view = this.createImageView(context, attrs);        this.verifyNotNull((View)view, name);        break;    case 2:        view = this.createButton(context, attrs);        this.verifyNotNull((View)view, name);        break;    case 3:        view = this.createEditText(context, attrs);        this.verifyNotNull((View)view, name);        break;    case 4:        view = this.createSpinner(context, attrs);        this.verifyNotNull((View)view, name);        break;    case 5:        view = this.createImageButton(context, attrs);        this.verifyNotNull((View)view, name);        break;    case 6:        view = this.createCheckBox(context, attrs);        this.verifyNotNull((View)view, name);        break;    case 7:        view = this.createRadioButton(context, attrs);        this.verifyNotNull((View)view, name);        break;    case 8:        view = this.createCheckedTextView(context, attrs);        this.verifyNotNull((View)view, name);        break;    case 9:        view = this.createAutoCompleteTextView(context, attrs);        this.verifyNotNull((View)view, name);        break;    case 10:        view = this.createMultiAutoCompleteTextView(context, attrs);        this.verifyNotNull((View)view, name);        break;    case 11:        view = this.createRatingBar(context, attrs);        this.verifyNotNull((View)view, name);        break;    case 12:        view = this.createSeekBar(context, attrs);        this.verifyNotNull((View)view, name);        break;    default:        view = this.createView(context, name, attrs);    }    if (view == null && originalContext != context) {        view = this.createViewFromTag(context, name, attrs);    }    if (view != null) {        this.checkOnClickListener((View)view, attrs);    }    return (View)view;}

通过 createTextView() 等源码可以发现,creatView() 方法把常用的组件都变成了 AppCompat 的类型,从而达到了兼容的目的。

至此,setContentView() 的流程就走完了。但是添加好布局文件之后,视图并不会显示到界面上,还需要通过 WindowsManagerService 去渲染界面才能使界面显示。这部分到内容在后面会讲到。

零碎的东西很多,为了方便大家记忆,我把上面的内容做成了思维导图,需要的朋友可以保存下来,偶尔看一下,帮助自己记忆。

欢迎关注本文作者:

扫码关注并回复「干货」,获取我整理的千G Android、iOS、JavaWeb、大数据、人工智能等学习资源。

更多相关文章

  1. Android知识梳理:消息机制之Looper
  2. 4种必须知道的Android屏幕自适应解决方案
  3. Android(安卓)主动获取电量的方法
  4. Android(安卓)九宫格图片展示的实现
  5. Android(安卓)Studio的Gradle错误解决方法
  6. android Bitmap
  7. SharedPreferences之Android数据保存
  8. 浅谈Java中Collections.sort对List排序的两种方法
  9. Python list sort方法的具体使用

随机推荐

  1. 数据库的认识+SQL
  2. MySQL5.7.10下载及安装及配置
  3. 使用不同的条件,在同一个表的一个SQL查询
  4. 下标越界: '[number: 0]'
  5. 如何将MDB (Access)文件转换为MySQL(或纯
  6. phthon 连接sql server数据库执行存储过
  7. PostgreSQL+pgpooll+Keepalived双机HA方
  8. 如何以小时为单位从oracle数据库中获取数
  9. 关于SQL注入的一些问题...URL注入....
  10. 如何用sql语句查询出当月的每日数据量