The main content

Add a little bit of LayoutInflater content from the previous article, explain how AsyncLayoutInflater works, and share a little optimization experience.

How to create layoutInflaters

Ask questions

In the last article I mentioned that the PhoneWindow constructor gets an instance of a LayoutInflater:

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

When we get instances of LayoutInflaters, we pass in a Context. How does that Context relate to layoutinflaters? Next, let’s take a look at the source code with this problem.

Look at the source code with a problem

Look at the LayoutInflater#from method first:

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

We can see that Context#getSystemService is called, which is the Activity Context, and we know from ActivityThread#performLaunchActivity, The Context is actually referring to the ContextImpl object, so let’s continue with the ContextImpl getSystemService method:

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

Here we call SystemServiceRegistry#getSystemService:

private static final HashMap<String, ServiceFetcher<? >> SYSTEM_SERVICE_FETCHERS = new HashMap<>(); public static Object getSystemService(ContextImpl ctx, String name) { ServiceFetcher<? > fetcher = SYSTEM_SERVICE_FETCHERS.get(name);returnfetcher ! = null ? fetcher.getService(ctx) : null; }Copy the code

🧐, remember fetcher. GetService (CTX) and SYSTEM_SERVICE_FETCHERS.

Here’s a quick summary of how SystemServiceRegistry retrieves each “Service” :

1. Use static code blocks to register the ServiceFetcher of each Service

Such as:

static {
    ......
    registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
            new CachedServiceFetcher<LayoutInflater>() {
        @Override
        public LayoutInflater createService(ContextImpl ctx) {
            returnnew PhoneLayoutInflater(ctx.getOuterContext()); }}); . } private static <T> void registerService(String serviceName, Class<T> serviceClass, ServiceFetcher<T> serviceFetcher) { ...... SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher); }Copy the code

You can see that Fecther is added to SYSTEM_SERVICE_FETCHERS. There are many more “services”, including ActivityManager, BluetoothManager, BatteryManager and so on.

2. Create CachedServiceFetcher and record the number

Each time a CachedServiceFetcher is created, a static variable in SystemServiceRegistry is +1:

private static int sServiceCacheSize;
Copy the code

CachedServiceFetcher extends Fetcher, implements Fetcher#getService, lazy creation of Service, and declares the createService abstract method. This method is used to implement specific creation functions in static code blocks, as shown in point 1. Because it is created in a static code block, a “Service” corresponds to a concrete CachedServiceFetcher object, and CachedServiceFetcher records the current value as a subscript. What does this subscript do? See next point.

Fetcher. GetService (CTX)

When fetcher. GetService (CTX) is called, CachedServiceFetcher retrits the object of “Service” from a non-static member of ContextImp:

// The system service cache for the system services that are cached per-ContextImpl.
final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();
Copy the code

SystemServiceRegistry’s static sServiceCacheSize member is used to initialize SystemServiceRegistry’s static sServiceCacheSize member (which will +1) :

/**
 * Creates an array which is used to cache per-Context service instances.
 */
public static Object[] createServiceCache() {
    return new Object[sServiceCacheSize];
}
Copy the code

I get it: Static code blocks create multiple “services”, while static sServiceCacheSize records the number of “services” and CachedServiceFetcher records the subscript of “Service”. When each ContextImpl is created, The non-static mServiceCache is initialized according to sServiceCacheSize. If Context#getSystemService is called, the mServiceCache of the current Context does not have a Service. Just create one and place it under CachedServiceFetcher’s pre-recorded subscript.

So, a ContextImpl object will hold a set of “services” stored in mServiceCache. Of course, if the “Service” is not “get”, it will not be created (so I always use only the Application Context to “getService”, so there is only one instance of each “Service”, reducing the number of objects 😂). To sum up:

conclusion

Does Context have anything to do with layoutInflaters? The relationship is that a Context can hold up to one LayoutInflater instance, and different contexts can hold their own LayoutInflater instances.


AsyncLayoutInflater

When an Activity needs to load pages that are too complex and we want the main thread to “de-load” so it doesn’t lag, we can use AsyncLayoutInflater to do this asynchronously. The source code is annotated as follows:

This is intended for parts of the UI that are created lazily or in response to user interactions. This allows the UI thread to continue to be responsive & animate while the relatively heavy inflate is being performed.

The purpose of this class is to create views asynchronously, so that the interface can respond to the user when a large number of views are loaded and play animations.

An important element of AsyncLayoutInflater

OnInflateFinishedListener

The callback interface, which passes in an object in the inflate method, executes the callback method in that object through the Handler callback to the main thread after asynchronous execution.

BasicInflater

ContextImpl layoutInflaters are phonelayOutInflaters. Basicinflaters are exactly the same as PhonelayOutInflaters. It is also an inner class in AsyncLayoutInflater.

InflateRequest

InflateRequest represents a inflate request, contains all the key information about a inflate, and is an inner class in the AsyncLayoutInflater.

private static class InflateRequest { AsyncLayoutInflater inflater; // AsyncLayoutInflater itself ViewGroup parent; // the inflate method passes in the argument and the parent layout is int resid; // The inflate method passes in the parameter and injects the layout ID View View; / / according to the layout to create a View OnInflateFinishedListener callback. // Callback objectInflateRequest() {}}Copy the code

InflateThread

A thread used to execute asynchronous tasks, which has a blocking queue + a pool of objects. Object pooling is used to reuse InflateRequest objects; The thread’s run method is an infinite loop that takes elements from the blocking queue.

Here’s the interesting thing:

private static class InflateThread extends Thread {
    private static final InflateThread sInstance;
    static {
        sInstance = new InflateThread();
        sInstance.start();
    }

    public static InflateThread getInstance() {
        returnsInstance; }... }Copy the code

Similar to the singleton implementation, the static block is lazy loading and is executed the first time a getInstance is called. If you look at the AsyncLayoutInflater constructor, when an InflateThread starts, That’s when we create the AsyncLayoutInflater:

public AsyncLayoutInflater(@NonNull Context context) {
    mInflater = new BasicInflater(context);
    mHandler = new Handler(mHandlerCallback);
    mInflateThread = InflateThread.getInstance();
}
Copy the code

Inflatethreads are also singletons.

How AsyncLayoutInflater works

AsyncLayoutInflater has very little code and the principle is very simple:

  1. createAsyncLayoutInflaterWhen the asynchronous thread starts and then blocks the queuetakeMethod blocking.
  2. throughinflateMethod incoming constructionViewThe information with the callback object is then retrieved from the object poolInflateRequestObject is updated and placed in a blocking queue.
  3. Blocking queuetakeThe element is retrieved and the asynchronous thread starts executingViewAnd sends a message to theHandler.
  4. HandlerTo call the main thread, executeOnInflateFinishedListenerCallback object, and then reclaimInflateRequestObject.

The process is roughly as follows:

About first frame optimization

Before, our App optimized the first frame of the home page. What was the optimized content? When we jump to the home page, there will be a short period of white screen time. This is because the whole measurement, layout and drawing takes too long. Our View hierarchy was very complex and difficult to refactor, so we came up with AsyncLayoutInflater, but that didn’t solve our problem because it was asynchronous, and the interface didn’t show up until the asynchronous execution was complete. Later, we used a method called ViewStub, which let the title, navigation bar and other elements load first, and then injected the rest of the elements with a delay of 300-500 milliseconds. The entire interface appears to be displayed instantaneously, but some elements fade in.

So that’s a little bit of experience.