preface

The latest addition to Jetpack is the Startup App, which is officially announced as a dependency library optimized for initialization components when an Android application is launched. I was very happy when I first heard that, because the project I was in charge of had so many things to initialize at startup, and it was a bit chaotic, all coupled together. There is no asynchronous processing for components that can be initialized asynchronously, and there is no good unified specification for dependencies between asynchronous components that have been handled, or for unified logic handling after multiple asyncs. Therefore, I have long wanted to find a solution to optimize this situation, and this time I finally got App Startup.

However, when I checked the official documents, I did not find the expected results. The dependencies() method is used to define dependencies between components. The dependencies() method is used to define dependencies between components. Didn’t? Wait, official did you miss something?

What about asynchronous processing? While we can manually create child threads in the create() method to perform asynchronous tasks, what if one asynchronous task depends on another? Where is the unified logical processing after multiple asynchronous tasks are completed? Where is the callback after the dependency task is done? Or do you rely on notifications when the task is done?

I didn’t believe it, so I went to check the source code of App Startup. The source code is very simple, only a few files, and finally found that only the above functions are supported.

If your project is initialized synchronously and uses multiple ContentProviders, App Startup may have some room for optimization. After all, it is unified in one ContentProvider and supports simple sequential dependencies.

It is worth to mention that App Startup only provides component instances that use reflection to get initialization. For some initialization projects that do not rely too much, blindly using App Startup to optimize will further affect the Startup speed.

So I think about it, can not help but remind me of The Three Kingdoms of a noun: chicken ribs. It is tasteless to eat, but it is a pity to discard.

But EVENTUALLY I decided not to use it.

I was a little reluctant to give up, probably more because it didn’t solve my current project scenario. All analyzed so much, the source code all looked, the total can not give up halfway, so their bite the bullet and then add a little bai.

So hang in there, and here’s the library, the advanced version of App Startup Android Startup.

Android Startup

Android Startup provides a simpler and more efficient way to initialize components at application Startup. Developers can use Android Startup to simplify the Startup sequence and explicitly set the dependency between the initialization order and components. Android Startup, meanwhile, supports synchronous and asynchronous wait, and ensures the initialization order of internal dependent components by ordering them in a directed acyclic graph topology.

Android Startup is an extension based on App Startup, so it is used in a similar way to App Startup.

Here’s a table comparing Google’s App Startup feature.

indicators App Startup Android Startup
Manual configuration
Automatic configuration
Depend on the support
The closed-loop processing
The thread of control
Asynchronous waiting
Relying on the callback
Topology optimization

The following is a brief introduction to Android Startup.

Add the dependent

Add the following dependencies to the build.gradle file:

Dependencies {implementation 'com. Rousetime. Android: android - startup: 1.0.1'}Copy the code

Update information about a dependent version: Release

Quick to use

Android-startup provides two modes of use. You need to define the initialized components before using it.

Define the initialized component

The AndroidStartup

abstract class, which implements the Startup

interface, has four abstract methods:

  • CallCreateOnMainThread (): Boolean controls the thread on which the create() method is called, returning true to indicate execution on the main thread.

  • WaitOnMainThread (): Boolean controls whether the currently initialized component needs to wait on the main thread to complete. If true is returned, the main thread will wait and block.

  • create(): T? Component initialization methods that perform initialization logic that needs to be handled and support returning an instance of type T.

  • dependencies(): List

    >>? Return a list of types Startup<*>. Represents the component that the current component needs to depend on before execution.

For example, define a SampleFirstStartup class to implement the AndroidStartup

abstract class:

class SampleFirstStartup : AndroidStartup<String>() {
 
    override fun callCreateOnMainThread(): Boolean = true
 
    override fun waitOnMainThread(): Boolean = false
 
    override fun create(context: Context): String? {
        // todo something
        return this.javaClass.simpleName
    }
 
    override fun dependencies(): List<Class<out Startup<*>>>? {
        return null
    }
 
}
Copy the code

Because SampleFirstStartup does not depend on other components until it executes, its dependencies() method returns null and is executed on the main thread.

Note: ️ although waitOnMainThread() returns false, since it is executed on the main thread, which is blocked by default, this method setting will be invalidated when callCreateOnMainThread() returns true.

Suppose you also need to define SampleSecondStartup, which depends on SampleFirstStartup. This means that SampleFirstStartup must be executed before SampleSecondStartup can be executed.

class SampleSecondStartup : AndroidStartup<Boolean>() { override fun callCreateOnMainThread(): Boolean = false override fun waitOnMainThread(): Boolean = true override fun create(context: Context): Sleep (5000) return true} Override fun dependencies(): List<Class<out Startup<*>>>? { return listOf(SampleFirstStartup::class.java) } }Copy the code

In the dependencies() method, SampleFirstStartup is returned, so it ensures that SampleFirstStartup finishes first. It is executed in child threads, but since waitOnMainThread() returns true, the main thread blocks and waits until it finishes.

For example, you also define SampleThirdStartup and SampleFourthStartup

Automatically configured in the Manifest

The first method of initialization is automatic configuration in the Manifest.

The StartupProvider class is provided in Android Startup, which is a special Content provider that automatically identifies initialization components configured in the Manifest. To enable automatic recognition, you need to define the

tag in the StartupProvider. Name is the defined component class, and value is android.startup.

<provider
    android:name="com.rousetime.android_startup.provider.StartupProvider"
    android:authorities="${applicationId}.android_startup"
    android:exported="false">
 
    <meta-data
         android:name="com.rousetime.sample.startup.SampleFourthStartup"
        android:value="android.startup" />
 
</provider>
Copy the code

You do not need to add SampleFirstStartup, SampleSecondStartup, and SampleThirdStartup to the

tag. This is because SampleFourthStartup relies on these components in its dependencies(). StartupProvider automatically identifies other components that depend on the declared component.

Manually configured in Application

The second method of initialization is manually configured in Application.

Manual initialization is required using startupManager.Builder ().

For example, the following code uses startupManager.Builder () for initial configuration.

class SampleApplication : Application() {
 
    override fun onCreate() {
        super.onCreate()
        StartupManager.Builder()
            .addStartup(SampleFirstStartup())
            .addStartup(SampleSecondStartup())
            .addStartup(SampleThirdStartup())
            .addStartup(SampleFourthStartup())
            .build(this)
            .start()
            .await()
    }
}
Copy the code

If you enable logging and then run the project, the console will print out the execution order of the initialized components optimized for topology sorting.

 D/StartupTrack: TopologySort result: 
    ================================================ ordering start ================================================
    order [0] Class: SampleFirstStartup => Dependencies size: 0 => callCreateOnMainThread: true => waitOnMainThread: false
    order [1] Class: SampleSecondStartup => Dependencies size: 1 => callCreateOnMainThread: false => waitOnMainThread: true
    order [2] Class: SampleThirdStartup => Dependencies size: 2 => callCreateOnMainThread: false => waitOnMainThread: false
    order [3] Class: SampleFourthStartup => Dependencies size: 3 => callCreateOnMainThread: false => waitOnMainThread: false
    ================================================ ordering end ================================================
Copy the code

The complete code example can be obtained by checking the app.

More and more

Optional configuration

  • LoggerLevel: Controls log output in Android Startup. Possible values include loggerLevel. NONE, loggerLevel. ERROR, and loggerLevel.debug.

  • AwaitTimeout: controls the timeout duration of the main thread in Android Startup.

The Manifest of the configuration

To use these configurations, you need to define a class that implements the StartupProviderConfig interface and implements its corresponding methods.

class SampleStartupProviderConfig : StartupProviderConfig {
 
    override fun getConfig(): StartupConfig =
        StartupConfig.Builder()
            .setLoggerLevel(LoggerLevel.DEBUG)
            .setAwaitTimeout(12000L)
            .build()
}
Copy the code

At the same time, you need to configure StartupProviderConfig in the Manifest.

<provider
     android:name="com.rousetime.android_startup.provider.StartupProvider"
    android:authorities="${applicationId}.android_startup"
    android:exported="false">
 
    <meta-data
         android:name="com.rousetime.sample.startup.SampleStartupProviderConfig"
         android:value="android.startup.provider.config" />
 
</provider>
Copy the code

After the above configuration, analytical SampleStartupProviderConfig StartupProvider automatically.

In the Application configuration

Startupmanager.builder () is used to configure the Application.

override fun onCreate() {
    super.onCreate()
 
    val config = StartupConfig.Builder()
        .setLoggerLevel(LoggerLevel.DEBUG)
        .setAwaitTimeout(12000L)
        .build()
 
    StartupManager.Builder()
        .setConfig(config)
        ...
        .build(this)
        .start()
        .await()
}
Copy the code

methods

AndroidStartup

  • CreateExecutor (): Executor: This method is used to control running child threads if the defined component is not running on the main thread.

  • onDependenciesCompleted(startup: Startup<*>, result: Any?) This method calls back after each dependency completes.

Practical test

Android Startup is used in AwesomeGithub, optimized configuration initialization time and configuration injection time of component development, comparison of time before and after use:

state The launch page Elapsed time
Before using WelcomeActivity 420ms
After using WelcomeActivity 333ms

AwesomeGithub

AwesomeGithub is based on Github client, pure exercise project, support component development, support account password and authentication login. Kotlin language for development, the project architecture is based on JetPack&DataBinding MVVM; Popular open source technologies such as Arouter, Retrofit, Coroutine, Glide, Dagger and Hilt are used in the project.

In addition to the Android native version, there is a cross-platform version of Flutter based on Flutter_Github.

If you like my article, you can follow my wechat official account: [Android supply station] or scan the following TWO-DIMENSIONAL code to follow, of course, you can also directly follow the current website account. The main difference is that wechat is more interactive.

Public update will not be very frequent, but once the update must be pure dry goods.