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对象的真正创建时机是什么时候?
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容