View繪製源碼淺析(一)佈局的加載

前言

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

  1. View繪製源碼淺析(一)佈局的加載
  2. View繪製源碼淺析(二)佈局的測量、佈局、繪製
  3. View繪製源碼淺析(三)requestLayoutinvalidatepostInvalidate三者的區別

本文的源碼基於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()最終是調到了AppCompatDelegateImplV9setContentView(),接下來具體實現分爲兩步。

  1. 通過ensureSubDecor()方法確保SubDecor相關佈局初始化完成。
  2. 找到SubDecor中id爲content的佈局,將我們自己的佈局inflater到content上。

這裏說明下,SubDecor不是DecorView,只是一個變量名爲subDecorViewGroup不過這裏充當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()方法後我們就完成了DecorViewSubDecor的初始化並通過mWindow.setContentView(subDecor)SubDecor添加到了DecorView上。完成了SubDecorDecorView的關聯。

在回到我們之前的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,之後根據rootattachToRoot這兩個條件判斷是在addView()還是在setLayoutParams()給根佈局加上lp,這中間還有一步rInflateChildren()將xml中的子佈局依次添加到根佈局上。

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

  1. createViewFromTag()如何創建佈局的(解答第一個問題)
  2. root.generateLayoutParams(attrs)如何讀取lp相關屬性的(解答第二個問題)

我們先看root.generateLayoutParams(attrs),之所以要這樣是因爲每個view的lp的創建都是跟父佈局有關的,比如root是LinearLayout那麼創建的就是LinearLayout.LayoutParams並初始化LinearLayout獨有的weightgravity屬性,而如果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;
        }
    }

代碼中可以看出佈局的創建是先用Factory2Factory,如果創建失敗在通過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);
        }
    }

可以看到如果FactoryFactory2創建View失敗則會通過反射的方式創建View。

那麼FactoryFactory2是在何時設置的呢,其實是在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);
    }
}

最後是調到AppCompatDelegateImplV9installViewFactory()

    @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);
        }
    }

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

    @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是通過AppCompatViewInflatercreateView()方法

    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()方法,依次先用Factory2Factory創建,如果創建失敗在通過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>

可以看到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()方法,依次先用Factory2Factory創建,如果創建失敗在通過mPrivateFactory創建,如果還是失敗則通過onCreateView()或者createView()方法創建。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章