天天看點

View繪制源碼淺析(一)布局的加載

前言

說到View的繪制大部分人都能說上一兩句,但細節實在太多如果沒系統的去看很難吃透。近期我專門抽了一周多的時間讀了繪制相關的源碼,這裡準備用三篇部落格做一個系統的講述,目錄如下。

  1. View繪制源碼淺析(一)布局的加載
  2. View繪制源碼淺析(二)布局的測量、布局、繪制
  3. View繪制源碼淺析(三)

    requestLayout

    invalidate

    postInvalidate

    三者的差別

本文的源碼基于API27。

疑問

布局加載最重要的就是

setContentView()

方法了,隻需要我們傳入一個布局id即可完成布局的加載,但實際上這裡是有幾個疑問的。

  1. 如何根據xml建立View的。
  2. 如何讀取xml中View相關屬性的。
  3. 建立的View添加到了哪。

接下來我們帶着這些問題再去看源碼,避免迷失方向。

setContentView()

我們先從

setContentView()

這個布局加載的入口開始,看看究竟如何加載布局的。

//MainActivity.java
public class MainActivity extends AppCompatActivity {//繼承appCompatActivity
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);//布局加載的入口
    }
}

//AppCompatActivity.java
@Override
public void setContentView(@LayoutRes int layoutResID) {
    getDelegate().setContentView(layoutResID);//拿到Activity的委托對象調用setContentView()
}

//AppCompatActivity.java
@NonNull
public AppCompatDelegate getDelegate() {//擷取Activity委托對象
    if (mDelegate == null) {
        mDelegate = AppCompatDelegate.create(this, this);
    }
    return mDelegate;
}

//AppCompatDelegate.java
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {//建立Activity委托對象
    return create(activity, activity.getWindow(), callback);//這裡将activity.getWindow()傳入。
}

//AppCompatDelegate.java
private static AppCompatDelegate create(Context context, Window window,
                                        AppCompatCallback callback) {//根據不同的版本建立不同的Activity委托對象
    if (Build.VERSION.SDK_INT >= 24) {
        return new AppCompatDelegateImplN(context, window, callback);
    } else if (Build.VERSION.SDK_INT >= 23) {
        return new AppCompatDelegateImplV23(context, window, callback);
    } else {
        return new AppCompatDelegateImplV14(context, window, callback);
    }
}

//AppCompatDelegateImplV9.java 
//最終是調到v9的setContentView方法
@Override
public void setContentView(int resId) {
    ensureSubDecor();//確定SubDecor相關布局初始化完成
    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);//找到id為content的view
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);//通過LayoutInflater直接把我們的布局添加到id為content的布局上
    mOriginalWindowCallback.onContentChanged();
}
           

由于繼承的是

appCompatActivity

這個相容的

Activity

是以是根據不同的api版本建立不同的

AppCompatDelegate

實作類以相容老邏輯。

setContentView()

最終是調到了

AppCompatDelegateImplV9

setContentView()

,接下來具體實作分為兩步。

  1. 通過

    ensureSubDecor()

    方法確定

    SubDecor

    相關布局初始化完成。
  2. 找到

    SubDecor

    中id為content的布局,将我們自己的布局inflater到content上。

這裡說明下,

SubDecor

不是

DecorView

,隻是一個變量名為

subDecor

ViewGroup

不過這裡充當

DecorView

的角色,不要混淆了。

這裡先說第一步

ensureSubDecor()

//AppCompatDelegateImplV9.java
private void ensureSubDecor() {//確定SubDecor的建立
    if (!mSubDecorInstalled) {//如果沒有建立SubDecor
        mSubDecor = createSubDecor();//建立SubDecor
        ...
    }
}

private ViewGroup createSubDecor() {
    TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);//拿到AppCompat相關的主題屬性
	//根據主題中的屬性執行對應的Feature方法
    if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);//我們比較熟悉的FEATURE_NO_TITLE Feature
    } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
        // Don't allow an action bar if there is no title.
        requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
    }
    if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
        requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
    }
    if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
        requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
    }
    mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
    a.recycle();

    mWindow.getDecorView();//確定DecorView的建立

    final LayoutInflater inflater = LayoutInflater.from(mContext);//用來填充SubDecor的inflater
    ViewGroup subDecor = null;//subDecor布局

	//接下來是根據主題屬性初始化不同的subDecor布局
    if (!mWindowNoTitle) {
        if (mIsFloating) {
            // If we're floating, inflate the dialog title decor
            subDecor = (ViewGroup) inflater.inflate(
                R.layout.abc_dialog_title_material, null);

            // Floating windows can never have an action bar, reset the flags
            mHasActionBar = mOverlayActionBar = false;
        } else if (mHasActionBar) {
            /**
                 * This needs some explanation. As we can not use the android:theme attribute
                 * pre-L, we emulate it by manually creating a LayoutInflater using a
                 * ContextThemeWrapper pointing to actionBarTheme.
                 */
            TypedValue outValue = new TypedValue();
            mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);

            Context themedContext;
            if (outValue.resourceId != 0) {
                themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
            } else {
                themedContext = mContext;
            }

            // Now inflate the view using the themed context and set it as the content view
            subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                .inflate(R.layout.abc_screen_toolbar, null);

            mDecorContentParent = (DecorContentParent) subDecor
                .findViewById(R.id.decor_content_parent);
            mDecorContentParent.setWindowCallback(getWindowCallback());

            /**
                 * Propagate features to DecorContentParent
                 */
            if (mOverlayActionBar) {
                mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
            }
            if (mFeatureProgress) {
                mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
            }
            if (mFeatureIndeterminateProgress) {
                mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
            }
        }
    } else {
        if (mOverlayActionMode) {
            subDecor = (ViewGroup) inflater.inflate(
                R.layout.abc_screen_simple_overlay_action_mode, null);
        } else {
            subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
        }

        if (Build.VERSION.SDK_INT >= 21) {
            // If we're running on L or above, we can rely on ViewCompat's
            // setOnApplyWindowInsetsListener
            ViewCompat.setOnApplyWindowInsetsListener(subDecor,
                                                      new OnApplyWindowInsetsListener() {
                                                          @Override
                                                          public WindowInsetsCompat onApplyWindowInsets(View v,
                                                                                                        WindowInsetsCompat insets) {
                                                              final int top = insets.getSystemWindowInsetTop();
                                                              final int newTop = updateStatusGuard(top);

                                                              if (top != newTop) {
                                                                  insets = insets.replaceSystemWindowInsets(
                                                                      insets.getSystemWindowInsetLeft(),
                                                                      newTop,
                                                                      insets.getSystemWindowInsetRight(),
                                                                      insets.getSystemWindowInsetBottom());
                                                              }

                                                              // Now apply the insets on our view
                                                              return ViewCompat.onApplyWindowInsets(v, insets);
                                                          }
                                                      });
        } else {
            // Else, we need to use our own FitWindowsViewGroup handling
            ((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener(
                new FitWindowsViewGroup.OnFitSystemWindowsListener() {
                    @Override
                    public void onFitSystemWindows(Rect insets) {
                        insets.top = updateStatusGuard(insets.top);
                    }
                });
        }
    }

    if (subDecor == null) {//檢查SubDecor是否建立
        throw new IllegalArgumentException(
            "AppCompat does not support the current theme features: { "
            + "windowActionBar: " + mHasActionBar
            + ", windowActionBarOverlay: "+ mOverlayActionBar
            + ", android:windowIsFloating: " + mIsFloating
            + ", windowActionModeOverlay: " + mOverlayActionMode
            + ", windowNoTitle: " + mWindowNoTitle
            + " }");
    }

    final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
        R.id.action_bar_activity_content);//找到subDecor中id為action_bar_activity_content的布局

    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);//找到PhoneWindow中id為content的布局
    if (windowContentView != null) {
        while (windowContentView.getChildCount() > 0) {//将windowContentView的子布局全部添加到subDecor中id為action_bar_activity_content的布局上
            final View child = windowContentView.getChildAt(0);
            windowContentView.removeViewAt(0);
            contentView.addView(child);
        }

        windowContentView.setId(View.NO_ID);//清除PhoneWindow中id為content的布局id
        contentView.setId(android.R.id.content);//給subDecor中id為action_bar_activity_content的布局設定上新的id為content以假亂真

        if (windowContentView instanceof FrameLayout) {
            ((FrameLayout) windowContentView).setForeground(null);
        }
    }

    // Now set the Window's content view with the decor
    mWindow.setContentView(subDecor);//将subDecor設定到window上
    return subDecor;
}
           

這裡先拿到主題中的屬性,然後根據主題中的屬性設定對應的Feature,并根據條件建立對應的

subDecor

。接下來拿到

PhoneWindow

中id為

content

的View,把它的子布局全部添加到subDecor中id為

action_bar_activity_content

的布局上,然後将

windowContentView

的id移除,給subDecor中id為

action_bar_activity_content

的布局設定上新的id為

content

以假亂真充當

ContentView

的角色,最後将

SubDecor

通過

mWindow.setContentView(subDecor)

設定到window上。

那麼經過

ensureSubDecor()

方法後我們就完成了

DecorView

SubDecor

的初始化并通過

mWindow.setContentView(subDecor)

SubDecor

添加到了

DecorView

上。完成了

SubDecor

DecorView

的關聯。

在回到我們之前的

setContentView()

//AppCompatDelegateImplV9.java 
//最終是調到v9的setContentView方法
@Override
public void setContentView(int resId) {
    ensureSubDecor();//確定SubDecor相關布局初始化完成
    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);//找到id為content的view
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);//通過LayoutInflater直接把我們的布局添加到id為content的布局上
    mOriginalWindowCallback.onContentChanged();
}
           

完成

SubDecor

初始化後,我們通過

mSubDecor.findViewById(android.R.id.content)

找到

contentParent

,然後直接

LayoutInflater.from(mContext).inflate(resId, contentParent)

将布局添加到了

contentParent

上完成了布局的添加。

那麼對于第一和第二個問題則必須在

LayoutInflater.inflate()

中尋找答案了,而第三個問題我們已經可以回答了

建立的View添加到了哪?

答:添加到了id為

android.R.id.content

的view上。

LayoutInflater.inflate()

接下來我們看下

inflate()

是如何将我們的布局添加到id為content的view上的。

//LayoutInflater.java
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
            return inflate(resource, root, root != null);
    }

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
		...
        final XmlResourceParser parser = res.getLayout(resource);//根據資源id建立解析器
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
           	...
            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;

            try {
                // Look for the root node.
                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();//拿到節點名字
                if (TAG_MERGE.equals(name)) {//merge單獨處理
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> 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
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);//建立xml中根布局
                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);//根據父布局建立xml中根布局的lp,因為自己的lp是跟父布局有關的。
                        if (!attachToRoot) {//當滿足root不為null并且attachToRoot為false則直接将lp設定到temp上
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }

                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);//inflate temp的子布局,具體實作就是遞歸的把view添加到temp上

                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }

                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {//如果root不為null 并且 attachToRoot為true則直接add到root上
                        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;
        }
    }


           

它是先通過

res.getLayout(resource)

從布局id拿到解析器對象

XmlResourceParser

,然後通過

createViewFromTag()

方法建立根布局,再

root.generateLayoutParams(attrs)

建立根布局的lp,之後根據

root

attachToRoot

這兩個條件判斷是在

addView()

還是在

setLayoutParams()

給根布局加上lp,這中間還有一步

rInflateChildren()

将xml中的子布局依次添加到根布局上。

這中間有些細節值得深究下

  1. createViewFromTag()

    如何建立布局的(解答第一個問題)
  2. root.generateLayoutParams(attrs)

    如何讀取lp相關屬性的(解答第二個問題)

我們先看

root.generateLayoutParams(attrs)

,之是以要這樣是因為每個view的lp的建立都是跟父布局有關的,比如root是

LinearLayout

那麼建立的就是

LinearLayout.LayoutParams

并初始化

LinearLayout

獨有的

weight

gravity

屬性,而如果root是

RelativeLayout

那麼建立的是

RelativeLayout.LayoutParams

并初始化跟各個布局的限制關系。

這裡我們可以看下

LinearLayout.generateLayoutParams()

方法

//LinearLayout.java
	@Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LinearLayout.LayoutParams(getContext(), attrs);
    }

	//LinearLayout.LayoutParams 内部類
    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);//這裡還調用了父類MarginLayoutParams的構造方
            TypedArray a =
                c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);

            weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);//擷取xml中weight屬性
            gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);//擷取layout_gravity屬性

            a.recycle();
        }
    }

	//ViewGroup.MarginLayoutParams 内部類 初始化margin屬性
    public static class MarginLayoutParams extends ViewGroup.LayoutParams {
        public MarginLayoutParams(Context c, AttributeSet attrs) {
            super();

            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
            setBaseAttributes(a,
                              R.styleable.ViewGroup_MarginLayout_layout_width,
                              R.styleable.ViewGroup_MarginLayout_layout_height);//調用父類ViewGroup.LayoutParams的setBaseAttributes()讀取最基礎的寬高屬性
            //下面就是讀取xml各種margin屬性
            int margin = a.getDimensionPixelSize(
                com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
            if (margin >= 0) {//如果margin大于0則直接用margin屬性
                leftMargin = margin;
                topMargin = margin;
                rightMargin= margin;
                bottomMargin = margin;
            } else {//分别讀取left、top、right、bottom的margin屬性
                int horizontalMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginHorizontal, -1);
                int verticalMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginVertical, -1);

                if (horizontalMargin >= 0) {
                    leftMargin = horizontalMargin;
                    rightMargin = horizontalMargin;
                } else {
                    leftMargin = a.getDimensionPixelSize(
                        R.styleable.ViewGroup_MarginLayout_layout_marginLeft,
                        UNDEFINED_MARGIN);
                    if (leftMargin == UNDEFINED_MARGIN) {
                        mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
                        leftMargin = DEFAULT_MARGIN_RESOLVED;
                    }
                    rightMargin = a.getDimensionPixelSize(
                        R.styleable.ViewGroup_MarginLayout_layout_marginRight,
                        UNDEFINED_MARGIN);
                    if (rightMargin == UNDEFINED_MARGIN) {
                        mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
                        rightMargin = DEFAULT_MARGIN_RESOLVED;
                    }
                }

                startMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginStart,
                    DEFAULT_MARGIN_RELATIVE);
                endMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginEnd,
                    DEFAULT_MARGIN_RELATIVE);

                if (verticalMargin >= 0) {
                    topMargin = verticalMargin;
                    bottomMargin = verticalMargin;
                } else {
                    topMargin = a.getDimensionPixelSize(
                        R.styleable.ViewGroup_MarginLayout_layout_marginTop,
                        DEFAULT_MARGIN_RESOLVED);
                    bottomMargin = a.getDimensionPixelSize(
                        R.styleable.ViewGroup_MarginLayout_layout_marginBottom,
                        DEFAULT_MARGIN_RESOLVED);
                }

                if (isMarginRelative()) {
                    mMarginFlags |= NEED_RESOLUTION_MASK;
                }
            }

            final boolean hasRtlSupport = c.getApplicationInfo().hasRtlSupport();
            final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;
            if (targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport) {
                mMarginFlags |= RTL_COMPATIBILITY_MODE_MASK;
            }

            // Layout direction is LTR by default
            mMarginFlags |= LAYOUT_DIRECTION_LTR;

            a.recycle();
        }
    }

	//ViewGroup.LayoutParams 内部類
    public static class LayoutParams {
        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {//讀取寬高
            width = a.getLayoutDimension(widthAttr, "layout_width");
            height = a.getLayoutDimension(heightAttr, "layout_height");
        }
	}
		

           

可以看到

LinearLayout.LayoutParams

有兩層繼承關系,

LinearLayout.LayoutParams

負責拿到

LinearLayout

的特有屬性,

ViewGroup.MarginLayoutParams

負責拿到

margin

屬性,

ViewGroup.LayoutParams

負責拿到寬高。一般的

ViewGroup

基本都是實作自己的lp然後繼承

ViewGroup.MarginLayoutParams

這裡可以回答我們第二個問題

如何讀取xml中View相關屬性的?

通過

root.generateLayoutParams(attrs)

根據root不同建立不同的LayoutParams讀取xml中相關屬性

接下來看

createViewFromTag()

如何建立布局的

private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
        return createViewFromTag(parent, name, context, attrs, false);//調用建立重載方法
    }

    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        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;
            }

            //其次通過mPrivateFactory建立,一般情況下是為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的名字中不帶'.',其實就是系統控件,類似TextView這種我們在xml寫的時候不是全類名。
                        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;
        }
    }
           

代碼中可以看出布局的建立是先用

Factory2

Factory

,如果建立失敗在通過

mPrivateFactory

,如果還是失敗則通過

onCreateView()

或者

createView()

方法建立。

Factory

我們稍後說,

mPrivateFactory

一般為null我們暫且忽略。

我們先看

onCreateView()

或者

createView()

protected View onCreateView(View parent, String name, AttributeSet attrs)
            throws ClassNotFoundException {//建立系統控件
        return onCreateView(name, attrs);
    }

    protected View onCreateView(String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return createView(name, "android.view.", attrs);//給系統控件加上全類名字首"android.view."
    }

	//最終都是通過createView建立View
    public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);//擷取緩存的Constructor
        Class<? extends View> clazz = null;

        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);//拿到name對應的class

                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
                constructor = clazz.getConstructor(mConstructorSignature);//拿到View的構造方法
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);//緩存
            } else {
                // If we have a filter, apply it to cached constructor
                if (mFilter != null) {
                    // Have we seen this name before?
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        // New class -- remember whether it is allowed
                        clazz = mContext.getClassLoader().loadClass(
                                prefix != null ? (prefix + name) : name).asSubclass(View.class);

                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                        mFilterMap.put(name, allowed);
                        if (!allowed) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    } else if (allowedState.equals(Boolean.FALSE)) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
            }

            Object lastContext = mConstructorArgs[0];
            if (mConstructorArgs[0] == null) {
                // Fill in the context if not already within inflation.
                mConstructorArgs[0] = mContext;
            }
            Object[] args = mConstructorArgs;
            args[1] = attrs;

            final View view = constructor.newInstance(args);//通過反射建立View
            if (view instanceof ViewStub) {
                // Use the same context when inflating ViewStub later.
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            mConstructorArgs[0] = lastContext;
            return view;

        } catch (NoSuchMethodException e) {
            final InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;

        } catch (ClassCastException e) {
            // If loaded class is not a View subclass
            final InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } catch (ClassNotFoundException e) {
            // If loadClass fails, we should propagate the exception.
            throw e;
        } catch (Exception e) {
            final InflateException ie = new InflateException(
                    attrs.getPositionDescription() + ": Error inflating class "
                            + (clazz == null ? "<unknown>" : clazz.getName()), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
           

可以看到如果

Factory

Factory2

建立View失敗則會通過反射的方式建立View。

那麼

Factory

Factory2

是在何時設定的呢,其實是在Activity的

onCreate()

public class AppCompatActivity extends FragmentActivity implements AppCompatCallback,
        TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {
	
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate();
        delegate.installViewFactory();//拿到委托對象調用installViewFactory()設定factory
        super.onCreate(savedInstanceState);
    }
}
           

最後是調到

AppCompatDelegateImplV9

installViewFactory()

@Override
    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);//拿到LayoutInflater
        if (layoutInflater.getFactory() == null) {//如果Factory為空
            LayoutInflaterCompat.setFactory2(layoutInflater, this);//設定Fractory2
        } else {
            if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
                Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                        + " so we can not install AppCompat's");
            }
        }
    }
           

這裡有人可能會問明明是

setFactory2()

為何要判斷

layoutInflater.getFactory() == null

,這裡解釋下,無論

Factory

還是

Factory2

都隻是提供給我們一個可以根據xml中标簽名字生成View的接口,而

Factory2

是繼承的

Factory

,隻是比

Factory

多一個方法,支援建立View時添加父布局參數,并且因為

Factory2

是繼承

Factory

是以無論是

setFactory2()

或者

setFactory()

都會給Factory指派,是以我們隻需要判斷

layoutInflater.getFactory() == null

就可以知道是否設定過。并且

setFactory()

也隻能設定一次,多次次設定會報錯的。

public interface Factory {
        public View onCreateView(String name, Context context, AttributeSet attrs);
    }

    public interface Factory2 extends Factory {
        public View onCreateView(View parent, String name, Context context, AttributeSet attrs);//多一個View parent參數的方法
    }

    public final Factory getFactory() {
        return mFactory;
    }

    public void setFactory2(Factory2 factory) {
        if (mFactorySet) {//判斷是否設定過了
            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
        }
        if (factory == null) {
            throw new NullPointerException("Given factory can not be null");
        }
        mFactorySet = true;//設定标記為置為true
        if (mFactory == null) {
            mFactory = mFactory2 = factory;//給mFactory和mFactory2都指派
        } else {
            mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
        }
    }

    public void setFactory(Factory factory) {
        if (mFactorySet) {//判斷是否設定過了
            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
        }
        if (factory == null) {
            throw new NullPointerException("Given factory can not be null");
        }
        mFactorySet = true;//設定标記為置為true
        if (mFactory == null) {
            mFactory = factory;//給mFactory指派
        } else {
            mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
        }
    }

           

從代碼上也印證了上面的結論。接下來回到

AppCompatDelegateImplV9

installViewFactory()

@Override
    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);//拿到LayoutInflater
        if (layoutInflater.getFactory() == null) {//如果Factory為空
            LayoutInflaterCompat.setFactory2(layoutInflater, this);//設定Fractory2
        } else {
            if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
                Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                        + " so we can not install AppCompat's");
            }
        }
    }

	//LayoutInflaterCompat.setFactory2()
    public static void setFactory2(
            @NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
        IMPL.setFactory2(inflater, factory);
    }

    static final LayoutInflaterCompatBaseImpl IMPL;
    static {
        if (Build.VERSION.SDK_INT >= 21) {
            IMPL = new LayoutInflaterCompatApi21Impl();//impl具體實作類
        } else {
            IMPL = new LayoutInflaterCompatBaseImpl();
        }
    }

    @RequiresApi(21)
    static class LayoutInflaterCompatApi21Impl extends LayoutInflaterCompatBaseImpl {
        @SuppressWarnings("deprecation")
        @Override
        public void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
            inflater.setFactory2(factory != null ? new Factory2Wrapper(factory) : null);
        }

        @Override
        public void setFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory)	{
            inflater.setFactory2(factory);//最終還是調的LayoutInflater.setFactory2()
        }
    }
           

可以看到

LayoutInflaterCompat.setFactory2(layoutInflater, this)

傳入的第二個參數

Factory2

是this,那麼我們看下

AppCompatDelegateImplV9

如何實作的。

@Override
    public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        final View view = callActivityOnCreateView(parent, name, context, attrs);//先調用activity的onCreateView()一般情況下我們都沒實作該方法是以傳回值為null
        if (view != null) {
            return view;
        }

        return createView(parent, name, context, attrs);//是以建立View的方法在此
    }

    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {//調用onCreateView(View parent, String name, Context context, AttributeSet attrs) 
        return onCreateView(null, name, context, attrs);
    }
           

我們在看到

createView()

方法

@Override
    public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        //根據各種條件建立AppCompatViewInflater
        if (mAppCompatViewInflater == null) {
            TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
            String viewInflaterClassName =
                    a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
            if ((viewInflaterClassName == null)
                    || AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
                mAppCompatViewInflater = new AppCompatViewInflater();//建立AppCompatViewInflater
            } else {
                try {
                    Class viewInflaterClass = Class.forName(viewInflaterClassName);
                    mAppCompatViewInflater =
                            (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
                                    .newInstance();//建立AppCompatViewInflater
                } catch (Throwable t) {
                    Log.i(TAG, "Failed to instantiate custom view inflater "
                            + viewInflaterClassName + ". Falling back to default.", t);
                    mAppCompatViewInflater = new AppCompatViewInflater();//建立AppCompatViewInflater
                }
            }
        }

        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
                true, /* Read read app:theme as a fallback at all times for legacy reasons */
                VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
        );//最終調用mAppCompatViewInflater.createView()建立View
    }
           

最終建立View是通過

AppCompatViewInflater

createView()

方法

final View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs, boolean inheritContext,
            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
        final Context originalContext = context;

		//拿到context
        if (inheritContext && parent != null) {
            context = parent.getContext();
        }
        if (readAndroidTheme || readAppTheme) {
            // We then apply the theme on the context, if specified
            context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
        }
        if (wrapContext) {
            context = TintContextWrapper.wrap(context);
        }

        View view = null;

        // We need to 'inject' our tint aware Views in place of the standard framework versions
        switch (name) {//根據标簽名字建立對應的View
            case "TextView":
                view = createTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageView":
                view = createImageView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Button":
                view = createButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "EditText":
                view = createEditText(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Spinner":
                view = createSpinner(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageButton":
                view = createImageButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckBox":
                view = createCheckBox(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RadioButton":
                view = createRadioButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckedTextView":
                view = createCheckedTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "AutoCompleteTextView":
                view = createAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "MultiAutoCompleteTextView":
                view = createMultiAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RatingBar":
                view = createRatingBar(context, attrs);
                verifyNotNull(view, name);
                break;
            case "SeekBar":
                view = createSeekBar(context, attrs);
                verifyNotNull(view, name);
                break;
            default:
                // The fallback that allows extending class to take over view inflation
                // for other tags. Note that we don't check that the result is not-null.
                // That allows the custom inflater path to fall back on the default one
                // later in this method.
                view = createView(context, name, attrs);
        }

        if (view == null && originalContext != context) {
            // If the original context does not equal our themed context, then we need to manually
            // inflate it using the name so that android:theme takes effect.
            view = createViewFromTag(context, name, attrs);
        }

        if (view != null) {
            // If we have created a view, check its android:onClick
            checkOnClickListener(view, attrs);
        }

        return view;
    }
           

接下來我們看下

createTextView()

方法

@NonNull
    protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
        return new AppCompatTextView(context, attrs);
    }
           

看發現了什麼,對于xml中的TextView實際建立的是

AppCompatTextView

,對沒錯這就是繼承

AppCompatActivity

後他對我們xml中部分布局做了相容。

到這裡View的建立其實就說完了,這裡可以回答第一個問題了。

如何根據xml建立View的?

View的建立是在

LayoutInflater.createViewFromTag()

方法,依次先用

Factory2

Factory

建立,如果建立失敗在通過

mPrivateFactory

建立,如果還是失敗則通過

onCreateView()

或者

createView()

方法建立。

這裡我們可以試着自己實作的一個

Factory2

然後在

super.onCreate()

之前設定給

LayoutInflater

将TextView标簽解析為一個

Button

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        if (LayoutInflater.from(this).getFactory() == null) {
            LayoutInflater.from(this).setFactory2(new LayoutInflater.Factory2() {
                @Override
                public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
                    View view = null;
                    switch (name) {
                        case "TextView":
                            view = new Button(context, attrs);
                            break;
                    }
                    return view;
                }

                @Override
                public View onCreateView(String name, Context context, AttributeSet attrs) {
                    return null;
                }
            });
        }
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }
}
           
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


</FrameLayout>
           
View繪制源碼淺析(一)布局的加載

可以看到xml中TextView變成了一個Button。

總結

整體流程用一句話來說就是

setContentView

完成

DecorView

相關布局初始化并将我們的布局通過

LayoutInflater.inflate()

方法添加到id為Content的ViewGroup上。

具體細節的話就是我們前面那個三個問題

  • 建立的View添加到了哪?

    答:添加到了id為

    android.R.id.content

    的view上。
  • 如何讀取xml中View相關屬性的?

    通過

    root.generateLayoutParams(attrs)

    根據root不同建立不同的LayoutParams讀取xml中相關屬性
  • 如何根據xml建立View的?

    View的建立是在

    LayoutInflater.createViewFromTag()

    方法,依次先用

    Factory2

    Factory

    建立,如果建立失敗在通過

    mPrivateFactory

    建立,如果還是失敗則通過

    onCreateView()

    或者

    createView()

    方法建立。