Image for post
Image for post
Illustration by Virginia Poltrack

App Startup, Part 2

Lazy Initialization

Chet Haase
Dec 16, 2020 · 8 min read

In the previous article, I showed how content providers (which show up in an app’s merged manifest file) automatically load some libraries and modules at launch time.

In this article, I will cover the use of the AndroidX App Startup library to have more control over when and how those libraries get loaded. And maybe, just maybe, we’ll see how to save time at app startup along the way.

Auto-Init with the App Startup Library

You do all of this in three steps, by adding App Startup as a dependency in your build.gradle file, creating an Initializer for each library that needs to be initialized, and adding information to your Manifest.xml file.

Let’s look again at the WorkManager example that I was using in Part 1. To load WorkManager via App Startup, I first added App Startup to my app’s build.gradle file:

implementation “androidx.startup:startup-runtime:1.0.0”

Next, I created an Initializer, which is an interface provided by App Startup:

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(): List<Class<out Initializer<*>>> {
// No dependencies on other libraries.
return emptyList()
}
}

Every Initializer has two functions to override: create() and dependencies(). dependencies() is used to establish a specific ordering in which to init multiple libraries. I didn’t need that functionality in this case, since I was only dealing with WorkManager. If you use several libraries in your app, check out the App Startup user guide for details on using dependencies().

For the create() function, I mimicked what I saw in WorkManager’s content provider.

By the way, this tends to be the way to go about this part of using App Startup; a library’s content provider is responsible for initialization, so you can usually use the code in that class as a hint on how to do it manually instead. There can be problems in some libraries if they are calling hidden or private APIs, but fortunately WorkManager was not, so this worked for my situation. Hopefully it will work for yours.

Finally, I added two provider tags inside of the <application> block of Manifest.xml. The first was this:

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

This WorkManagerInitializer tag is important because it tells Android Studio to remove the auto-generated provider that comes from adding the WorkManager dependency to the build.gradle file. Without this special tag, the library would continue to be initialized at startup automatically, and you might get an error later when App Startup tries to initialize it, since it has already been initialized.

Here is 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>

This InitializationProvider tag is almost the same as the one that is auto-generated by simply adding the startup dependency to the build.gradle file (as you can verify by looking at the merged manifest file — see Part 1 for more details on that). But there are two important differences:

tools:node="merge"

This is an attribute for manifest merging that is managed by Android Studio. It tells the tool to merge multiple incarnations of this tag in the final merged manifest. In this case, it will merge the <provider> that was auto-generated by the library dependency with this version of the provider, so there will be only one in the final merged manifest.

The next interesting line holds the meta-data:

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

This metadata tag inside of the provider tells the App Startup library where to find your Initializer code, which will be run at startup to init the library. Note the difference in how this happens: when you are not using App Startup, initialization happens automatically because Android creates and runs the content provider in that library, which then inits the library itself. But by telling App Startup about your Initializer, and by removing WorkManager’s provider from the merged manifest, you are telling Android to use the content provider of App Startup to load WorkManager’s library instead. If you init multiple libraries in this way, you effectively pool all of these requests through this single App Startup content provider instead of causing each library to create its own.

Be Lazy… If You Feel Like It

For example, maybe there is a particular flow in your app that needs some content-provider-initialized library which doesn’t happen immediately upon startup. Or may not even happen at all in some usages. In that case, why spend time initializing a large library at startup that is only needed in that code path? Why not, instead, wait until the library is actually needed to take on that initialization cost?

This is where App Startup shines: It helps you remove hidden content providers from the merged manifest and from the startup process, and to initialize these libraries later and more intentionally.

Lazy-Init with App Startup

We’re actually almost there with the code above: you need the same dependencies in build.gradle for startup and whatever other libraries you want to use. And you need the special “remove” provider tag to strip out the auto-generated content provider for each of the libraries. All we need to add at this point is a little more information to the manifest file to tell it to also remove the App Startup provider. Then none of these pieces will happen at start time, and it will be up to you to trigger initialization whenever you deem the time is right.

To do this, I replaced the InitializationProvider in the previous section with the one below. The one I showed above told the system where to find the code to auto-initialize your libraries in its content provider. Now I want to skip that part and just have it remove the auto-generated provider for startup instead, since I manually trigger the initialization later:

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

After I made this change, there were no longer any content providers in the merged manifest, so neither App Startup nor WorkManager were being automatically initialized at launch time.

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

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

AppInitializer is provided by the App Startup library to connect these pieces. You create the AppInitializer object with a context object, then pass it a reference to the Initializer(s) you created for initializing the various libraries. In this case, I pointed it at my MyWorkManagerInitializer and I was done.

Timing Is Everything

Note, as mentioned before, that these timings were with locked clocks, as discussed in that testing article, so these durations are much larger than they would have been with unlocked clocks. They are only meaningful in comparison to each other, and not to any real-world situation. Here’s what I found:

  • Without WorkManager: 1244 ms
  • With WorkManager loaded via content provider: 1311 ms
  • With WorkManager loaded via App Startup: 1315 ms
  • With WorkManager (loaded lazily, not at startup): 1268 ms

Finally, I timed how long it took to initialize WorkManager manually with AppInitalizer:

  • WorkManager Init via AppInitializer: 51 ms

There are a couple of takeaways from this data. First, WorkManager added an average of 67 milliseconds (1311–1244) to the startup time of my app when it was loaded during startup. Note that loading it the usual way (with content providers) took roughly the same amount of time as loading it with App Startup (1315–1244 = 71 ms). This is because App Startup isn’t actually saving us anything for the single-library case; we’re just shifting the work to happen in a different code path. There can be benefits from loading several libraries through App Startup, but for the single-library case here, there is no time-savings benefit to this approach.

Meanwhile, initializing WorkManager lazily allowed me to defer about 51 ms of that duration until a later time.

Is this significant enough for you to worry about? The answer, as always, is “it depends.”

51 ms out of 1.3 seconds is less than 4% of the total, and it would be even less in a real app that was doing much more than my simple app. This duration might not be worth the bother in your situation. On the other hand, you might find that some of your libraries take much longer to initialize. Or, even more likely, you probably use several libraries with content providers, each of which adds another chunk to your startup time. If you can defer most or all of these to a more appropriate time and get them out of the startup path, then maybe you could see significant launch-time benefits from App Startup.

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

  • Look at your merged manifest. How many <provider> tags do you see?
  • Can you remove some or all of those content providers out of the merged manifest using App Startup and see how it impacts startup time? And can you do so in a way that does not impact runtime behavior? (Note that you need to make sure to initialize libraries before your app starts implicitly depending on their functionality.

In the meantime, happy performance testing and improving. I’ll keep looking into more ways to analyze and improve app performance, and will post about it when I find anything worthy. When there’s time.

Android Developers

The official Android Developers publication on Medium

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store