LayoutInflater——你應該知道的一點知識

在Android開發中我們經常使用LayoutInflater,俗稱布局填充器,使用它來把布局轉為一個View。一般來講可能采用的方式如下:

  1. 調用其靜態from方法,獲取LayoutInflater對象,然后調用其inflate方法獲取一個View對象
public static LayoutInflater from(Context context)
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
  1. 調用View的靜態inflate方法,獲取對象,不過該方法其實等同于封裝了方式1。
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
    LayoutInflater factory = LayoutInflater.from(context);
    return factory.inflate(resource, root);
}

當然了,其實今天想討論的不是如何加載一個布局的問題,而是一些其它問題,比如:

  1. 每次調用LayoutInflater的from方法都會創建一個新的LayoutInflater對象嗎?
  2. LayoutInflater的from方法中如果傳遞不同的Context對象會有什么不同?
  3. 調用View的getContext() 方法會獲取一個Context,那么這個Context對象具體是誰呢?
  4. 為什么想要在xml中使用View,就必須要有兩個參數的構造方法呢?

下面我們就通過實踐和源碼分析的方式來回答一下上面的這些問題。

LayoutInflater的from方法

先給出結論:我們通過LayoutInflater的from(Context context)方法獲取一個LayoutInflater對象。傳遞不同的Context對象,獲取到的LayoutInflater對象也不同。每一個Activity 都會持有一個 LayoutInflater 對象, 如果每次傳遞的Context對象都是同一個Activity 對象,只會創建一個 LayoutInflater 對象。

驗證:

LayoutInflater fromActivity1 = LayoutInflater.from(this);
LayoutInflater fromActivity2 = LayoutInflater.from(this);
LayoutInflater fromApplication = LayoutInflater.from(getApplication());

Log.e(TAG, "onCreate: layoutInflater: " + fromActivity1);
Log.e(TAG, "onCreate: layoutInflater: " + fromApplication);
Log.e(TAG, "onCreate: layoutInflater: " + fromActivity2);

結果:

onCreate: layoutInflater: com.android.internal.policy.impl.PhoneLayoutInflater@28d214ef
onCreate: layoutInflater: com.android.internal.policy.impl.PhoneLayoutInflater@3adebfc
onCreate: layoutInflater: com.android.internal.policy.impl.PhoneLayoutInflater@28d214ef

在打印的 log 中也可以看到調動LayoutInflater的from方法實際上創建的是它的子類 PhoneLayoutInflater 對象。從結果中也看到傳遞Application 或者Activity,創建的LayoutInflater 不同,而且如果傳遞的是Activity,多次調用只會創建一個LayoutInflater 對象。

源碼分析:

#android.view.LayoutInflater
public static LayoutInflater from(Context context) {
    LayoutInflater LayoutInflater =
            (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null) {
        throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;
}

可以看到調用了Context的getSystemService方法,如果我們傳遞的是Activity,會先調用Activity的getSystemService() 方法。,所以我們先看一下Activity中的getSystemService方法。

# android.app.Activity
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
    if (getBaseContext() == null) {
        throw new IllegalStateException(
                "System services not available to Activities before onCreate()");
    }

    if (WINDOW_SERVICE.equals(name)) {
        return mWindowManager;
    } else if (SEARCH_SERVICE.equals(name)) {
        ensureSearchManager();
        return mSearchManager;
    }
    return super.getSystemService(name);
}

Activity 中的 getSystemService() 方法只是對 WINDOW_SERVICE 和 SEARCH_SERVICE 做了單獨的處理,沒有對 LAYOUT_INFLATER_SERVICE 做什么處理,不過Activity是繼承自 ContextThemeWrapper 的,我們再來看一下ContextThemeWrapper 的 getSystemService() 方法。

#android.view.ContextThemeWrapper
@Override
public Object getSystemService(String name) {
    if (LAYOUT_INFLATER_SERVICE.equals(name)) {
        if (mInflater == null) {
            mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);//1
        }
        return mInflater;
    }
    return getBaseContext().getSystemService(name);
}

可以看到,如果我們獲取LayoutInflater對象,并且當前的 mInflater 變量為空的話就會調用1處的 LayoutInflater.from(getBaseContext()).cloneInContext(this) 方法創建一個LayoutInflater對象,下次再調用的時候直接返回這個對象,不會重復創建。
上面的代碼中getBaseContext()方法獲取的就是該Activity綁定的mBase對象,這個mBase是一個Context對象 ,但Context是一個抽象類,那么它實際上是什么對象呢,這個我們一會再來解釋。

先來看一下如果我們傳遞的是Application對象會發生什么呢?
我們需要知道一點,Application是繼承自ContextWrapper的,來看一下ContextWrapper的 getSystemService() 方法。

# android.content.ContextWrapper
@Override
public Object getSystemService(String name) {
    return mBase.getSystemService(name);
}

直接調用了 mBase 的 getSystemService() 方法,這個mBase也不繞關子了,它就是一個ContextImpl對象。Application、Activity、Service對象在創建完畢之后他們的attach() 方法都會被調用,在 attach() 中會調用 attachBaseContext() 方法,就會給這個mBase 賦值,實際上是一個 ContextImpl對象。這個在我之前寫的幾篇文章中都有涉及到相關內容,想要了解的可以看下Activity啟動過程分析Android四大組件——Service的工作過程分析

通過上面的分析我們會發現,不管是傳遞Activity還是Application實際上都會先調用ContextImpl 的 getSystemService() 方法,直接來看一下它:

# android.app.ContextImpl 
@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}

ContextImpl 調用了 SystemServiceRegistry的getSystemService() 方法,需要說明一點,ContextImpl 的getSystemService() 方法在不同的版本中都會有不同。我們不需要糾結API的不同,側重流程,看到我們想要看的即可。比如現在,我們想要看的是LayoutInflater對象是怎么創建的,跟著代碼繼續看 SystemServiceRegistry的getSystemService() 方法。

# android.app.SystemServiceRegistry
public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}

在 SystemServiceRegistry的getSystemService() 方法中通過一個 SYSTEM_SERVICE_FETCHERS 來根據一個 name 獲取一個 ServiceFetcher 對象,然后調用它的getService 返回一個對象。我們的LayoutInflater 對象應該就是通過ServiceFetcher 獲得的。SYSTEM_SERVICE_FETCHERS 實際上就是一個HashMap。SystemServiceRegistry這個類都是靜態方法和靜態代碼塊,也就是說只要這個類加載的時候就會觸發注冊,SystemServiceRegistry注冊了App運行需要用到的所有服務,有AMS,PMS、NMS,我們需要用到 LAYOUT_INFLATER_SERVICE 也就是LayoutInflater 也在其中。

ServiceFetcher的getService中首先會看看當前是否已經緩存了對應的對象,比如我們想要獲取的LayoutInflater,如果已經有的話會直接返回這個對象,如果沒有的話就會調用其createService() 方法,從下面的代碼中也可以看到,通過PhoneLayoutInflater 的一個參數的構造方法創建了LayoutInflater 對象。

final class SystemServiceRegistry {
    private static final String TAG = "SystemServiceRegistry";

    // Service registry information.
    // This information is never changed once static initialization has completed.
    private static final HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES =
            new HashMap<Class<?>, String>();
    private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();
    private static int sServiceCacheSize;

    // Not instantiable.
    private SystemServiceRegistry() { }

    static {
...
        registerService(Context.ACTIVITY_SERVICE, ActivityManager.class,
                new CachedServiceFetcher<ActivityManager>() {
            @Override
            public ActivityManager createService(ContextImpl ctx) {
                return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
            }});
...
        registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});
...
    }

通過上面的分析我們也可以發現,不管是LayoutInflater的from方法中的 Context,傳遞Activity還是Application實際上都會先調用ContextImpl 的 getSystemService() 方法獲取一個LayoutInflater。這個對象是唯一的,只要是從ContextImpl這獲取的,就只有一個。對于Activity不同的是又調用了 cloneInContext() 方法來獲取一個LayoutInflater對象,這個 cloneInContext() 方法是一個抽象方法,由PhoneLayoutInflater實現:

# com.android.internal.policy.PhoneLayoutInflater
public PhoneLayoutInflater(Context context) {
    super(context);
}

public LayoutInflater cloneInContext(Context newContext) {
    return new PhoneLayoutInflater(this, newContext);
}

protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
    super(original, newContext);
}

 #android.view.LayoutInflater
protected LayoutInflater(LayoutInflater original, Context newContext) {
    mContext = newContext;
    mFactory = original.mFactory;
    mFactory2 = original.mFactory2;
    mPrivateFactory = original.mPrivateFactory;
    setFilter(original.mFilter);
}

從上面的代碼中也可以看到,調用 LayoutInflater的cloneInContext() 方法實際上就是創建了一個新的LayoutInflater對象,并且會把原來對象上的一些屬性拷貝過來,如mFactory、mFactory2、mPrivateFactory、mFilter。這些factory 和 filter 就是為了從xml中加載布局來創建View對象時候用到的。

不過還有一點需要注意,PhoneLayoutInflater的一個構造方法中的Context是由ContextImpl的getOuterContext() 方法獲取到的,那么這個mOuterContext是什么呢?

ContextImpl對象與Application、Activity、Service對象是一一綁定的。我們的ContextImpl 中的mOuterContext對象,這個在Activity中就是當前Activity對象,在Service中就是當前的Service對象。賦值時機是在ContextImpl對象創建后。ContextImpl的創建時機都是在ActivityThead中每次啟動Activity或者Service或者一個新的Application的時候。

LayoutInflater 的創建時機

上面我們講到的是開發者主動調用LayoutInflater的from方法來返回一個對象,但是我們知道一點是當在Activity中調用 setContentView(layoutRes) 方法的時候,會調用到PhoneWindow的setContentView(layoutRes) 方法,在該方法中通過LayoutInflater對象來創建View樹了,那么這個LayoutInflater是什么時候創建的呢?

實際上,LayoutInflater的創建時機就是在Activity對象被創建出來之后。當Activity創建后,其attach方法就會被調用,在這個過程中Activity相關的對象或者屬性就會被綁定,比如,PhoneWindow就是在這個時候被創建出來的。

# android.app.Activity
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, ActivityConfigCallback activityConfigCallback) {
    attachBaseContext(context);

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

    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
        mWindow.setSoftInputMode(info.softInputMode);
    }
 ...
}

在PhoneWindow中有兩個構造函數,一個有3個參數,一個有1個參數。3個參數的首先會調用1個參數的。在這個過程中調用了LayoutInflater的from 方法。來創建LayoutInflater對象。

public PhoneWindow(Context context) {
    super(context);
    mLayoutInflater = LayoutInflater.from(context);
}

注意:這個context對象就是Activity。

LayoutInflater的inflate() 方法

我們使用LayoutInflater的inflate方法的時候一般會使用2個參數或者3個參數的方法,第一個參數代表所要加載的布局,第二個參數是跟ViewGroup,這個參數需要與第3個參數配合使用,attachToRoot如果為true,表示要把當前的布局添加到ViewGroup中,作為其子View。如果為false就是表示只是采用ViewGroup的LayoutParams作為測量的依據。如果ViewGroup為null的話也能夠得到一個View,不過這個View的尺寸有可能不是我們想要的。所以盡量不要使第二個參數為null。如果你只想從布局中加載View,而不想添加到ViewGroup中,可以使用3個參數的方法,attachToRoot為false即可。

下面這兩個方法表示從資源中找到對應的布局xml,然后創建一個XmlResourceParser對象用來解析便簽。解析方式采用的是pull解析。

# android.view.LayoutInflater
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 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();
    }
}

除了上面兩個我們開發者經常使用的方法,還有一個3個參數的inflate方法。第一個參數是XmlPullParser對象,我們把它當做一個解析標簽的對象即可。在這個inflate() 方法中會對所有的view標簽(包括自定義View)、include、merge、ViewStub等進行處理。

在該方法中有一個含有兩個元素的數組 mConstructorArgs,這個就是在使用View的兩個參數的構造方法時用于提供參數的。mConstructorArgs的第一個元素就是inflaterContext,實際上就是創建LayoutInflater對象的mContext。在一個Activity創建的LayoutInflater,mContext指向的就是這個Activity。


public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

        final Context inflaterContext = mContext;
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context) mConstructorArgs[0];
        mConstructorArgs[0] = inflaterContext;
        View result = root;

        try {
            // Look for the root node.
            int type;
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
                // Empty
            }
...
            final String name = parser.getName();
...
            if (TAG_MERGE.equals(name)) {
                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  //1
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                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);
                    if (!attachToRoot) {
                        // Set the layout params for temp if we are not
                        // attaching. (If we are, we use addView, below)
                        temp.setLayoutParams(params);
                    }
                }
...
                // Inflate all children under temp against its context.
                rInflateChildren(parser, temp, attrs, true);
...
                // We are supposed to attach all the views we found (int temp)
                // to root. Do that now.
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }

                // Decide whether to return the root that was passed in or the
                // top view found in xml.
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }
        } 
...
        return result;
    }
}

在注釋1調用了 createViewFromTag() 方法。該方法又會調用其重載方法。在createViewFromTag 重載方法中,會一次判斷mFactory2、mFactory、mPrivateFactory是否會null,如果不為null的就按照優先級調用mFactory2、mFactory、mPrivateFactory的相關方法來創建View,只有前一個返回的View為null的時候,才會由后一個來創建,如果這幾個創建的View都會null的話,就會調用LayoutInflater自身的方法來創建View。

mFactory2、mFactory、mPrivateFactory均有相關的set方法用于設置。
相信看到這里,就應該明白了我們之前講的在clone一個原始的LayoutInflater的作用了,就是可以復用它的mFactory2、mFactory、mPrivateFactory,不需要再重新設置了。

# android.view.LayoutInflater
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");
    }

    // Apply a theme wrapper, if allowed and one is specified.
    if (!ignoreThemeAttr) {
        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        final int themeResId = ta.getResourceId(0, 0);
        if (themeResId != 0) {
            context = new ContextThemeWrapper(context, themeResId);
        }
        ta.recycle();
    }
// 1 開始創建View
    try {
        View view;
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }

        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }

        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(parent, name, attrs);
                } else {
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }

        return view;
...
}

我們看一下Factory、Factory2,Factory2繼承自Factory,它們的接口方法都是用來創建View對象的。

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

我們再來看一下LayoutInflater的createView方法,首先通過View的名稱來獲取它的構造函數Constructor。如果Constructor為null的話就會采用ClassLoader去加載對應的class。需要注意的時候我們在xml填寫的View的名稱比如TextView,實際上是有全路徑名的,即為:android.widget.TextView,類加載器加載必須要使用全路徑名,因此對于TextView這樣的Android系統自帶的空間,需要加上全路徑,因此可以在注釋1處看到使用了prefix。當Class加載成功的時候就會通過mConstructorSignature創建一個兩個參數的構造器,對應的參數是 Context.class, AttributeSet.class。之后就可以看到利用的反射的方式創建View對象。

# android.view.LayoutInflater
public final View createView(String name, String prefix, AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    if (constructor != null && !verifyClassLoader(constructor)) {
        constructor = null;
        sConstructorMap.remove(name);
    }
    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 
            //1
            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 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);
        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;
...
}

通過View的兩個參數的構造函數創建了View對象,第一個參數Context,傳遞的是LayoutInflater自身的mContext,對于Activity中的LayoutInflater,這個mContext就是Activity自身。

public View(Context context, @Nullable AttributeSet attrs)

所以你應該明白了,在View中的Context實際上就是其所在的Activity對象。那么對于Fragment中的View也是這樣的。

Fragment中的LayoutInflater

Fragment中有一個onCreateView() 方法,方法中有一個參數是LayoutInflater,這么LayoutInflater對象是從哪來的呢?

這個對象也是clone而來,而且是由Activity中的LayoutInflater clone而來。Fragment中的LayoutInflater與Activity中的LayoutInflater不是同一個對象,但既然是clone,Fragment中的LayoutInflater中把是Activity中的LayoutInflater中的mFactory、mFactory2、mPrivateFactory、mFilter變量全部賦值給自己相應的成員變量。注意:這是一個淺拷貝,也就是對象中的成員變量拷貝的是引用而不是實例。

我們來分析一下源碼,Fragment是由FragmentManager來管理的,Fragment在創建階段的生命周期方法是由FragmentManager的moveToState() 方法中回調的。代碼很長,我們截取一些關鍵的信息,可以看到在這個方法中,Fragment的生命周期方法被回調。其中我們看到調用了Fragment的performCreateView() 方法,在參數中傳遞了LayoutInflater,而這個是通過調用的Fragment的 performGetLayoutInflater() 方法獲得的。

# android.app.FragmentManagerImpl
@SuppressWarnings("ReferenceEquality")
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
        boolean keepActive) {
    if (DEBUG && false) Log.v(TAG, "moveToState: " + f
        + " oldState=" + f.mState + " newState=" + newState
        + " mRemoving=" + f.mRemoving + " Callers=" + Debug.getCallers(5));

    // Fragments that are not currently added will sit in the onCreate() state.
    if ((!f.mAdded || f.mDetached) && newState > Fragment.CREATED) {
        newState = Fragment.CREATED;
    }
    if (f.mRemoving && newState > f.mState) {
        if (f.mState == Fragment.INITIALIZING && f.isInBackStack()) {
            // Allow the fragment to be created so that it can be saved later.
            newState = Fragment.CREATED;
        } else {
            // While removing a fragment, we can't change it to a higher state.
            newState = f.mState;
        }
    }
    // Defer start if requested; don't allow it to move to STARTED or higher
    // if it's not already started.
    if (f.mDeferStart && f.mState < Fragment.STARTED && newState > Fragment.STOPPED) {
        newState = Fragment.STOPPED;
    }
    if (f.mState <= newState) {
...
        switch (f.mState) {
            case Fragment.INITIALIZING:
...
                    dispatchOnFragmentPreAttached(f, mHost.getContext(), false);
                    f.mCalled = false;
                    f.onAttach(mHost.getContext());
...
            case Fragment.CREATED:
                // This is outside the if statement below on purpose; we want this to run
                // even if we do a moveToState from CREATED => *, CREATED => CREATED, and
                // * => CREATED as part of the case fallthrough above.
                ensureInflatedFragmentView(f);

                if (newState > Fragment.CREATED) {
                    if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
                    if (!f.mFromLayout) {
                        ViewGroup container = null;
                 ...
                            container = mContainer.onFindViewById(f.mContainerId);
                            if (container == null && !f.mRestored) {
                                String resName;
                                try {
                                    resName = f.getResources().getResourceName(f.mContainerId);
...
                        }
                        f.mContainer = container;
                        f.mView = f.performCreateView(f.performGetLayoutInflater(
                                f.mSavedFragmentState), container, f.mSavedFragmentState);
                        if (f.mView != null) {
                            f.mView.setSaveFromParentEnabled(false);
                            if (container != null) {
                                container.addView(f.mView);
                            }
                            if (f.mHidden) {
                                f.mView.setVisibility(View.GONE);
                            }
                            f.onViewCreated(f.mView, f.mSavedFragmentState);
                            dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState,
                                    false);
                            // Only animate the view if it is visible. This is done after
                            // dispatchOnFragmentViewCreated in case visibility is changed
                            f.mIsNewlyAdded = (f.mView.getVisibility() == View.VISIBLE)
                                    && f.mContainer != null;
                        }
                    }

                    f.performActivityCreated(f.mSavedFragmentState);
                    dispatchOnFragmentActivityCreated(f, f.mSavedFragmentState, false);
                    if (f.mView != null) {
                        f.restoreViewState(f.mSavedFragmentState);
                    }
                    f.mSavedFragmentState = null;
                }
                // fall through
...
                }
        }
    }    
...
}

來看一下Fragment的 performGetLayoutInflater() 方法,在該方法中又調用了其onGetLayoutInflater() 方法。

# android.support.v4.app.Fragment
LayoutInflater performGetLayoutInflater(Bundle savedInstanceState) {
    LayoutInflater layoutInflater = onGetLayoutInflater(savedInstanceState);
    mLayoutInflater = layoutInflater;
    return mLayoutInflater;
}

public LayoutInflater getLayoutInflater(Bundle savedFragmentState) {
    if (mHost == null) {
        throw new IllegalStateException("onGetLayoutInflater() cannot be executed until the "
                + "Fragment is attached to the FragmentManager.");
    }
    LayoutInflater result = mHost.onGetLayoutInflater();
    getChildFragmentManager(); // Init if needed; use raw implementation below.
    LayoutInflaterCompat.setFactory2(result, mChildFragmentManager.getLayoutInflaterFactory());
    return result;
}

在Fragment的getLayoutInflater() 方法中,通過調用 mHost.onGetLayoutInflater()獲取了一個LayoutInflater對象。mHost 就是FragmentHostCallback對象,來看一下它的onGetLayoutInflater() 方法:

# android.support.v4.app.FragmentHostCallback
public LayoutInflater onGetLayoutInflater() {
    return (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

FragmentHostCallback的onGetLayoutInflater() 方法實際上就是調用了mContext的getSystemService方法,這個就跟我們前面分析的通過LayoutInflater的from方法是一個意思,mContext實際上就是Fragment所在的Activity。再加上之前的分析我們可以得出一個結論,Fragment中的LayoutInflater是由Activity中的LayoutInflater clone而來,它們不是同一個對象,不過Fragment中的LayoutInflater把Activity的LayoutInflater設置的一些factory copy過來,相當于它們使用的是同樣的工廠。

在Fragmen的getLayoutInflater方法中調用了 LayoutInflaterCompat.setFactory2(result, mChildFragmentManager.getLayoutInflaterFactory()); ,其實就調用LayoutInflater的setFactory2() 方法。這個方法我們下一小結再講。

LayoutInflater的setFactory2() 方法

LayoutInflater的setFactory2方法很有意思,如果原來LayoutInflater上面的mFactory為null,就是把實際上mFactory、mFactory2均賦值為當前設置的factory。如果不為null創建了一個FactoryMerger對象賦值給mFactory、mFactory2。

# android.view.LayoutInflater
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;
    if (mFactory == null) {
        mFactory = mFactory2 = factory;
    } else {
        mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
    }
}

假設我們之前在Activity中設置了mFactory2,那么當在Fragment中的LayoutInflater調用setFactory2方法的時候,mFactory 、mFactory2 均不為空,那么就會走到else里面,也就是說創建了一個FactoryMerger對象。FactoryMerger實際上實現了Factory2。

private static class FactoryMerger implements Factory2 {
    private final Factory mF1, mF2;
    private final Factory2 mF12, mF22;

    FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
        mF1 = f1;
        mF2 = f2;
        mF12 = f12;
        mF22 = f22;
    }

    public View onCreateView(String name, Context context, AttributeSet attrs) {
        View v = mF1.onCreateView(name, context, attrs);
        if (v != null) return v;
        return mF2.onCreateView(name, context, attrs);
    }

    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
                : mF1.onCreateView(name, context, attrs);
        if (v != null) return v;
        return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
                : mF2.onCreateView(name, context, attrs);
    }
}

想到沒有,當Fragment中的LayoutInflater中的Factory方法去執行的時候,實際上先執行的是mF12和mF1的onCreateView,我們知道在Fragment中設置的是mChildFragmentManager.getLayoutInflaterFactory(),mChildFragmentManager是FragmentManager,我們在Activity中通過getSupportFragmentManager獲取也是FragmentManager對象。FragmentManager的實現類FragmentManagerImpl實現了Factory2接口,是用來解析fragment標簽的。
可以看到,如果標簽不是fragment,實際上它還是會直接返回的。

# android.support.v4.app.FragmentManager$FragmentManagerImpl
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
   if (!"fragment".equals(name)) {
       return null;
   }
   …
   return fragment.mView;
}

其實想想,這樣的設計真的挺好,Fragment會復用了Activity的Factory對象,只是在解析fragment標簽的時候,采用用FragmentManagerImpl來解析。

總結

相信看完上面的內容,你對LayoutInflater應該有一個比較全面的了解了,具體怎么靈活運用就要看你的需求了。比如你想完成一個換膚框架,那么你首先肯定要獲得所有需要換膚的控件,此時LayoutInflater的Factory2就可以派上用場了。通過給LayoutInflater設置Factory2,可以自己處理View的創建邏輯,獲取相關的View,當你需要換膚的時候,給這些View設置新的屬性即可。

我們再來回顧一些問題,現在你能夠自己解答了嗎?

  1. 每次調用LayoutInflater的from方法都會創建一個新的LayoutInflater對象嗎?
  2. LayoutInflater的from方法中如果傳遞不同的Context對象會有什么不同?
  3. 調用View的getContext() 方法會獲取一個Context,那么這個Context對象具體是誰呢?
  4. 為什么想要在xml中使用View,就必須要有兩個參數的構造方法呢?
  5. Fragment中有一個onCreateView方法,有一個參數是LayoutInflater,這么LayoutInflater對象是從哪來的呢?
  6. LayoutInflater對象的真正創建時機是什么時候?
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容