Like attention, no more lost, your support means a lot to me!

🔥 Hi, I’m Chouchou. GitHub · Android-Notebook has been included in this article. Welcome to grow up with Chouchou Peng. (Contact information at GitHub)


preface

  • The LayoutInflater class is often used in Android UI development. Its basic function is to parse XML layout files into View/View trees. In addition to basic layout parsing, LayoutInflaters can be used for dynamic peels, view transformations, attribute transformations, and more.
  • In this article, I’ll take you through the source code for LayoutInflater. In addition, don’t miss the suggestions at the end of the article, if you can help, please be sure to like and follow, this is really very important to me.

Related articles

  • The Android | a process how many the Context object (right not much)”
  • The Android | belt you explore LayoutInflater layout principle”
  • The Android | View & fragments & Window of getContext () must return to the Activity?”
  • The Android | about from Android: text to TextView process”

directory


1. Obtain LayoutInflater objects

@SystemService(Context.LAYOUT_INFLATER_SERVICE)
public abstract class LayoutInflater {
    ...
}
Copy the code

First, you need to get instances of LayoutInflaters. Since LayoutInflaters are abstract classes, you can’t create objects directly, so here’s a summary of how to get LayoutInflaters. Details are as follows:

  • 1. View.inflate(...)
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
    LayoutInflater factory = LayoutInflater.from(context);
    return factory.inflate(resource, root);
}
Copy the code
  • 2. Activity#getLayoutInflater()
public LayoutInflater getLayoutInflater() {
    return getWindow().getLayoutInflater();
}
Copy the code
  • 3. PhoneWindow#getLayoutInflater()
private LayoutInflater mLayoutInflater;

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

public LayoutInflater getLayoutInflater() {
    return mLayoutInflater;
}
Copy the code
  • 4. LayoutInflater#from(Context)
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;
}
Copy the code

As you can see, the first three methods end up with LayoutInflater#from(Context), which is actually the most common method. Now let’s look at getSystemService(…) Logic within:

ContextImpl.java

@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}
Copy the code

SystemServiceRegistry.java

private static final Map<String, ServiceFetcher<? >> SYSTEM_SERVICE_FETCHERS = new ArrayMap<String, ServiceFetcher<? > > (); static { ... 1. Register context.layout_inflater_service and service fetcher PhoneLayoutInflater registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class, New CachedServiceFetcher<LayoutInflater>() {@override public LayoutInflater createService(ContextImpl CTX) { Activity Return new PhoneLayoutInflater(ctx.getouterContext ()); }}); . } public static Object getSystemService(ContextImpl CTX, String name) {ServiceFetcher<? > fetcher = SYSTEM_SERVICE_FETCHERS.get(name); return fetcher ! = null ? fetcher.getService(ctx) : null; } Private static <T> void registerService(String serviceName, Class<T> serviceClass, ServiceFetcher<T> serviceFetcher) { SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher); } static abstract interface ServiceFetcher<T> {T getService(ContextImpl CTX); }Copy the code

As you can see, ContextImpl uses SystemServiceRegistry internally to retrieve the service object. The logic is not complicated:

1, static code block registered name – ServiceFetcher mapping 2, obtain name ServiceFetcher 3, ServiceFetcher create object

There are three subclasses of ServiceFetcher, and their getSystemService() is thread-safe. The main differences are in the singleton scope, as follows:

ServiceFetcher subclass Singleton scope describe For example,
CachedServiceFetcher ContextImpl domain / LayoutInflater, LocationManager, etc. (maximum)
StaticServiceFetcher Process domain / InputManager, JobScheduler, etc
StaticApplicationContextServiceFetcher Process domain Use ApplicationContext to create the service ConnectivityManager

For LayoutInflater, the service getter is a subclass of CachedServiceFetcher, and the final service object is PhoneLayoutInflater.

Here’s an important point. This code is very subtle, so be aware of it:

 return new PhoneLayoutInflater(ctx.getOuterContext());
Copy the code

LayoutInflater.java

public Context getContext() {
    return mContext; 
}

protected LayoutInflater(Context context) {
    mContext = context;
    initPrecompiledViews();
}
Copy the code

As you can see, PhoneLayoutInflater is instantiated using getOuterContext(), which is a ContextImpl proxy object. That is, in the Activity/Fragment/View/Dialog, get the LayoutInflater#getContext() and return the Activity.

Summary:

  • 1. Obtain LayoutInflater objects only throughLayoutInflater.from(context)Internally delegate toContext#getSystemService(...), thread safety;
  • 2. Using the same Context object, the obtained LayoutInflater is a singleton.
  • 3. The implementation class for LayoutInflater is PhoneLayoutInflater.

2. inflate(…) Main process source code analysis

In the previous section, we looked at the process of getting the LayoutInflater object, and now we can call the inflate() for layout resolution. LayoutInflater#inflate(…) There are multiple overloaded methods that eventually call:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); View View = tryInflatePrecompiled(Resource, res, root, attachToRoot); if (view ! = null) { return view; } 2. Construct XmlPull parser XmlResourceParser parser = res.getLayout(resource); {3. Parse return inflate(parser, root, attachToRoot); } finally { parser.close(); }}Copy the code
  1. tryInflatePrecompiled(...)Parse the precompiled layout, which I’ll talk about later;
  2. Construct the XmlPull parser XmlResourceParser
  3. Performing parsing is the main process of parsing

Tip: HERE, I’ve stripped out the XmlPull code, leaving only the logic we care about:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { 1. View result = root; Final String Name = parser.getName(); 3. The < merge > if (TAG_MERGE equals (name)) {3.1 if abnormal (root = = null | |! attachToRoot) { throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } 3.2 Perform recursive parsing rInflate(parser, root, inflaterContext, attrs, false); } else {4.1 Create final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root ! = null) {4.2 create matching LayoutParams params = root. GenerateLayoutParams (attrs); if (! AttachToRoot) {4.3 if attachToRoot is false, setLayoutParams temp.setlayoutparams (params); }} rInflateChildren(Parser, temp, attrs, true); 6. AttachToRoot is true, addView() if (root! = null && attachToRoot) { root.addView(temp, params); } 7. Root is empty or attachToRoot is false, return temp if (root = = null | |! attachToRoot) { result = temp; } } return result; } -> 3.2 void rInflate(XmlPullParser parser parser, View parent, Context Context, AttributeSet attrs, Boolean finishInflate) {while(parser does not end) {if (tag_include.equals (name)) {1) <include> if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { 2) <merge> throw new InflateException("<merge /> must be the root element"); } else {3) Create View final View View = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); 4) Recursive rInflateChildren(Parser, View, attrs, true); 5) Add to view tree viewgroup. addView(view, params); }}} -> final void rInflateChildren(XmlPullParser Parser, View parent, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { rInflate(parser, parent, parent.getContext(), attrs, finishInflate); }Copy the code

About

&

, I’ll talk later. For different cases of root & attachToRoot parameters, the corresponding output is different, which can be summarized as a figure:


CreateViewFromTag () : from View to View

In section 2 of the main flow code, we use createViewFromTag(), which is responsible for creating the View object:

CreateViewFromTag (View parent, String name, Context Context, AttributeSet attrs, Boolean ignoreThemeAttr) {1. Apply ContextThemeWrapper to support Android: Theme 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(); } Factory2 / Factory2 / Factory2 / Factory2 / Factory2 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) {if (-1 == name.indexof ('.')) {4.1 <tag> View = createView(name, null, attrs); } } return view; } -> 4.2 <tag> Constructor method signature static final Class<? >[] mConstructorSignature = new Class[] { Context.class, AttributeSet.class}; Private static final HashMap<String, Constructor<? extends View>> sConstructorMap = new HashMap<String, Constructor<? extends View>>(); Public final View createView(String name, String prefix, AttributeSet attrs) {Constructor<? extends View> constructor = sConstructorMap.get(name); if (constructor ! = null && ! verifyClassLoader(constructor)) { constructor = null; sConstructorMap.remove(name); } Class<? extends View> clazz = null; Clazz = McOntext.getclassloader ().loadClass(prefix!) clazz = McOntext.getclass ().loadClass(prefix! = null ? (prefix + name) : name).asSubclass(View.class); Constructor = clazz.getconstructor (mConstructorSignature); constructor.setAccessible(true); Sconstructormap. put(name, constructor); } 3) Instantiate final View object 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])); } return view; } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- > 4.1 < tag > no. PhoneLayoutInflater.java private static final String[] sClassPrefixList = { "android.widget.", "android.webkit.", "android.app." }; Protected View onCreateView(String name, AttributeSet attrs) {for (String prefix: sClassPrefixList) { View view = createView(name, prefix, attrs); if (view ! = null) { return view; } } return super.onCreateView(name, attrs); }Copy the code
    1. Apply ContextThemeWrapper to support itandroid:theme, which handles setting themes for a particular View;
    1. useFactory2 / FactoryInstantiating a View is like intercepting, which I’ll talk about later;
    1. usemPrivateFactoryInstantiating a View is like intercepting, which I’ll talk about later;
    1. Calling LayoutInflater’s own logic can be divided into:
    • 4.1 no.This is processingThe < linearlayout >, < TextView >And so on, try to join three path prefixes in turn, enter 3.2 Instantiating View
    • In the 4.2.To actually instantiate a View, there are four main steps:
    1) Cached constructor 2) New constructor 3) instantiate View object 4) Special handling of ViewStubCopy the code

Summary:

  • Using the Factory2 interface, you can intercept the steps to instantiate a View object;
  • Instantiate views in the following order: Factory2 / Factory -> mPrivateFactory -> PhoneLayoutInflater;
  • The View object is instantiated using reflection, while the constructor object is cached.


4. Factory2 interface

Now let’s talk about the Factory2 interface. As mentioned in the previous section, Factory2 can intercept steps to instantiate views. There are two methods that can be set in LayoutInflater: LayoutInflater

Public void setFactory2(Factory2 factory) {if (factorySet) { 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); @hide public void setPrivateFactory(Factory2 Factory) {if (mPrivateFactory == null) {mPrivateFactory = factory; } else { mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory); }}Copy the code

Now, let’s look at where the two methods are called in the source code:

4.1 setFactory2 ()

In AppCompatActivity & AppCompatDialog, the relevant source code is simplified as follows:

AppCompatDialog.java

Override protected void onCreate(Bundle savedInstanceState) {Factory2 getDelegate(). InstallViewFactory (); super.onCreate(savedInstanceState); getDelegate().onCreate(savedInstanceState); }Copy the code

AppCompatActivity.java

@Override protected void onCreate(@Nullable Bundle savedInstanceState) { final AppCompatDelegate delegate = getDelegate(); Set the Factory2 delegate. InstallViewFactory (); delegate.onCreate(savedInstanceState); If (delegate.applydaynight () && mThemeId! = 0) { if (Build.VERSION.SDK_INT >= 23) { onApplyThemeResource(getTheme(), mThemeId, false); } else { setTheme(mThemeId); } } super.onCreate(savedInstanceState); }Copy the code

AppCompatDelegateImpl.java

public void installViewFactory() { LayoutInflater layoutInflater = LayoutInflater.from(mContext); If (layoutInflater. GetFactory () = = null) {concerns: Set the Factory2 = this (AppCompatDelegateImpl) LayoutInflaterCompat. SetFactory2 (layoutInflater, this); } else { if (! (layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) { Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed" + " so we can not install AppCompat's"); }}}Copy the code

LayoutInflaterCompat.java

public static void setFactory2(@NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) { inflater.setFactory2(factory); if (Build.VERSION.SDK_INT < 21) { final LayoutInflater.Factory f = inflater.getFactory(); if (f instanceof LayoutInflater.Factory2) { forceSetFactory2(inflater, (LayoutInflater.Factory2) f); } else { forceSetFactory2(inflater, factory); }}}Copy the code

As you can see, when AppCompatDialog & AppCompatActivity is initialized, an interceptor is set to AppCompatDelegateImpl via setFactory2() :

AppCompatDelegateImpl.java

Class AppCompatDelegateImpl extends AppCompatDelegate implements menuBuilder.callback LayoutInflater.Factory2 { @Override public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) { return createView(parent, name, context, attrs); } @Override public View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs) { if (mAppCompatViewInflater == null) { mAppCompatViewInflater = new AppCompatViewInflater(); }} to AppCompatViewInflater handle return mAppCompatViewInflater. CreateView (...). }Copy the code

AppCompatViewInflater is similar to the core process of LayoutInflater, the main difference being that the former will parse tags such as

into AppCompatTextView objects:

AppCompatViewInflater.java

final View createView(...) {... switch (name) { case "TextView": view = createTextView(context, attrs); break; . default: view = createView(context, name, attrs); } return view; } @NonNull protected AppCompatTextView createTextView(Context context, AttributeSet attrs) { return new AppCompatTextView(context, attrs); }Copy the code

4.2 setPrivateFactory ()

SetPrivateFactory () is a hide method called in an Activity.

Activity.java

final FragmentController mFragments = FragmentController.createController(new HostCallbacks()); final void attach(Context context, ActivityThread aThread,...) { attachBaseContext(context); mFragments.attachHost(null /*parent*/); mWindow = new PhoneWindow(this, window, activityConfigCallback); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); Focus: set Factory2 mWindow. GetLayoutInflater (). SetPrivateFactory (this); . }Copy the code

As you can see, the Factory2 set here is actually the Activity itself (this), which means that the Activity also implements Factory2:

public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2,... { public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { if (!" fragment".equals(name)) { return onCreateView(name, context, attrs); } return mFragments.onCreateView(parent, name, context, attrs); }}Copy the code

The original

tag is handled by Factory2 set here, about FragmentController#onCreateView(…) Internal how to generate fragments and return to the View of logic, we discuss in this article, please attention: the Android | examination site full, take you all-round diagram Fragment source “.

Summary:

  • Use setFactory2() and setPrivateFactory() to set up the Factory2 interface (interceptor), where the same LayoutInflater’ssetFactory2()SetPrivateFactory () is a hide method;
  • AppCompatDialog & AppCompatActivity is called when it initializessetFactory2(), will be some<tag>convertAppCompatVersion;
  • Is called when the Activity initializessetPrivateFactory()Used to deal with<fragment>The label.


5. <include> & <merge> & <ViewStub>


&

&


5.1 <include> Layout reuse

5.2 <merge> Lower the layout level

5.3 < ViewStub > Layout lazy loading

Editting…


6. Summary

  • Takes an exam the advice
  1. Understand how to obtain LayoutInflater objects, and understand the differences between the three getSystemService() singletons. Context is the most common domain, and LayoutInflater belongs to Context.
  2. Focus on understanding the core flow of LayoutInflater layout parsing;
  3. Factory2 is a useful interface that requires mastering the art of intercepting layout parsing with setFactory2().


Recommended reading

  • Cryptography | is Base64 encryption algorithm?
  • Interview questions | back algorithm framework to solve problems
  • The interview questions | list questions summary algorithm
  • Java | show you understand the ServiceLoader principle and design idea
  • Android | interview will ask Handler, are you sure you don’t look at it?
  • Android | show you understand NativeAllocationRegistry principle and design idea
  • Computer composition principle | Unicode utf-8 and what is the relationship?
  • | why floating-point arithmetic of computer constitute principle is not accurate? (Ali written test)
  • Computer network | graphic DNS & HTTPDNS principle

Thank you! Your “like” is the biggest encouragement for me! Welcome to attentionPeng XuruiThe lot!