• The official file address of Startup is as follows:

Developer. The android. Google. Cn/topic/libra…

The official definition is as follows:

The App Startup library provides a straightforward, performant way to initialize components at application startup. Both library developers and app developers can use App Startup to streamline startup sequences and explicitly set the order of initialization.

Instead of defining separate content providers for each component you need to initialize, App Startup allows you to define component initializers that share a single content provider. This can significantly improve app startup time.

Translation:

The application startup library provides a simple, efficient way to initialize components at application startup time. Both library developers and application developers can use StartUp to simplify the StartUp sequence and explicitly set the initialization order. StartUp allows you to define component initializers that share a single content provider, rather than defining separate Content providers for each component that needs to be initialized. This can significantly reduce application startup time.

Simply put, a common Content provider is used to centrally manage the components that need to be initialized to improve the startup speed of the application.

StartUp usage

  • Add required dependencies
Dependencies {implementation "androidx. Startup :startup-runtime:1.0.0"}Copy the code
  • Define one for each component you needcomponent initializerSuppose there are four components A, B, C, and D that need to be initializedinitializer.
Class ASdk {// Suppose this is the component we need to initialize A companion object {fun getInstance(): ASdk { return Instance.instance } } private object Instance { val instance = ASdk() } }Copy the code

For each component that needs to be initialized, we need to create a class to implement the Initializer

interface. The corresponding Initializer is as follows:

class ASdkInitializer : Initializer<ASdk> { override fun create(context: Context): ASdk {log. I ("gj","ASdkInitializer create() ") return asdk.getinstance ()} Override fun dependencies(): MutableList<Class<out Initializer<*>> {log. I ("gj","ASdkInitializer dependencies() run ") return mutableListOf()}}Copy the code

You can see that there are only two methods to implement, the create () method and dependencies() method.

  • create ()Method contains all the actions needed to initialize the component and returns an instance of T.
  • dependencies()The method returns aInitializer<T>thelistThis collection contains the currentInitializer<T>Others that depend onInitializer<T>This method allows us to control the order in which components are initialized when the program starts.

For example, the Initializer

of components A, B, and C should be written as follows when C depends on B and B depends on A:

/** * Class BSdkInitializer: Initializer<BSdk> {override fun create(context: context): BSdk {log. I ("gj","BSdkInitializer create() ") return bsdk.getinstance ()} override fun dependencies(): MutableList<Class<out Initializer<*>>> {log. I ("gj","BSdkInitializer dependencies() method execute ") return mutableListOf(ASdkInitializer::class.java) } }Copy the code

In the dependencies() method, we need to return ASdkInitializer. Initializer for C is the same as the following:

/** * Class CSdkInitializer: Initializer<CSdk> {override fun create(context: context): CSdk {log. I ("gj", "CSdkInitializer create() ") return csdk.getinstance ()} Override fun dependencies(): MutableList<Class<out Initializer<*>>> { Log.i("gj", "CSdkInitializer dependencies () method performs") return mutableListOf (BSdkInitializer: : class. Java)}}Copy the code

How do you start a StartUp?


StartUp provides two ways to start a StartUp, either automatically or manually.

  • The automatic startup mode is as follows:

We just need to add the corresponding declaration to the InitializationProvider in the AndroidManifest, as follows:

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

meta-data Under the label is us InitializerThe path,valueNote that must beandroidx.startupAs you can see above, the A, B, C components I only use C’s InitializerI made a statement, and that’s because both B and C can passdependencies()If there are no dependencies between A, B, and C, then each of the corresponding InitializerMake a declaration. A, B, and C are executed in the same order as we declared them. At this point, the program will be initialized in the order we want it to be initialized. The logs are as follows:It’s not hard to see from the logs:creat()The order of execution is A–>B–>C,dependencies()The order of execution is C- >B- >A, which is in line with our expected effect.

  • The manual control mode is as follows:

For example, we also defined a DSdkInitializer. In this case, we need to manually initialize the D component, so we can directly initialize it:

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {super.oncreate (savedInstanceState) setContentView(r.layout.activity_main) // Manually start SDK initialization AppInitializer.getInstance(this).initializeComponent(DSdkInitializer::class.java) } }Copy the code

You can also see from the logs that the D component is initialized last.

It is important to note that the DSdkInitializer declaration in the AndroidManifest is not intended for the initialization of the D component, As you can see, we added a tools:node=”remove” attribute to prevent an initialization of the same component in another reference library, ensuring that the automatic initialization of the component is actually turned off.


  • To disable automatic initialization of all components of startup, we can invoke the following methods:
<provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup"  tools:node="remove"/>Copy the code

This actually turns off all automatic initialization logic for Startup.


StartUp source code

By above can see StartUp class contains only five AppInitializer InitializationProvider,, Initializer, StartupException, StartupLogger below we in turn to the five classes in detail .


AppInitializer

This class is the core class of the StartUp library.

public final class AppInitializer { ... private static AppInitializer sInstance; /** * Guards app initialization. */ private static final Object sLock = new Object(); @NonNull final Map<Class<? >, Object> mInitialized; @NonNull final Context mContext; /** * Creates an instance of {@link AppInitializer} * * @param context The application context */ AppInitializer(@NonNull Context context) { mContext = context.getApplicationContext(); mInitialized = new HashMap<>(); } /** * @param context The Application {@link Context} * @return The instance of {@link AppInitializer} after initialization. */ @NonNull @SuppressWarnings("UnusedReturnValue") public static AppInitializer getInstance(@NonNull Context context) { synchronized (sLock) { if (sInstance == null) { sInstance = new AppInitializer(context); } return sInstance; }}... }Copy the code

We first get the dynamic instance we want by using the getInstance(@nonNULL Context Context) method of the class. Notice what this parameter does: ‘Map

, Object> Use map to store components that have been initialized.
>

@NonNull @SuppressWarnings("unused") public <T> T initializeComponent(@NonNull Class<? extends Initializer<T>> component) { return doInitialize(component, new HashSet<Class<? > > ()); } @NonNull @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) <T> T doInitialize( @NonNull Class<? extends Initializer<? >> component, @NonNull Set<Class<? >> initializing) { synchronized (sLock) { boolean isTracingEnabled = Trace.isEnabled(); try { if (isTracingEnabled) { // Use the simpleName here because section names would get too big otherwise. Trace.beginSection(component.getSimpleName()); } if (initializing.contains(component)) { String message = String.format( "Cannot initialize %s. Cycle detected.", component.getName() ); throw new IllegalStateException(message); } Object result; if (! mInitialized.containsKey(component)) { initializing.add(component); try { Object instance = component.getDeclaredConstructor().newInstance(); Initializer<? > initializer = (Initializer<? >) instance; List<Class<? extends Initializer<? >>> dependencies = initializer.dependencies(); if (! dependencies.isEmpty()) { for (Class<? extends Initializer<? >> clazz : dependencies) { if (! mInitialized.containsKey(clazz)) { doInitialize(clazz, initializing); } } } if (StartupLogger.DEBUG) { StartupLogger.i(String.format("Initializing %s", component.getName())); } result = initializer.create(mContext); if (StartupLogger.DEBUG) { StartupLogger.i(String.format("Initialized %s", component.getName())); } initializing.remove(component); mInitialized.put(component, result); } catch (Throwable throwable) { throw new StartupException(throwable); } } else { result = mInitialized.get(component); } return (T) result; } finally { Trace.endSection(); }}}Copy the code

DoInitialize (Component, new HashSet

>()) finally calls result = initializer.create(mContext); It is easy to see that the ultimate goal of this method is to complete the initialization of all dependencies. The general process is as follows:
>

  • The first thing to determine is what is being initializedInitializerSet, if there is a set ofcomponent , indicating that the currentInitializerThere are cyclic dependencies, a cyclic dependent exception is thrown, and if there are no dependencies, the execution continues.
  • If it’s already initializedmInitializedContains nocomponent, indicating the currentInitializerAnd adds it to the collection being initialized for initializationinitializing, and then called by reflectioncomponentClass, while getting theInitializerAll the dependencies ofdependencies If thedependencies Where there are dependencies, each dependency is called recursivelydoInitialize(clazz, initializing)To initialize.
  • The final callinitializer.create(mContext)Completes the initialization and converts the already initializedInitializerFrom the collectioninitializingRemove while initialization is completecomponentIn themInitializedSave it in.
  • If you’re already inmInitializedIncluded in thecomponent, just need to fromresult = mInitialized.get(component);To obtain the cache.

Now what does the discoverAndInitialize() method do? This method is called by the InitializationProvider, which eventually calls the doInitialize(Component, initializing) method mentioned above;

@SuppressWarnings("unchecked") void discoverAndInitialize() { try { Trace.beginSection(SECTION_NAME); ComponentName provider = new ComponentName(mContext.getPackageName(), InitializationProvider.class.getName()); ProviderInfo providerInfo = mContext.getPackageManager() .getProviderInfo(provider, GET_META_DATA); Bundle metadata = providerInfo.metaData; String startup = mContext.getString(R.string.androidx_startup); if (metadata ! = null) { Set<Class<? >> initializing = new HashSet<>(); Set<String> keys = metadata.keySet(); for (String key : keys) { String value = metadata.getString(key, null); if (startup.equals(value)) { Class<? > clazz = Class.forName(key); if (Initializer.class.isAssignableFrom(clazz)) { Class<? extends Initializer<? >> component = (Class<? extends Initializer<? >>) clazz; if (StartupLogger.DEBUG) { StartupLogger.i(String.format("Discovered %s", key)); } doInitialize(component, initializing); } } } } } catch (PackageManager.NameNotFoundException | ClassNotFoundException exception) { throw new StartupException(exception); } finally { Trace.endSection(); }}Copy the code
  • First of all, you getInitializationProvider All of themetadata
  • And then you go through all of themmetadata Find all the files that belong to startupmetadata And check whether the package name path is correctInitializer If so, initialize the class.

InitializationProvider

InitializationProvider is derived from ContentProvider, and its main function is to trigger the entire initialization of a StartUp.

/** * The {@link ContentProvider} which discovers {@link Initializer}s in an application and * initializes them before {@link Application#onCreate()}. * * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY) public final class InitializationProvider extends ContentProvider { @Override public boolean onCreate() { Context context = getContext(); if (context ! = null) { AppInitializer.getInstance(context).discoverAndInitialize(); } else { throw new StartupException("Context cannot be null"); } return true; }... }Copy the code

The onCreate() method is actively triggered by the system.


Initializer

This class is the one provided by StartUp that requires us to declare which components to initialize and which dependencies to initialize and in which order.

/** * {@link Initializer}s can be used to initialize libraries during app startup, without * the need to use additional {@link android.content.ContentProvider}s. * * @param <T> The instance type being initialized */ public interface Initializer<T> { /** * Initializes and a component given the application {@link Context}  * * @param context The application context. */ @NonNull T create(@NonNull Context context); /** * @return A list of dependencies that this {@link Initializer} depends on. This is * used to determine initialization order of {@link Initializer}s. * <br/> * For e.g. if a {@link Initializer} `B` defines another * {@link Initializer} `A` as its dependency, then `A` gets initialized before `B`. */ @NonNull List<Class<? extends Initializer<? >>> dependencies(); }Copy the code
  • T create(@NonNull Context context);Method completes initialization and returns.
  • List<Class<? extends Initializer<? >>> dependencies();Method to specify the currentInitializerIs dependent on.

StartupException

@RestrictTo(RestrictTo.Scope.LIBRARY) @SuppressWarnings("WeakerAccess") public final class StartupException extends RuntimeException { public StartupException(@NonNull String message) { super(message); } public StartupException(@NonNull Throwable throwable) { super(throwable); } public StartupException(@NonNull String message, @NonNull Throwable throwable) { super(message, throwable); }}Copy the code

A custom subclass of RuntimeException that is thrown when an error is encountered during StartUp initialization.


StartupLogger

public final class StartupLogger {

    private StartupLogger() {
        // Does nothing.
    }

    /**
     * The log tag.
     */
    private static final String TAG = "StartupLogger";

    /**
     * To enable logging set this to true.
     */
    static final boolean DEBUG = false;

    /**
     * Info level logging.
     *
     * @param message The message being logged
     */
    public static void i(@NonNull String message) {
        Log.i(TAG, message);
    }

    /**
     * Error level logging
     *
     * @param message   The message being logged
     * @param throwable The optional {@link Throwable} exception
     */
    public static void e(@NonNull String message, @Nullable Throwable throwable) {
        Log.e(TAG, message, throwable);
    }
}
Copy the code

This class is just a normal utility class, nothing to say.