background

As we all know, Application initialization is one of the most criticized problems in Android development, especially when apps support multiple processes and carrier level applications. With business iteration, if the initialization code is not in place, it will be disastrous. Future generations dare not move their positions at will, or because the time is too early and it is the only way to start the APP, it is difficult to evaluate the impact of each modification and the startup performance will be seriously affected.

Jetpack’s App Startup library is designed to solve the problem. The App Startup Library provides a straightforward, performant way to initialize components at application Startup., I want to say that you have been misled, please join me in vindicating App Startup.

No more myths or misconceptions about AppStartup

Many developer blogs in China are also misleading, trumpeting App Startup as a perfect solution to the Startup initialization problem. I want to say, please don’t mislead, because it’s really not true. First of all, it’s official: 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. They mainly solve the performance problems brought by multiple SDKS and multiple modules with a ContentProvider, rather than the initialization of the hosting framework. In short, AppStartup only solves the problem shown below:

Therefore, to put it plainly, the authorities have made a set of constraint rules. When each SDK and module wants to provide initialization without the awareness of business parties, they should not implement it internally through their own unique ContentProvider, but inherit itInitializer<T>The App Module will be packaged into a shared ContentProvider, which avoids the problem of multiple ContentProviders. This is the core problem solved by App Startup. The dependencies provided are a subsidiary feature.

Still confused, consider this typical scenario:

As an SDK developer, I finally give you aar dependency. In order to reduce your access cost, I will initialize the SDK without explicitly providing it to you. Instead, it is implemented using the ContentProvider onCreate method inside the SDK (because ContentProvider is started by ActivityThread, which corresponds to the main thread of the application process, That is, the ContentProvider is started when the Application process is started, so its timing is between Application attachBaseContext and onCreate. And only in this way can I be unaware of you, but the downside is that if you access ten SDKS similar to what I have implemented for you, you will be called ten ContentProviders when you start up, which is a bit too expensive.

But on second thought, I have no other perception of you can also initialize such a clever opportunity to borrow ah! How to do? App Startup solves this problem by combining interface oriented programming with automatic merge of App build manifest files, as shown above.

The use of advanced

General automatic usage

ExampleLogger initializes WorkManager by inheriting Initializer and ExampleLogger initializes WorkManager as follows:

/ / 【 craftsman if water to add WeChat yanbo373131686 contact me, concern WeChat public number: code farmers a daily topic Without permission is strictly prohibited reproduced, https://blog.csdn.net/yanbober]

// Initializes WorkManager.
class WorkManagerInitializer : Initializer<WorkManager> {
    override fun create(context: Context): WorkManager {
        val configuration = Configuration.Builder().build()
        WorkManager.initialize(context, configuration)
        return WorkManager.getInstance(context)
    }
    override fun dependencies(a): List<Class<out Initializer<*>>> {
        // No dependencies on other libraries.
        return emptyList()
    }
}
Copy the code
// Initializes ExampleLogger.
class ExampleLoggerInitializer : Initializer<ExampleLogger> {
    override fun create(context: Context): ExampleLogger {
        // WorkManager.getInstance() is non-null only after
        // WorkManager is initialized.
        return ExampleLogger(WorkManager.getInstance(context))
    }

    override fun dependencies(a): List<Class<out Initializer<*>>> {
        // Defines a dependency on WorkManagerInitializer so it can be
        // initialized after WorkManager is initialized.
        return listOf(WorkManagerInitializer::class.java)
    }
}
Copy the code

Listing file declarations (other initializers relied on May not be explicitly declared, as lint checks this) :

/ / 【 craftsman if water to add WeChat yanbo373131686 contact me, concern WeChat public number: code farmers a daily topic Without permission is strictly prohibited reproduced, https://blog.csdn.net/yanbober]<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <! -- This entry makes ExampleLoggerInitializer discoverable. -->
    <meta-data  android:name="com.example.ExampleLoggerInitializer"
          android:value="androidx.startup" />
</provider>
Copy the code

General manual usage

In addition to the above automatic usage, we can also use this method manually. To use this method manually, we need to first remove the list meta, and then call it where you want to initialize it as follows:

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

Listing file removal is written as follows:

/ / 【 craftsman if water to add WeChat yanbo373131686 contact me, concern WeChat public number: code farmers a daily topic Without permission is strictly prohibited reproduced, https://blog.csdn.net/yanbober]<! -- Remove all -->
<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    tools:node="remove" />

<! -- Remove the specified Initializer -->
<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

Special usage

Sometimes you need AarExampleInitializer for the SDK (empty dependencies), and the aar manifest declares something like this:

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

You want the AarExampleInitializer initialization to depend on other SDKS in your aar module. That you need to write in your own module a OverAarExampleInitializer extends AarExampleInitializer (AarExampleInitializer can be inherited, Because App Startup requires Initializer to be public), rewrite its dependencies method to the one you want to rely on, and do the following in your own list of main modules:

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

The above requirements can be fulfilled. If you want other modules to rely on AarExampleInitializer, you can only rely on the declaration in other modules.

Source code analysis

The overall source structure of the App Startup package is as follows:

First, according to the usage, we need to implement the Initializer

initialization convention for each SDK as follows:

/ / 【 concern WeChat public number: code farmers a daily topic Without permission is strictly prohibited reproduced, https://blog.csdn.net/yanbober]

/** * the initialization wrapper interface convention for the SDK to be initialized */
public interface Initializer<T> {
    /** * This method initializes the SDK and returns an SDK API object, typically a singleton with the return value NonNull */
    @NonNull
    T create(@NonNull Context context);

    /** * The SDK initialization depends on other SDK initializers list * return value NonNull non-empty */
    @NonNullList<Class<? extends Initializer<? >>> dependencies(); }Copy the code

If Initializer

is used automatically, app Startup uses the Initializer initialization time smartly. The source code listing is as follows:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="androidx.startup" >

    <uses-sdk
        android:minSdkVersion="14"
        android:targetSdkVersion="30" />

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

As you can see, the clever use of the merge node to merge multiple modules. And it provides a unique androidx startup. InitializationProvider rules over all. Each of our Initializer

is a meta-data entry inside its node, as follows:

/ / 【 craftsman if water to add WeChat yanbo373131686 contact me, concern WeChat public number: code farmers a daily topic Without permission is strictly prohibited reproduced, https://blog.csdn.net/yanbober]<meta-data android:name="com.example.OverAarExampleInitializer"
              android:value="androidx.startup" />
Copy the code

These meta – data can eventually in androidx. Startup. InitializationProvider parsing calls, because app startup InitializationProvider are automatically calls, So let’s move the process to the InitializationProvider as follows:

/** * InitializationProvider is used to scan through the onCreate to find Initializer *@hide* /
@RestrictTo(RestrictTo.Scope.LIBRARY)
public final class InitializationProvider extends ContentProvider {
    @Override
    public boolean onCreate(a) {
        Context context = getContext();
        if(context ! =null) {
            AppInitializer.getInstance(context).discoverAndInitialize();
        } else {
            throw new StartupException("Context cannot be null");
        }
        return true; }...Query, getType, insert, delete, and update implementations all throw IllegalStateException("Not allowed."); , no longer given.
}
Copy the code

It is obvious that we need to view to AppInitializer. GetInstance (context). DiscoverAndInitialize (); AppInitializer class code is the essence of the App Startup framework, as follows:

/ / 【 craftsman if water to add WeChat yanbo373131686 contact me, concern WeChat public number: code farmers a daily topic Without permission is strictly prohibited reproduced, https://blog.csdn.net/yanbober]

public final class AppInitializer {
    private static final String SECTION_NAME = "Startup";

    private static volatile AppInitializer sInstance;
    private static final Object sLock = new Object();
	
	// The Initializer collection has been initialized
    finalMap<Class<? >, Object> mInitialized;// The list of initializers that were automatically discovered
    finalSet<Class<? extends Initializer<? >>> mDiscovered;final Context mContext;

    /** * non-public constructor that assigns context to getApplicationContext */
    AppInitializer(@NonNull Context context) {
        mContext = context.getApplicationContext();
        mDiscovered = new HashSet<>();
        mInitialized = new HashMap<>();
    }

    / * * * double check singleton pattern * external use AppInitializer getInstance (CTX) for singleton AppInitializer instance object * /
    @NonNull
    @SuppressWarnings("UnusedReturnValue")
    public static AppInitializer getInstance(@NonNull Context context) {
        if (sInstance == null) {
            synchronized (sLock) {
                if (sInstance == null) {
                    sInstance = newAppInitializer(context); }}}return sInstance;
    }

    Initializer
      
        Initializer
       
         Initializer
        
          Initializer
         
           Initializer
          
            Initializer
           
             Initializer
            
              Initializer
             
               Initializer
             
            
           
          
         
        
       
      
    @NonNull
    public <T> T initializeComponent(@NonNull Class<? extends Initializer<T>> component) {
        return doInitialize(component, newHashSet<Class<? > > ()); }/** * Determine whether the specified Initializer
      
        implementation SDK has been initialized in hungry mode (that is, whether it has been initialized in automatic initialization mode). * Hunky-dory initialization is the same as singleton initialization, that is, the object is new first, instead of being new the first time it is used; Corresponding to here have been AppInitializer. The getInstance (context). DiscoverAndInitialize processed (). * /
      
    public boolean isEagerlyInitialized(@NonNullClass<? extends Initializer<? >> component) {
        // If discoverAndInitialize() was never called, then nothing was eagerly initialized.
        return mDiscovered.contains(component);
    }
	
	/** * The first argument is the Initializer interface implementation class object; * The second parameter is a temporary set of initializations, used to recursively recalibrate, to prevent the initialization of loop dependency resulting in an infinite loop; * /
    @NonNull
    <T> T doInitialize(
            @NonNullClass<? extends Initializer<? >> component,@NonNullSet<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 the recursive dependency initialization has a cyclic dependency, that is, it has already been initialized, then an exception is thrown
                if (initializing.contains(component)) {
                    String message = String.format(
                            "Cannot initialize %s. Cycle detected.", component.getName()
                    );
                    throw new IllegalStateException(message);
                }
                Object result;
                // If Initializer has never been initialized for this recursive dependency, initialize it
                if(! mInitialized.containsKey(component)) {// Add the recursive dependency initialization to the temporary set first
                    initializing.add(component);
                    try {
                    	// Instantiate a corresponding Initializer objectObject instance = component.getDeclaredConstructor().newInstance(); Initializer<? > initializer = (Initializer<? >) instance;// Get the return value of the corresponding Initializer Dependencies methodList<Class<? extends Initializer<? >>> dependencies = initializer.dependencies();// The Initializer dependencies method must not return null, even if it does not return emptyList.
                        if(! dependencies.isEmpty()) {// Iterate through the dependencies list of the current Initializer
                            for(Class<? extends Initializer<? >> clazz : dependencies) {// If the dependent Initializer has not already been initialized, the call is recursively initialized
                                if(! mInitialized.containsKey(clazz)) { doInitialize(clazz, initializing); }}}if (StartupLogger.DEBUG) {
                            StartupLogger.i(String.format("Initializing %s", component.getName()));
                        }
                        // Call the create of Initializer
                        result = initializer.create(mContext);
                        if (StartupLogger.DEBUG) {
                            StartupLogger.i(String.format("Initialized %s", component.getName()));
                        }
                        Initializer dependencies and create can be removed from the temporary collection after being called.
                        initializing.remove(component);
                        // Put the Initializer into the initialized collection of the singleton global for quick use next time
                        mInitialized.put(component, result);
                    } catch (Throwable throwable) {
                        throw newStartupException(throwable); }}else {
                    // If the instance of Initializer has been recorded in the singleton, the initialized object is returned
                    result = mInitialized.get(component);
                }
                return (T) result;
            } finally{ Trace.endSection(); }}}/ * * * be androidx. Startup. InitializationProvider onCreate calls automatically. * Scan the manifest file for meta-data and initialize it. * /
    void discoverAndInitialize(a) {
        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;
            //startup="androidx.startup", so the value of meta-data in each InitializationProvider must be this value.
            String startup = mContext.getString(R.string.androidx_startup);
            // Get meta-data from the InitializationProvider in androidmanifest.xml
            if(metadata ! =null) { Set<Class<? >> initializing =new HashSet<>();
                / / get InitializationProvider inside all the android: name = "com. Example. OverAarExampleInitializer" and so on registration, Initializer implementation class
                Set<String> keys = metadata.keySet();
                for (String key : keys) {
                    String value = metadata.getString(key, null);
                    // Make sure each meta-data is android:value="androidx.startup"
                    if (startup.equals(value)) {
                    	/ / get each, Initializer class object, such as com. Example. OverAarExampleInitializer. ClassClass<? > clazz = Class.forName(key);if(Initializer.class.isAssignableFrom(clazz)) { Class<? extends Initializer<? >> component = (Class<? extends Initializer<? >>) clazz;// Add the Initializer implementation for each meta-data discovery to the mDiscovered Set collection.
                            mDiscovered.add(component);
                            if (StartupLogger.DEBUG) {
                                StartupLogger.i(String.format("Discovered %s", key));
                            }
                            // Loop through to initialize each Initializer implementation class
                            doInitialize(component, initializing);
                        }
                    }
                }
            }
        } catch (PackageManager.NameNotFoundException | ClassNotFoundException exception) {
            throw new StartupException(exception);
        } finally{ Trace.endSection(); }}}Copy the code

To this entire App Startup even if the truth comes to light, the above analysis of the automatic process under the source code. We do not need to analyze the general manual usage in the previous section, because this part of the implementation is already included in the automatic process.

The following is a source summary flow chart of the overall situation:

The meta-data of the manifest file only explicitly registers A\B\C, and C depends on D\E, but after App Startup, A\B\C\D\E is initialized, and D\E is initialized before C.

Conclusion reflection

This can echo what we said at the beginning, App Startup is not so perfect, it can be said that it only solves the problem of specific scenarios, which is actually ok in the case of all modules are self-developed controllable source code (especially in the component development, interface programming is well done). But for the tripartite SDK that he wants to really constrain, according to the domestic market, it may not reach the purpose of its design expectation in the short term.

Personally, I think the best implementation of App Startup is actually to solve some scenarios of module global initialization in component-based programming. Multi-contentprovider can be said to be difficult to promote three parties at present.

App Startup provides dependencies(), but it does not provide asynchronous parallelism and lazy initialization of dependencies.

On the other hand, the Initializer App Startup relies on is called at runtime by reflection discovery, so each implementation needs to be kept, which is also not perfect from this point of view.

What do you recommend?

Since App Startup isn’t perfect, you might say, what’s the perfect recommendation? I would say no, because the initializations are highly relevant to your application business, so the best ones are for your business. However, there are several frameworks in the industry that actually address Startup initialization, not App Startup.

The framework is recommended

[Alpha startup framework] github.com/alibaba/alp…

Alpha is an Android asynchronous startup framework based on PERT diagrams. It is simple, efficient, and fully functional. We usually have a lot of work to do when an application starts up, and we try to make it as concurrent as possible to speed it up. However, these tasks may be dependent on each other, so we need to find ways to ensure that they are executed in the correct order. Alpha is designed for this purpose, so users can define their own tasks, describe the tasks they depend on, and add them to a Project. The framework automatically executes these tasks concurrently and sequentially and throws out the results of the execution. Since Android applications support multiple processes, Alpha supports different startup modes for different processes.

Github.com/NoEndToLF/A AppStartFaster 】 【…

Official summary: AppStartFaster consists of two parts, one is cold start task distribution and the other is Multdex cold start optimization. In essence, all tasks of the initiator are a directed acyclic graph. Through Systrace, wallTime and cpuTime are determined and appropriate thread pools are selected. There are two types of thread pools here (CPU constant capacity thread pool and IO cache thread pool). The nature of multi-threaded concurrency is to grab time slices, so select a fixed capacity thread pool for a long cpuTime to prevent its concurrency from affecting CPU efficiency, instead choose a cache thread pool, and then construct a graph between tasks, because some tasks are sequential (it is easy to correctly use 30% startup speed optimization). Under 5.0, start a new process Activity to load dex, in fact, in order to display the first Activity in the first time, which is a pseudo optimization. In fact, in the process of loading dex, Multdex compresses dex into ZIP, and then decompresses zip. And he can directly load dex, there is a process of compression and decompression, so in fact, the real optimization should avoid decompression and compression.

Anchors github.com/YummyLau/An 】…

Many Anchors are a graph-based structure that allows asynchronously dependent task initialization of the Android startup framework. Its anchor points provide “hook” dependencies and can flexibly solve complex synchronization problems during initialization. Refer to Alpha and improve some of its details to better fit the Android startup scenario, and support optimization of the dependency initialization process to choose a better path for initialization.

Start optimizing recommendations for quality articles

“Thoughts on Android’s Asynchronous Startup Framework Alpha”

“A closer to the android scene start frame | Anchors”

Rocket-android Startup Task Scheduling Framework