天天看點

LayoutInflater源碼分析1. setContentView2. LayoutInflater3. 總結

在《(-)Android中的單例模式》分析中,我們分析了Android中單例模式的實作,且以LayoutInflater為執行個體,本博文就帶大家來認識下我們常用的LayoutInflater源碼。

1. setContentView

首先來看我們平時給Activity設定布局的姿勢:

setContentView(R.layout.activity_main)
           

那麼R.layout.activity_main是怎麼呈現在螢幕上的呢?中間過程與LayoutInflater的有關系嗎?

帶着這個疑問,我們繼續前行分析。

1.1 PhoneWindow.setContentView

繼續上面的分析,我們往下看:

Activity.setContentView

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

    public Window getWindow() {
        return mWindow;// 這是個什麼鬼
    }
           

我們可以看到,我們調用的Activity.setContentView實際上調用了mWindow.setContentView方法。

那麼這個mWindow是何方大神呢?從Activity的attach方法中我們可以看到,mWindow實際上是一個PhoneWindow執行個體。

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        // 這個就是我們使用的mWindow
        mWindow = new PhoneWindow(this, window);

        //....
    }
           

接下來我們接着看PhoneWindow.setContentView方法。

PhoneWindow.setContentView

@Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            // 1. 首次進入,加載DecorView
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            // 2.将我們設定的布局檔案,添加到mContentParent
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }
           

通過注釋2處的代碼:mLayoutInflater.inflate(layoutResID, mContentParent)成功将我們添加的布局檔案加入到mContentParent,并顯示在螢幕上。

那麼到這還會産生下面的疑問:

1. installDecor方法在做什麼操作?

2. mContentParent是什麼?

3. 為什麼調用LayoutInflater.infalte就能呈現我們設定的布局檔案?

不要着急,我們一個個來分析。

1.2 PhoneWindow.installDecor

installDecor方法實在是太長,我們隻挑出核心代碼來分析。

private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            // 1. 生成mDecor
            mDecor = generateDecor(-);
            //....
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            // 2. 生成mContentParent,我們設定的布局檔案就是添加到了mContentParent
            mContentParent = generateLayout(mDecor);
            // 3. 查找ActionBar的氣息地
            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);

            if (decorContentParent != null) {
                // 設定ActioBar相關
                mDecorContentParent = decorContentParent;
                mDecorContentParent.setWindowCallback(getCallback());
                if (mDecorContentParent.getTitle() == null) {
                    mDecorContentParent.setWindowTitle(mTitle);
                }
                //...
                invalidatePanelMenu(FEATURE_ACTION_BAR);
                }
            } else {
                // 沒有ActionBar,則查找設定Title
                mTitleView = (TextView) findViewById(R.id.title);
                if (mTitleView != null) {
                    if ((getLocalFeatures() & ( << FEATURE_NO_TITLE)) != ) {
                        final View titleContainer = findViewById(R.id.title_container);
                        if (titleContainer != null) {
                            titleContainer.setVisibility(View.GONE);
                        } else {
                            mTitleView.setVisibility(View.GONE);
                        }
                        mContentParent.setForeground(null);
                    } else {
                        mTitleView.setText(mTitle);
                    }
                }
            }

            //...

    }
           

installDecor主要幹了下面這幾件事:

1. 初始化DecorView(現在你可能還不知道DecorView是個什麼鬼,下面就會分析)。

2. 初始化mContentLayout(還記得麼,我們設定的布局就是添加到了它裡面)。

1.3 PhoneWindow.generateDecor

直接上代碼:

protected DecorView generateDecor(int featureId) {
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext().getResources());
                if (mTheme != -) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }
           

generateDecor方法的實作很簡單,就是根據設定構造了一個DecorView執行個體。

我們來看看DecorView到底是個什麼鬼?:

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    DecorView(Context context, int featureId, PhoneWindow window,
            WindowManager.LayoutParams params) {
        super(context);
        mFeatureId = featureId;

        //...

        updateAvailableWidth();

        setWindow(window);

        updateLogTag(params);

        mResizeShadowSize = context.getResources().getDimensionPixelSize(
                R.dimen.resize_shadow_size);
        initResizingPaints();
    }
}
           

現在你知道了,DecorView是一個FrameLayout,看起來很普通的樣子。

1.4 PhoneWindow.generateLayout

直接上源碼(作了精簡)

protected ViewGroup generateLayout(DecorView decor) {
        // 1. 設定目前的主題,我們常用的setFlag和requestWindowFeature都要在setContentView之前調用,否則就無效了
        TypedArray a = getWindowStyle();
        mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
        if (mIsFloating) {
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
        }
        // .... 一大堆設定feature的代碼

        // Inflate the window decor.
        // 2. 填充window decor
        // 會根據布局和設定,加載不同的布局檔案(帶ActionBar的和不帶Actionbar的)
        int layoutResource; // 這個就是選擇的布局檔案
        int features = getLocalFeatures();
        if ((features & ( << FEATURE_SWIPE_TO_DISMISS)) != ) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } else if ((features & (( << FEATURE_LEFT_ICON) | ( << FEATURE_RIGHT_ICON))) != ) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & (( << FEATURE_PROGRESS) | ( << FEATURE_INDETERMINATE_PROGRESS))) != 
                && (features & ( << FEATURE_ACTION_BAR)) == ) {
            layoutResource = R.layout.screen_progress;
        } else if ((features & ( << FEATURE_CUSTOM_TITLE)) != ) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_custom_title;
            }
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & ( << FEATURE_NO_TITLE)) == ) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & ( << FEATURE_ACTION_BAR)) != ) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                layoutResource = R.layout.screen_title;
            }
        } else if ((features & ( << FEATURE_ACTION_MODE_OVERLAY)) != ) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            layoutResource = R.layout.screen_simple;
        }

        // 3. 将選擇的布局問添加到DecorView中
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        // 4. 從布局檔案中查找R.id.content的ViewGroup并傳回
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        // ....
        return contentParent;
    }
           

generateLayout主要做了下面這幾件事:

1. 根據設定和主題等選擇合适的布局檔案,并将布局添加到DecorVew中。

2. 從選擇布局檔案中查找id為R.id.content(我們給Activity設定的布局原來添加到了它裡面)的ViewGroup并傳回。

将選擇的布局添加到DecorView.

DecorView.onResourcesLoaded

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        mStackId = getStackId();

        mDecorCaptionView = createDecorCaptionView(inflater);
        // 1. 解析布局檔案
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                // 添加到DecorView中
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {
            // 添加到DecorView中
            addView(root, , new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }
           

到現在我們似乎産生了新的疑問,R.id.content從哪來呢?從系統為我們選擇的布局檔案中加載。

在注釋2處,我們看到系統經過各種判斷,會為我們選擇一個合适的布局檔案,我們簡單分析兩個布局檔案。

(1)R.layout.screen_title.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:fitsSystemWindows="true">
    <!-- Popout bar for action modes -->
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
        android:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
        style="?android:attr/windowTitleBackgroundStyle">
        <TextView android:id="@android:id/title" 
            style="?android:attr/windowTitleStyle"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
    // 你要的R.id.content在這裡
    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent" 
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

           

(2)R.layout.screen_simple

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />

    // 你要的R.id.content在這裡
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
           

現在你應該明白了R.id.content從何而來。

1.5 Activity視圖結構

Activity

|

|—PhoneWindow

|

|—DecorView(FrameLayout)

|

|—LinearLayout(系統為我們選擇的布局檔案根)

|

|—Actionbar

|

|—R.id.content(FrameLaout)

|

|—R.layout.main(我們設定的布局檔案)

我們使用的Activity視圖結構大概就是這個結構,根據不同的主題和設定可能會有所不同。

分析到現在,我們隻是了解了大概的視圖結構,視圖是如何解析的呢?往下接着看。

2. LayoutInflater

在setContentView方法中,我們使用mLayoutInflater.inflate(layoutResID, mContentParent)來将我們自定義的布局加載到mContentParent中,似乎視圖的顯示和解析都與LayoutInflater.inflate有關。

2.1 inflate

我們常用的inflate方法源碼

/**
     * Inflate a new view hierarchy from the specified xml resource. Throws
     * {@link InflateException} if there is an error.
     * 
     * @param resource ID for an XML layout resource to load (e.g.,
     *        <code>R.layout.main_page</code>)
     * @param root Optional view to be the parent of the generated hierarchy.
     * @return The root View of the inflated hierarchy. If root was supplied,
     *         this is the root View; otherwise it is the root of the inflated
     *         XML file.
     */
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }

    /**
     * Inflate a new view hierarchy from the specified xml resource. Throws
     * {@link InflateException} if there is an error.
     * 
     * @param resource ID for an XML layout resource to load (e.g.,
     *        <code>R.layout.main_page</code>)
     * @param root Optional view to be the parent of the generated hierarchy (if
     *        <em>attachToRoot</em> is true), or else simply an object that
     *        provides a set of LayoutParams values for root of the returned
     *        hierarchy (if <em>attachToRoot</em> is false.)
     * @param attachToRoot Whether the inflated hierarchy should be attached to
     *        the root parameter? If false, root is only used to create the
     *        correct subclass of LayoutParams for the root view in the XML.
     * @return The root View of the inflated hierarchy. If root was supplied and
     *         attachToRoot is true, this is root; otherwise it is the root of
     *         the inflated XML file.
     */
    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) + ")");
        }

        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
           

上面兩個方法是我們經常使用的方法,我們會根據加載的需求決定參數,但這兩個方法都不是最終的實作,我們繼續看最終實作的代碼。

2.2 inflate(parser,root, attachToRoot)

/**
     * Inflate a new view hierarchy from the specified XML node. Throws
     * {@link InflateException} if there is an error.
     * <p>
     * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
     * reasons, view inflation relies heavily on pre-processing of XML files
     * that is done at build time. Therefore, it is not currently possible to
     * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
     * 
     * @param parser XML dom node containing the description of the view
     *        hierarchy.
     * @param root Optional view to be the parent of the generated hierarchy (if
     *        <em>attachToRoot</em> is true), or else simply an object that
     *        provides a set of LayoutParams values for root of the returned
     *        hierarchy (if <em>attachToRoot</em> is false.)
     * @param attachToRoot Whether the inflated hierarchy should be attached to
     *        the root parameter? If false, root is only used to create the
     *        correct subclass of LayoutParams for the root view in the XML.
     * @return The root View of the inflated hierarchy. If root was supplied and
     *         attachToRoot is true, this is root; otherwise it is the root of
     *         the inflated XML file.
     */
    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[];
            mConstructorArgs[] = inflaterContext;
            View result = root;

            try {
                // 1. 從布局檔案中查找根節點
                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();
                // 2. 如果根節點為merge
                if (TAG_MERGE.equals(name)) {
                    // root為null,或者attachToRoot為false,直接抛出異常
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    // 3. 填充布局
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // 4. 根據查找的根節點資訊,構造布局檔案的根view
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    ViewGroup.LayoutParams params = null;
                    // root參數不為空,xml布局檔案中的設定參數才有效
                    if (root != null) {
                        // 在root的基礎上,建立布局參數
                        params = root.generateLayoutParams(attrs);
                        // 如果我們不附加到root上,我們就為temp設定布局參數
                        if (!attachToRoot) {
                            temp.setLayoutParams(params);
                        }
                    }
                    // 5.填充xml檔案根view下的所有子view
                    rInflateChildren(parser, temp, attrs, true);

                    // 6. 如果root不為空,且attachToRoot為true,則将解析的view直接添加到root中
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    // 7. 如果root不為空,且attachToRoot為true,則傳回結果為root,否則傳回我們從xml中解析的view
                    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[] = lastContext;
                mConstructorArgs[] = null;
            }
            return result;
        }
    }
           

從上面的分析中,我們得出如下内容:

1. 我們傳回的view受參數root和attachToRoot控制,root不為空且attachToRoot為true的情況下,傳回值為root,否則就傳回我們從xml解析的view。

2. xml中根view布局屬性是否有效受參數root,root為空的情況下屬性無效,否則有效。

上面的源碼中涉及到幾個方法:

1. rInflate:xml根為merge調用。

2. createViewFromTag:根據view名稱構造view.

3. rInflateChildren:加載所有的子View。

2.3 rInflate

void rInflate(XmlPullParser parser, View parent, Context context,
        AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;

        // 解析merge标簽下的子view資訊
        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            final String name = parser.getName();

            if (TAG_REQUEST_FOCUS.equals(name)) {
                // view.requestFocus()
                parseRequestFocus(parser, parent);
            } else if (TAG_TAG.equals(name)) {
                // view.setTag(key, value);
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                // include不能作為根元素
                if (parser.getDepth() == ) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                // merge必須為根标簽
                throw new InflateException("<merge /> must be the root element");
            } else {
                // 這段代碼是不是似曾相識,我們後面會分析createViewFromTag與rInflateChildren方法
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);
                // 将解析出的view添加到parent中(仔細看代碼,這個parent就是inflate方法中的root)
                viewGroup.addView(view, params);
            }
        }

        if (finishInflate) {
            parent.onFinishInflate();
        }
    }
           

rInflate方法是針對merge方法設計的,實作也比較簡單,在此不做贅述。

2.4 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) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        // ....

        //TAG_1995 = "blink";
        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }

        try {
            View view;
            // mFactory2和mFactory都為空,是以這段代碼擷取到的view為null
            // 為什麼為空?因為構造函數中壓根沒初始化,也沒外部set
            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) {
                // 擷取LayoutInflater持有的context
                final Object lastContext = mConstructorArgs[];
                mConstructorArgs[] = context;
                try {
                    // 判斷名字中是否包含.,包含.證明其為自定義view,不包含.證明其為系統自帶view
                    if (- == name.indexOf('.')) {
                        // 1. 調用方法構造内置view
                        view = onCreateView(parent, name, attrs);
                    } else {
                        // 2. 調用方法構造自定義view
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[] = 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;
        }
    }
           

2.4.1 onCreateView

onCreateView(View parent, String name, AttributeSet attrs)方法用于構造系統内置view。

LayoutInflater.onCreateView

protected View onCreateView(View parent, String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return onCreateView(name, attrs);
    }

    @Override 
    protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
        };

        for (String prefix : sClassPrefixList) {
            try {
                View view = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) {
            }
        }
        return super.onCreateView(name, attrs);
    }

    protected View onCreateView(String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return createView(name, "android.view.", attrs);
    }
           

代碼最終還是調用了createView來建立view。

在建立view時,會嘗試在view名稱前添加字首(其實為包名,這樣就能通過反射來建立執行個體了)來構造view。

2.4.2 createView

public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        // 關于參數prefix的說明:
        // 自定義view解析時prefix為空,内置view解析時prefix不為空

        // 1. 查找緩存的構造方法
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(name);
        }

         // 2. 反射擷取構造方法
        Class<? extends View> clazz = null;
        try {
            if (constructor == null) {
                // 檢查其是不是真的view(是否繼承了view)
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);

                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);
            } else {
                if (mFilter != null) {
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                         // 檢查其是不是真的view(是否繼承了view)
                        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[] args = mConstructorArgs;
            args[] = attrs;
            // 3. 反射構造view執行個體
            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[]));
            }
            // 4. 傳回view
            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);
        }
    }

           

代碼的實作比較簡單,核心内容如下:

1. 根據類名和字首構造類的完整路徑,驗證類是否繼承了View。

2. 反射擷取類的構造方法。

3. 反射構造類執行個體并傳回。

分析到現在,你應該已經明白view是如何從xml被構造出來的了。

2.5 rInflateChildren(parser, temp, attrs, true);

我們回到inflate方法,分析下根view下的子view是如何解析的。

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }

           

rInflateChildren又調用了我們前面分析過的rInflate方法,一步步解析子view并添加到根view中。

分析到這裡,現在整個布局的解析你應該了然于胸了,LayoutInflater的代碼結構是比較清晰明确的。

3. 總結

LayoutInflater在我們日常開中,還是比較常見的,特别是對于inflate方法中幾個參數的使用和了解,筆者曾經就在面試中被問到inflate的使用方法有幾種,通過源碼的分析,相信我們對LayoutInflater會有一個更深入的了解。