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

  • On October 28, 2020, JetPack | App Startup 1.0.0 was officially released, just the recently concluded in modular architecture project, so also specially study the App Startup working principle;
  • In this article, I will give you a summary of App Startup usage & implementation principles & source code analysis. Please be sure to like and follow if you can help, it really means a lot to me.

directory


Front knowledge

The content of this article will involve the following pre/related knowledge, dear I have prepared for you, please enjoy ~

  • ContentProvider component resolution: The working process of the Android | ContentProvider

1. What problem does App Startup solve?

In this section, we’ll talk about why App Startup is used, and what problem App Startup solves. Before I wrote an article, I once told a ContentProvider based as possible mechanism of no invasion for Contex method: the Android | use ContentProvider without intrusion to obtain the Context “. Let me repeat it briefly here:

  • 1. In binary or tripartite libraries, it is often necessary to obtain the Context for initialization;
  • 2. Since ContentProvider is initialized at application startup, many libraries use ContentProvider startup mechanismsApplication#onCreate(), for exampleLeakCanary 2.4:

AppWatcherInstaller.java

internal sealed class AppWatcherInstaller : ContentProvider() { internal class MainProcess : AppWatcherInstaller() internal class LeakCanaryProcess : AppWatcherInstaller() override fun onCreate(): Boolean { val application = context!! .applicationContext as Application AppWatcher. ManualInstall (Application) return true}Copy the code
  • 3. The risk of this approach is that there are too many ContentProviders, which will increase the startup time of the application.

  • AppStartup consolidates all initializers, reduces creation of contentProviders, and provides global management.


2. Procedure

In this section, we will summarize the steps for using App Startup.

build.gradle

Implementation "androidx startup: startup - runtime: 1.0.0"Copy the code

2.1 Implement the Initializer interface for components

The Initializer interface is a component interface encapsulated by Startup that specifies the initialization logic and initialization order (that is, dependencies) of the component.

Initializer.java

@nonnull T create(@nonnull Context Context); public interface Initializer<T> {public interface Initializer<T> {1. @nonnull List<Class<? extends Initializer<? >>> dependencies(); }Copy the code
  • 1.create(...)Initialization operations:The returned initialization result will be cached, wherecontextThe argument is Application;
  • The return value is a list of dependencies, or an empty list if no other dependencies are required. App Startup When initializing the current component, ensure that the dependent component has been initialized.

2.2 Automatic Initialization

InitializationProvider: InitializationProvider: InitializationProvider: InitializationProvider: InitializationProvider Such as:

<provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup"  android:exported="false" tools:node="merge"> <meta-data android:name="com.example.ExampleLoggerInitializer" android:value="androidx.startup" /> </provider>Copy the code

The main points are as follows:

  • 1. The component name must beandroidx.startup.InitializationProvider;
  • 2, need to declareandroid:exported="false"To restrict access to this component by other applications;
  • 3, requirementsandroid:authorities${applicationId} is used as a prefix that is unique throughout the phone;
  • 4, need to declaretools:node="merge"To ensure thatmanifest merger toolNodes that resolve conflicts correctly;
  • 5, meta – datanameFully qualified name for the Initializer implementation class for the component,valueforandroidx.startup.

Androidx. startup set to value instead of name Because in key-value pairs, name is unique and value is allowed to duplicate.

I talk in Section 3 about how App Startup automatically initializes components after they are declared in AndroidManifest.

2.3 Manual Initialization

When your component needs lazy loading (time-consuming tasks), you can manually initialize it. Note that Initializer cannot be declared in AndroidManifest or relied on by other components. Manual initialization is done by calling:

AppInitializer.getInstance(context)
.initializeComponent(ExampleLoggerInitializer::class.java)
Copy the code

Note that App Startup caches the results of initialization, and repeated calls to initializeComponent() do not result in repeated initialization. The source code analysis for the manual initialization section of App Startup is described in section 3.

2.4 Canceling Automatic Initialization

If a library has already configured automatic initialization using the method in Section 2.2 and we want lazy loading, we need to use the manifest Merger tool’s merger rules to remove the Initializer for that library. Details are as follows:

<provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup"  android:exported="false" tools:node="merge"> <meta-data android:name="com.example.ExampleLoggerInitializer" tools:node="remove" /> </provider>Copy the code

2.5 Disable automatic initialization

If you want to disable App Startup auto-initialization, you can also use the manifest merger tool’s rules:

<provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup"  tools:node="remove" />Copy the code

3. Source code analysis

3.1 InitializationProvider analysis

We mentioned above, in the AndroidManifest file configuration of components must be androidx. Startup. InitializationProvider, now we come to this class’s source code:

InitializationProvider.java

Public Final Class InitializationProvider extends ContentProvider {@override public Boolean onCreate() {Context context = getContext(); if (context ! . = null) {initialization AppInitializer getInstance (context). DiscoverAndInitialize (); } else { throw new StartupException("Context cannot be null"); } return true; } @Override public Cursor query(...) { throw new IllegalStateException("Not allowed."); } @Override public String getType(...) { throw new IllegalStateException("Not allowed."); } @Nullable @Override public Uri insert(...) { throw new IllegalStateException("Not allowed."); } @Override public int delete(...) { throw new IllegalStateException("Not allowed."); } @Override public int update(...) { throw new IllegalStateException("Not allowed."); }}Copy the code

You can see:

  • 1.InitializationProviderIn fact, the use of ContentProvider startup mechanism, inContentProvider#onCreate(...)To perform initialization;
  • Because the other methods of ContentProvider are meaningless, they are thrownIllegalStateException.

3.2 Automatic initialization source code analysis

As you can see from the section, App Startup calls AppInitializer#discoverAndInitialize() in the ContentProvider to perform the automatic initialization. AppInitializer is the core class of the App StartUp framework. There is very little code in the App StartUp framework. Most of the core code is in the AppInitializer class.

AppInitializer.java

final Set<Class<? extends Initializer<? >>> mDiscovered; Has been simplified void discoverAndInitialize () {1, obtain androidx. Startup. InitializationProvider component information the ComponentName provider = new ComponentName(mContext.getPackageName(), InitializationProvider.class.getName()); ProviderInfo providerInfo = mContext.getPackageManager().getProviderInfo(provider, GET_META_DATA); Startup String String startup = McOntext. getString(r.sing.androidx_startup); Metadata = providerInfo. metadata; metadata = providerinfo. metadata; If (metadata! = null) { Set<Class<? >> initializing = new HashSet<>(); Set<String> keys = metadata.keySet(); for (String key : keys) { String value = metadata.getString(key, null); Androidx. startup if (startup.equals(value)) {Class<? > clazz = Class.forName(key); 4.2 check the specified class is, Initializer interface implementation class if (, Initializer. Class. IsAssignableFrom (clazz)) {class <? extends Initializer<? >> component = (Class<? extends Initializer<? >>) clazz; 4.3 Add Class to mDiscovered Set mDiscovered. Add (Component); 4.4 Initialize this component doInitialize(Component, initializing); }}}}} -> 4.3 mDiscovered Indicates whether the component is automatically started. Public Boolean isEagerlyInitialized(@nonNULL Class<? extends Initializer<? >> component) { return mDiscovered.contains(component); }Copy the code

The above code has been very simplified, focusing on the following points:

  • 1, obtainandroidx.startup.InitializationProviderComponent information (component information declared in each Module is displayed in themanifest merger toolProcessing under merge);
  • 2,androidx.startupstring
  • 3. Obtain meta-data from component information
  • 4. Iterate over meta-data
    • 4.1 Determining the value of the meta-data, value is the key-value pair of Androidx. startup
    • 4.2 Checking that the specified class is the implementation class of the Initializer interface
    • 4.3 Add Class to mDiscovered Set. This will be used later to determine whether the component has started automatically
    • 4.4 Initializing this Component

AppInitializer.java

private static final Object sLock = new Object(); Caches the initialization results of each component final Map<Class<? >, Object> mInitialized; <T> T doInitialize(Class<? extends Initializer<? >> component, Set<Class<? >> initializing) {1, I will talk about sLock later. Object result; 2. Determine the current component in initializing, Note Loop dependencies exist if (initializing. Contains (Component)) {String message = string. format("Cannot initialize % S. Cycle detected.",  component.getName()); throw new IllegalStateException(message); } 3, check whether the current component is initialized if (! MInitialized. ContainsKey (Component)) {3.1 Current component not initialized 3.1.1 Record Initializing. Add (Component); 3.1.2 through reflection instantiation, Initializer interface implementation class Object instance = component. GetDeclaredConstructor (). NewInstance (); Initializer<? > initializer = (Initializer<? >) instance; 3.1.3 Iterating through dependent components List<Class<? extends Initializer<? >>> dependencies = initializer.dependencies(); if (! dependencies.isEmpty()) { for (Class<? extends Initializer<? >> clazz: dependencies) {recursively initialize if (! mInitialized.containsKey(clazz)) { doInitialize(clazz, initializing); Initializing the current component result = initializer.create(mContext);}} 3.1.4 (by now, the dependent component has been initialized) 3.1.5 Remove initializing record initializing. Remove (Component); 3.1.6 Cache initialization results mInitialized. Put (component, result); } else {3.2 If the component is initialized, result = mInitialized. Get (component); } return (T) result; }Copy the code

The above code has been very simplified, focusing on the following points:

  • 1, lock sLock, I will discuss later.
  • 2. Determine the presence of the current component in initializing, indicating the existence of circular dependency (this is because initializing the dependent component recursively will be passed in as a parameter, if initializing the current component in initializing, indicating that the dependency relationship forms a loop, if no exception is thrown, Will form an infinite recursion.
  • Check whether the current component has been initialized. If so, return (3.2) directly, otherwise:
    • 3.1.1 Records being Initialized
    • 3.1.2 Instantiate the Initializer interface through reflection to implement the class
    • 3.1.3 Iterate over dependent components, recursively call if dependent components are not initializeddoInitialize(...)Perform initialization
    • 3.1.4 (By this point, dependent components have been initialized) Initialize the current component
    • 3.1.5 Removing An Initializing Record
    • 3.1.6 Caching initialization Results

3.3 Manually Initializing source code analysis

Now let’s look at the manual initialization (lazy load) source analysis:

AppInitializer.java

public <T> T initializeComponent(@NonNull Class<? extends Initializer<T>> component) {
    调用 doInitialize(...) 方法:
    return doInitialize(component, new HashSet<Class<?>>());
}
Copy the code

It’s as simple as calling doInitialize(…) from the previous section. Perform initialization. Note that this method is allowed to be called on child threads. In other words, automatic initialization and manual initialization have thread synchronization problems.

Remember we didn’t say sLock earlier? It is a lock used to keep threads synchronized:

AppInitializer.java

<T> T doInitialize(Class<? extends Initializer<? >> component, Set<Class<? >> initializing) {1, synchronized (sLock) {... }}Copy the code

4. To summarize

  • Advantages: Using the App Startup framework, you can simplify the Startup sequence and explicitly set the initialization dependency sequence. In terms of simplicity and efficiency, App Startup basically meets requirements.

  • Weaknesses: The App Startup framework is also weak because it is too simple and offers too simple features that often don’t fit perfectly into commercial needs. For example, App Startup cannot meet the following features:

    • Lack of asynchronous wait: Synchronous wait refers to the initialization of dependent components on the current line and then the current component. App Startup supports asynchronous wait, but not asynchronous wait. For example, App Startup cannot wait for the asynchronous task to return if the dependent component needs to execute a time-consuming asynchronous task to complete initialization.
    • Lack of dependency callbacks: No callback is issued after the initialization of the component on which the current component depends.


The resources

  • App Startup — Android Developers
  • Merge Multiple Manifest Files — Android Developers
  • AndroidX: App Startup by Husayn Hakeem
  • New member of Jetpack, App Startup, by Lin Guo
  • Why DID I quit Jetpack’s App Startup? — A nap in the afternoon
  • Faster! That’s the Android Startup Library I want! – the idisfkj
  • Componentization: Sequential initialization of components is no problem with code isolation. By Leobert-Lan
  • Jetpack (5) Startup source Code By Zhicchen Ye

Creation is not easy, your “three lian” is chouchou’s biggest motivation, we will see you next time!