Jetpack App Startup: A Deep Dive and Some Concerns
The Reddit thread for my blog post on Exploiting Content Providers had an interesting comment:
Quoting the docs, here’s what App Startup is about:
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.
It promises a few things:
- A simple API which can be used by both libraries and applications
- Performant initialization of components to improve app startup times
- Ability to explicitly request a particular order of initialization
I wanted to try it out in my own project WhatTheStack, so I decided to take a look at the source code and share my thoughts on it.
A lot of libraries use empty Content Providers to run initialization logic automatically at application startup. As the number of libraries using this trick increases, so does the number of empty content providers. Apparently a large number of them affects startup time significantly, as illustrated by a chart in this video:
The idea of the App Startup library is to reduce the number of such content providers by replacing them with just the one supplied by it.
App Startup relies on consumers registering the components they want to be initialized at startup. Let’s use the canonical example of a custom logger.
To register a component, create an initializer for it by implementing the
Initializer<T> interface, and add a corresponding Manifest entry:
createmethod must initialize the component being registered and return an instance of it.
dependenciesmethod must return a list of
Initializerclasses which need to run before the
createmethod of this initializer. This is how you specify the dependencies of the current component to make sure that they are available beforehand.
<provider>registers the content provider supplied by App Startup, which is called “InitializationProvider”.
<meta-data>of the provider registers the initializer we created above.
Each initializer must go in a separate metadata tag and contain
android:valueattribute. This is important because App Startup uses this string to filter out metadata tags which do not contain initializers.
With this setup complete, App Startup will discover the
LoggerInitializer class and initialize it automatically at application startup.
Components which are discovered through metadata tags are initialized automatically. To disable this for a particular component, add the
tools:node="remove" attribute to the
<meta-data> tag for it.
The component can then be lazily initialized using the
To understand why this works, and how App Startup discovers initializers automatically, let’s take a look under the hood.
Under the hood
The source code for App Startup is available here. This post is based on the first public release (
1.0.0-alpha01) of the library. There might be major changes as it matures. This snapshot represents what the code looks like at the time of writing.
App Startup makes use of the Manifest Merger feature of Android’s build tooling. All declarations of the
InitializationProvider in manifests of dependencies are merged into a single
<provider> entry in the final manifest of our application. The final
<meta-data> tags of all merged entries. On app startup, the OS sees this content provider in the manifest and therefore creates it automatically.
AppInitializer upon creation, which then accesses the merged
<meta-data> tags to find registered initializers.
The metadata bundle is like a
Map containing merged tags as key-value pairs. In each pair, the key (declared with
android:name) contains the fully qualified class name of an initializer, and value (
android:value) contains the string
"androidx.startup". Refer to the example shown in the Usage section to see how this is declared.
In each iteration, the key is used to instantiate the Initializer class using Reflection.
doInitialize method contains the bulk of the code related to component instantiation and initializing its dependencies. The dependencies are initialized before the component itself, repeating this process recursively for each dependency.
After the component is initialized it is stored in a
Manifest Merger also explains why we need to add
tools:node="remove" for lazy initialization to work. A metadata tag containing this attribute is not merged into the final content provider entry of the Manifest, and therefore its initializer is not discovered at startup.
In my opinion, using manifest merger this way is a neat trick. I wonder how many folks outside Google understand the Android build process well enough to come up with unique solutions like this.
I like App Startup so far, but I have a few concerns about it in its current state:
- Performance benefit of using App Startup depends on libraries rolling out support for it. If a library decides against supporting it, creating initializers for it on your own is futile because doing so will not prevent its content providers from running at startup. You might be able to manually disable them, though.
- Initializers are required to have a no-arg constructor because they need to be instantiated through reflection. This is a drawback if you want to wire them with constructor injection.
createmethod of initializers is required to return an instance of the component being initialized. This does not make sense for libraries which are never meant to interacted with in code (such as WhatTheStack) as there is no “instance” to return.
- Someone will misuse the
AppInitializersingleton as a service locator, and that’s not what its meant for.
With the negatives out of the way, there is also a lot to like here as well. I am happy to see that libraries do not need to declare App Startup as an
api dependency in their build config. This means that its usage is opaque to applications consuming those libraries. I also like that now there is a standard way to write initialization code for libraries that run at startup.
Well, not “standard” yet. We will have to wait and see if App Startup takes off.
If you would like to see an example of migrating your app/library to use App Startup instead of Content Providers, check out this PR. While you are there, consider checking out some of my other projects on my GitHub.
Huge thanks to Subrajyoti Sen and Anubhav Gupta for helping me with this post.
Question, comments or feedback? Feel free to reach out to me on Twitter @haroldadmin