In the last article, I showed how the Content Provider (which appears in the merged application manifest file) automatically loads third-party libraries and modules at application startup.

In this article, I’ll show you how to use AndroidX’s App Startup library to further control when and how those libraries are loaded. Maybe, I mean maybe, we’ll also figure out how to shorten the startup time of an app.

Automatic initialization using the application startup library

The easiest way to use App Startup is to use its Content provider to initialize other libraries in the background. You can either specify how the application launch library should initialize other libraries, or remove content providers for other libraries from the merged manifest file. Instead of using multiple Content Providers to perform startup tasks, use resources to load the application startup library and then load the rest of the content.

You can do this by adding the application startup library as a dependency in your project’s build.gradle file, creating an Initializer for each library that needs to be initialized, and adding relevant information in your project’s manifest.xml file.

Let’s look again at the WorkManager example I used in the first article. To load the WorkManager from the application startup library, I first added the application startup library to the build.gradle file of the application:

/ / to view the latest version number https://developer.android.google.cn/jetpack/androidx/releases/startup def startup_version = "1.0.0" Implementation "androidx. Startup: startup - runtime: $startup_version"Copy the code

Then, based on the Initializer interface provided by the app startup library, I created an Initializer:

class MyWorkManagerInitializer : 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<*>>> {
        // There are no other dependent libraries
        return emptyList()
    }
}
Copy the code

For each Initializer, two methods need to be copied: create() and dependencies(). Dependencies () is used to specify the initialization order of multiple dependent libraries. I don’t need this functionality in this example because I only need to work with the WorkManager. If you need to use more than one library in your application, check the application startup manual for details about using Dependencies ().

For the create() method, I mimic the implementation in WorkManager’s Content Provider.

By the way, this is actually a common method when using application startup libraries. A library’s Content Provider is responsible for its initialization implementation, so you can usually implement it manually by referring to the code in that class. Some libraries can be cumbersome because they use hidden or internal apis, but the good news is that WorkManager doesn’t, so I can do this and hopefully the approach will work for you as well.

Finally, I added two provider tags to the manifest.xml file’s

code block. The first one looks like this:

<provider
    android:name="androidx.work.impl.WorkManagerInitializer"
    android:authorities="${applicationId}.workmanager-init"
    android:exported="false"
    tools:node="remove" />
Copy the code

The WorkManagerInitializer tag is important because it indicates that Android Studio needs to remove the provider that was automatically generated after adding the WorkManager to the build.gradle file. Without this special tag, the library will still automatically initialize when the application starts, and then report an error when the application startup library tries to initialize it because it has already been initialized.

Here’s the second provider tag I added:

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

The InitializationProvider tag is basically the same as the tag automatically generated by adding the application startup library to the build.gradle file (you can verify this by looking at the merged manifest file — see the first article for details), But they differ in two important ways:

tools:node="merge"
Copy the code

This parameter is primarily used for the Manifest merge operation that Android Studio is responsible for. It tells the tool to merge multiple instances of this tag in the manifest file for the final merge. In this case, it merges the automatically generated by the library dependencies into this version of the provider, so that there is only one instance of the label in the final merged manifest file.

Another line contains this meta-data:

<meta-data  android:name="com.example.startuplibtest.MyWorkManagerInitializer"
    android:value="androidx.startup" />
Copy the code

The metadata tag in the provider tells the application startup library how to find your Initializer code, which is executed to initialize the library when the application starts. Notice the difference: if you don’t use the app startup library, the initialization is automatically performed because Android creates and executes the Content Provider in that library, and then automatically initializes the library itself. But by applying the startup library to specify your Initializer and removing the Provider from the WorkManager in the merged manifest file, This tells Android to load the WorkManager library using the Content Provider of the app launcher library instead. If you initialize multiple libraries in this way, you can effectively manage these requests using a single Content provider for the application launch library, rather than causing each library to create its own Content provider.

Have a lazy… If you want

When optimizing application startup performance, we can’t change code implementations that are out of our control. So the idea here is not to speed up the initialization of the libraries we use, but to control when and how those libraries are initialized. In particular, we can decide whether any library needs to be initialized at application startup (either by adding content providers to the merged manifest file using the library’s default mechanism, Or you can use the content Provider of the application launcher library to centrally manage initialization requests), again loading them later.

For example, you might need a library that contains content Provider initialization in a particular flow of your application, but that library doesn’t need to be loaded immediately upon application startup, or in some cases it doesn’t need to be loaded at all. If so, why take the time to initialize a large library at application startup because it is only needed in a particular code path? Why not wait until the library is actually needed to introduce the associated initialization overhead?

This is where the app launch library comes in handy. It can help you remove hidden Content providers from the consolidated manifest file and app launch process, and it can also help you delay or more purposefully load these libraries.

Use the application startup library for lazy initialization

We now know how to use the application startup library for auto-loading and initialization. Let’s take a closer look at how to implement lazy initialization if you don’t want to initialize it at startup.

Gradle file you need the same startup dependencies as any other library you want to use. You also need a special “Remove” Provider tag to remove content Providers automatically generated by each library. We just need to add a bit more information to the manifest file to tell it to remove the provider from the application launcher as well. This way, no Content Provider initialization takes place at application startup time, leaving it entirely up to you to decide when the initialization should be triggered.

To do this, I replaced the InitializationProvider I used earlier with the following code. The code shown above tells the system how to locate the code in the Content Provider that automatically initializes your library. Since the initialization will be triggered manually later, I’m going to skip that part this time, leaving only the part that removes the automatically generated Content Provider when the application starts.

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

After I made this change, there are no longer any Content providers in the merged manifest file, so neither the app startup library nor the WorkManager are automatically initialized at app startup.

To initialize these libraries manually, I added the following code elsewhere in the application to do it:

val initializer = AppInitializer.getInstance(context)
initializer.initializeComponent(MyWorkManagerInitializer::class.java)
Copy the code

AppInitializer is a class provided by the application startup library to connect all of these parts. You need to use a Context object to create the AppInitializer object, and you can then pass it an Initializer reference that you created for initializing the various libraries. In this example, I use MyWorkManagerInitializer and I’m done.

Time is everything

I ran several tests (using the timing method I mentioned in my article testing application startup performance) to compare several different ways to start an application and initialize the library. I counted with no libraries, with WorkManager (using the default auto-generated Content Provider), automatically initializing WorkManager at startup using the app startup library, and delaying the initialization of WorkManager using AppInitializer And the application startup library.

Note that, as we discussed in the previous article, all of these time calculations are based on locked CPU frequencies, so these times are much larger than they would be on a machine without locked CPU frequencies. They are only meaningful when compared to each other, not representative of the real situation. Here’s what I found:

  • Without WorkManager: 1244 MS
  • Load with WorkManager and via Content Provider: 1311 ms
  • With WorkManager and loaded via App Startup: 1315 ms
  • With WorkManager (lazy loading): 1268 ms

Finally, I counted the time it took to manually initialize the WorkManager using AppInitalizer:

  • usingAppInitializerInitialize theWorkManager: 51 ms

There are some implications to this data. First, loading WorkManager at startup time added an average of 67 milliseconds (1311 — 1244) to my app’s startup time. Note that the normal way to load this library (using a Content provider) takes about the same time as using the app launcher library (1315 — 1244 = 71 ms). This is because the application startup library doesn’t save us time in the case of a single library, we’re just moving the logic to another code path. If we used the application launcher library to load multiple libraries, we would get optimized results, but for the single library example here, there would be no time saving advantage using this method.

Also, delaying the initialization of the WorkManager allowed me to “save” about 51 milliseconds.

Is the difference significant enough for you to worry about? The answer is always “it depends”.

51 milliseconds is less than 4% of the total time of 1.3 seconds, and for a real application, which is usually more complex than my simple application, it will take a lower percentage of the total startup time. In this case, it’s probably not worth worrying about. But sometimes you might find that some libraries take too long to initialize, and more likely, you might use several libraries that come with Content Providers, each of which adds a bit to your application’s startup time. If you can postpone most or all of the above work to a more appropriate point in time and take it away from the startup process, you might find that your application launches significantly faster.

Like all performance tuning projects, the most important thing you can do is analyze the details, measure, and decide:

  • Check your project’s merged manifest file. You can see how much<provider>Tag?
  • Can you use the application launch library to remove some or all of these Content providers from the consolidated manifest file and see how it affects startup time? Can you do this without affecting runtime behavior? (It’s worth noting that you need to make sure you initialize the libraries before your application starts to rely on them.)

Finally, enjoy performance testing and tuning. I will continue to look for more ways to analyze and optimize the performance of my applications, and if I find anything valuable I will post about it.