Android LayoutInflater from(@UiContext Context context)

LayoutInflater from(Context context) 流程
LayoutInflaterfromprocess

LayoutInflater from() flowchart

  The code of Android LayoutInflater from(@UiContext Context context) is as follows:

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

  Call the getSystemService() method of the parameter context to get the instance LayoutInflater, and then return it.
  The parameter context is generally an Activity instance, so look at the activity's getSystemService(), as follows:

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

  The parameter name is currently Context.LAYOUT_INFLATER_SERVICE, so continue to call the parent classThe getSystemService() method of ContextThemeWrapper is as follows:

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

  Find the corresponding parameter LAYOUT_INFLATER_SERVICE here. When this method is called for the first time, the member variable mInflater is null, so the LayoutInflater.from() method is called again, but note that the parameters are different. The parameter is getBaseContext(), and then The cloneInContext(this) method is called to make a copy.
  It can also be seen from the above that the getSystemService() method of Activity is processed separately when the parameters are WINDOW_SERVICE, SEARCH_SERVICE, and LAYOUT_INFLATER_SERVICE. When it is other parameters, it calls getBaseContext().getSystemService(name).
  Activity's getBaseContext() gets its member variable mBase. Looking at the code, mBase is also of type Context, and Activity is also of type Context. What is the difference between them? In fact, mBase is an instance of ContextImpl, and the functions in the Context class of Activity are all implemented through mBase. Here, the proxy mode is used.

ContextImpl's getSystemService() method

  Then look at the getSystemService() method of ContextImpl, as follows:

    @Override
    public Object getSystemService(String name) {
    
    
        if (vmIncorrectContextUseEnabled()) {
    
    
            // Check incorrect Context usage.
            if (WINDOW_SERVICE.equals(name) && !isUiContext()) {
    
    
                final String errorMessage = "Tried to access visual service "
                        + SystemServiceRegistry.getSystemServiceClassName(name)
                        + " from a non-visual Context:" + getOuterContext();
                final String message = "WindowManager should be accessed from Activity or other "
                        + "visual Context. Use an Activity or a Context created with "
                        + "Context#createWindowContext(int, Bundle), which are adjusted to "
                        + "the configuration and visual bounds of an area on screen.";
                final Exception exception = new IllegalAccessException(errorMessage);
                StrictMode.onIncorrectContextUsed(message, exception);
                Log.e(TAG, errorMessage + " " + message, exception);
            }
        }
        return SystemServiceRegistry.getSystemService(this, name);
    }

  The getSystemService() method of SystemServiceRegistry is called, as follows:

    /**
     * Gets a system service from a given context.
     * @hide
     */
    public static Object getSystemService(ContextImpl ctx, String name) {
    
    
        if (name == null) {
    
    
            return null;
        }
        final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        if (fetcher == null) {
    
    
            if (sEnableServiceNotFoundWtf) {
    
    
                Slog.wtf(TAG, "Unknown manager requested: " + name);
            }
            return null;
        }

        final Object ret = fetcher.getService(ctx);
        if (sEnableServiceNotFoundWtf && ret == null) {
    
    
            // Some services do return null in certain situations, so don't do WTF for them.
            switch (name) {
    
    
                case Context.CONTENT_CAPTURE_MANAGER_SERVICE:
                case Context.APP_PREDICTION_SERVICE:
                case Context.INCREMENTAL_SERVICE:
                case Context.ETHERNET_SERVICE:
                    return null;
            }
            Slog.wtf(TAG, "Manager wrapper not available: " + name);
            return null;
        }
        return ret;
    }

  SystemServiceRegistry's getSystemService() first calls SYSTEM_SERVICE_FETCHERS.get(name) to get the ServiceFetcher instance fetcher, then calls the fetcher's getService() method, then gets the specific result instance, and finally returns the result, which is the final LayoutInflater instance.
  SYSTEM_SERVICE_FETCHERS is of ArrayMap<String, ServiceFetcher<?>> type, the value is obtained through the key value, and the value type is ServiceFetcher. Then in this ArrayMap, when did it start to register it.
  Look at the getService(ContextImpl ctx) method of the ServiceFetcher class again,

    static abstract interface ServiceFetcher<T> {
    
    
        T getService(ContextImpl ctx);
    }

  ServiceFetcher is an interface. For LayoutInflater, the fetcher obtained in SYSTEM_SERVICE_FETCHERS is of CachedServiceFetcher type, and its getService(ContextImpl ctx) implementation will be analyzed later

LayoutInflater is registered in SYSTEM_SERVICE_FETCHERS

  Its related registration is in the static code block of SystemServiceRegistry, which will be executed as soon as the SystemServiceRegistry class is loaded. Look at the relevant code:

    static {
    
    
        //CHECKSTYLE:OFF IndentationCheck
        registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
                new CachedServiceFetcher<AccessibilityManager>() {
    
    
            @Override
            public AccessibilityManager createService(ContextImpl ctx) {
    
    
                return AccessibilityManager.getInstance(ctx);
            }});
            …………
            …………
        registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
    
    
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
    
    
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});     
            …………
            …………
        registerService(Context.DISPLAY_HASH_SERVICE, DisplayHashManager.class,
                new CachedServiceFetcher<DisplayHashManager>() {
    
    
                    @Override
                    public DisplayHashManager createService(ContextImpl ctx) {
    
    
                        return new DisplayHashManager();
                    }});

            …………
            …………
    }       

  In the static initialization block, many related ServiceFetchers are registered through the registerService() method. Mainly look at the related LAYOUT_INFLATER_SERVICE. Look at the registerService() method:

    /**
     * Statically registers a system service with the context.
     * This method must be called during static initialization only.
     */
    private static <T> void registerService(@NonNull String serviceName,
            @NonNull Class<T> serviceClass, @NonNull ServiceFetcher<T> serviceFetcher) {
    
    
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
        SYSTEM_SERVICE_CLASS_NAMES.put(serviceName, serviceClass.getSimpleName());
    }

  The static member SYSTEM_SERVICE_NAMES of the SystemServiceRegistry class is an ArrayMap<Class<?>, String> type, and the name corresponding to the corresponding registration service can be obtained through the Class; SYSTEM_SERVICE_FETCHERS is an ArrayMap<String, ServiceFetcher<?>> type, and the corresponding name can be obtained through the registered name ServiceFetcher instance; SYSTEM_SERVICE_CLASS_NAMES is ArrayMap<String, String> type, you can get the corresponding class name through the registered name.
  For LayoutInflater, here you can see that the registered corresponding ServiceFetcher is the CachedServiceFetcher type.

CachedServiceFetcher的getService(ContextImpl ctx)

  Take a look at the first piece of code:

        public final T getService(ContextImpl ctx) {
    
    
            final Object[] cache = ctx.mServiceCache;
            final int[] gates = ctx.mServiceInitializationStateArray;
            boolean interrupted = false;

            T ret = null;

  First obtain the member variable mServiceCache of the parameter ctx, and its assignment mServiceCache = SystemServiceRegistry.createServiceCache(). And createServiceCache() of SystemServiceRegistry:

    public static Object[] createServiceCache() {
    
    
        return new Object[sServiceCacheSize];
    }

  It generates a new Object array every time, the size is sServiceCacheSize. This size is that sServiceCacheSize will be increased by 1 every time the registerService() function described above is called to generate a CachedServiceFetcher instance. It is the number of registered CachedServiceFetcher instances.
  Then getService(ContextImpl ctx) calls ctx.mServiceInitializationStateArray to get the int array gates. Its size is equal to the size of the obtained array cache.
  Continue to initialize interrupted to false, and ret to null.
  Let's look at the second piece of code:

            for (;;) {
    
    
                boolean doInitialize = false;
                synchronized (cache) {
    
    
                    // Return it if we already have a cached instance.
                    T service = (T) cache[mCacheIndex];
                    if (service != null) {
    
    
                        ret = service;
                        break; // exit the for (;;)
                    }

                    // If we get here, there's no cached instance.

                    // Grr... if gate is STATE_READY, then this means we initialized the service
                    // once but someone cleared it.
                    // We start over from STATE_UNINITIALIZED.
                    // Similarly, if the previous attempt returned null, we'll retry again.
                    if (gates[mCacheIndex] == ContextImpl.STATE_READY
                            || gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) {
    
    
                        gates[mCacheIndex] = ContextImpl.STATE_UNINITIALIZED;
                    }

                    // It's possible for multiple threads to get here at the same time, so
                    // use the "gate" to make sure only the first thread will call createService().

                    // At this point, the gate must be either UNINITIALIZED or INITIALIZING.
                    if (gates[mCacheIndex] == ContextImpl.STATE_UNINITIALIZED) {
    
    
                        doInitialize = true;
                        gates[mCacheIndex] = ContextImpl.STATE_INITIALIZING;
                    }
                }

  After entering a for loop, declare a doInitialize with a value of false, then acquire the lock of the variable cache, and then go to the cache to see if there is already a cache. If it exists, the cached result will be returned.
  The cache lock is used here to avoid confusion in the setting of gates[mCacheIndex] in the case of multi-threading. gates is an int array, and gates[mCacheIndex] is used here to indicate which step the current service instance generation process corresponding to ContextImpl has reached. The initial state is ContextImpl.STATE_UNINITIALIZED. ContextImpl.STATE_READY indicates that the corresponding service has been successfully generated before. In LayoutInflater, the corresponding LayoutInflater instance has been generated and cached; ContextImpl.STATE_NOT_FOUND indicates that the corresponding service was not found when the corresponding service was generated before.
  If the value of gates[mCacheIndex] is ContextImpl.STATE_READY, it means that the generated cache is cleared. When the value of gates[mCacheIndex] is ContextImpl.STATE_READY or ContextImpl.STATE_NOT_FOUND, first set gates[mCacheIndex] = ContextImpl.STATE_UNINITIALIZED. One needs to be regenerated.
  The code then judges that if gates[mCacheIndex] == ContextImpl.STATE_UNINITIALIZED, it will set doInitialize = true and gates[mCacheIndex] = ContextImpl.STATE_INITIALIZING. doInitialize = true, which means it has been initialized, and it is used to achieve multi-threaded competition. Only a single thread can execute createService() to generate services. gates[mCacheIndex] = ContextImpl.STATE_INITIALIZING, representing the process of executing createService(). gates[mCacheIndex] is ContextImpl.STATE_INITIALIZING, which can also prevent threads from executing createService(). When other thread codes go to judge gates[mCacheIndex] == ContextImpl.STATE_UNINITIALIZED, set doInitialize = true, resulting in multiple threads Execute createService() to generate a service.
  Then look at the third piece of code:

                if (doInitialize) {
    
    
                    // Only the first thread gets here.

                    T service = null;
                    @ServiceInitializationState int newState = ContextImpl.STATE_NOT_FOUND;
                    try {
    
    
                        // This thread is the first one to get here. Instantiate the service
                        // *without* the cache lock held.
                        service = createService(ctx);
                        newState = ContextImpl.STATE_READY;

                    } catch (ServiceNotFoundException e) {
    
    
                        onServiceNotFound(e);

                    } finally {
    
    
                        synchronized (cache) {
    
    
                            cache[mCacheIndex] = service;
                            gates[mCacheIndex] = newState;
                            cache.notifyAll();
                        }
                    }
                    ret = service;
                    break; // exit the for (;;)
                }

  First judge doInitialize, and then call createService(ctx) to generate the service. After createService(ctx) is successful, the state will be set to ContextImpl.STATE_READY. If an exception occurs, the state will be ContextImpl.STATE_NOT_FOUND. Finally, in the finally code, the generated service service will be cached, and the status will be set to gates[mCacheIndex]. It can be seen that this value may be ContextImpl.STATE_READY or ContextImpl.STATE_NOT_FOUND.
  In the finally code, synchronized is also used to lock, and cache.notifyAll() will be called to wake up the thread waiting on the lock. This wait will be seen from the following code to see the thread waiting for the lock.
  After generating the service, assign it to ret and jump out of the for loop.

createService() generate service

  The implementation of createService() is in the registration block, take a look:

			public LayoutInflater createService(ContextImpl ctx) {
    
    
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }

  A PhoneLayoutInflater object is directly generated, so what is obtained through LayoutInflater from(Context context) is a PhoneLayoutInflater instance. Its parameter is the getOuterContext() of the ContextImpl class object, which points to an Activity instance.
  Then look at the fourth piece of code of getService():

                synchronized (cache) {
    
    
                    while (gates[mCacheIndex] < ContextImpl.STATE_READY) {
    
    
                        try {
    
    
                            // Clear the interrupt state.
                            interrupted |= Thread.interrupted();
                            cache.wait();
                        } catch (InterruptedException e) {
    
    
                            // This shouldn't normally happen, but if someone interrupts the
                            // thread, it will.
                            Slog.w(TAG, "getService() interrupted");
                            interrupted = true;
                        }
                    }
                }
            }
            if (interrupted) {
    
    
                Thread.currentThread().interrupt();
            }
            return ret;
        }

  The synchronized (cache) lock is for multi-threading. If multiple threads call LayoutInflater.from(Context context) of the same Activity at the same time, multiple threads may access getService() at the same time. However, only one thread will execute createService(), and it will be generated successfully, and then put into the cache. Other threads may go to this locked code, and then judge that the value of gates[mCacheIndex] is ContextImpl.STATE_INITIALIZING, which is smaller than ContextImpl.STATE_READY, so wait for createService() to complete through cache.wait().
  In the third piece of code, we analyzed that after createService() is completed, cache.notifyAll() will be called to wake up the thread waiting on the lock. After the thread wakes up, it will judge through the while loop. At this time, the value of gates[mCacheIndex] is already ContextImpl.STATE_READY or ContextImpl.STATE_NOT_FOUND. If the condition of the loop is not satisfied, the while loop will jump out. Then it will enter the for loop again. At this time, if it is obtained from the cache, the value can be obtained.
  The last is to return the result to ret.

PhoneLayoutInflater类的cloneInContext(Context newContext)

After calling LayoutInflater.from(getBaseContext()) in the getSystemService() of   the previous ContextThemeWrapper to return the resulting LayoutInflater class instance, it will also call the cloneInContext(Context newContext) method of the LayoutInflater class instance, and then return the result. And this LayoutInflater class instance knows from the above analysis that it is actually a PhoneLayoutInflater instance.
  Take a look at cloneInContext(Context newContext) of the PhoneLayoutInflater class:

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

  Then call the constructor of the LayoutInflater class:

    protected LayoutInflater(LayoutInflater original, Context newContext) {
    
    
        StrictMode.assertConfigurationContext(newContext, "LayoutInflater");
        mContext = newContext;
        mFactory = original.mFactory;
        mFactory2 = original.mFactory2;
        mPrivateFactory = original.mPrivateFactory;
        setFilter(original.mFilter);
        initPrecompiledViews();
    }

  This is mainly to generate a new PhoneLayoutInflater class object, and point the member variable mContext of the new object to the newContext in the cloneInContext(Context newContext) method parameter. The PhoneLayoutInflater class object does not set mFactory, mFactory2, mPrivateFactory, mFilter.

  After analyzing these codes, we found that it mainly obtained a PhoneLayoutInflater instance through the locking mechanism, and then through new PhoneLayoutInflater(). Why, it is quite complicated to register first, then design the interface, and then implement it like the above. In fact, more than LayoutInflater instances are implemented in this way. Instances like other ActivityManager, ActivityTaskManager, AccessibilityManager, etc. are implemented in a way similar to this. In this way, the acquisition methods of these instances are unified through the design form. This is also worth learning.

Guess you like

Origin blog.csdn.net/q1165328963/article/details/126938059