The App Startup Pitfalls
If you are an Android developer, you probably heard about the App Startup library. It provides a performant way to initialize components at the app startup. A lot of library authors implement an automatic initialization of their libraries at the app startup using content providers. If your app uses a lot of libraries, which use content providers, it can have a significant negative impact on the app startup time. The App Startup library solves this issue by allowing to define component initializers that share a single content provider. Each library developer can implement an initializer instead of the content provider and all libraries can then share one content provider in the app. You can read more details about the App Startup library in this article or official docs.
Apart from using the App Startup as a library developer, you can use it also as an app developer in your app. It can be especially useful for bigger apps that need to initialize a lot of libraries manually in their
Application class (i.e. Koin or Timber). By using the App Startup library, you can extract these initializations to separate initializers, making your code cleaner and reducing the amount of code in the
Application class or removing it completely. Even though it sounds great, there is one issue with this approach that does not have to be really obvious. This article explains it together with the solution.
The App Startup Library: Problem
App Startup uses a content provider to initialize all components (both from libraries and your app). The problem might come when you also use a library that does not use App Startup yet and uses its own content provider. When you want to use this library in some initializer, it can easily happen that the library was not yet initialized because its content provider was not called yet. It is really easy to fall into this trap, because this situation is new. Without App Startup, all initializations usually happen in the
Application class that is called after all content providers are created, so you can safely access all libraries that use their own content provider for initialization. When using App Startup, your initializers accessing libraries’ code are on the same “initialization level” as content providers of those libraries. Here is a picture for a better illustration
Initializer 3 accesses a library code initialized in
Content provider B, the app will probably crash or will not behave correctly. On the other hand
Application can safely and reliably access any code that is initialized in any content provider. As we can see, the real problem here is the order of creation of content providers. If you need one initializer in App Startup to depend on another one, you can declare this dependency to be sure that the initialization order will always be correct. Unfortunately the initializer can’t declare dependency on the content provider. Or at least not directly. Fortunately, there is a way, how you can influence the order of creation of content providers, so let’s see how it works.
Init order of content providers
When you declare a content provider in the manifest, you can also specify the
initOrder attribute. It allows the application to express dependencies between content providers and specify the order of their creation. The type of the value is
Integer and the higher the value is, the sooner a content provider is created. So for example let’s have these three content providers declared in the manifest together with a specified
… …. … android:name=”.ContentProviderA” android:initOrder=”-1” /> … android:name=”.ContentProviderB” android:initOrder=”0” /> … android:name=”.ContentProviderC” android:initOrder=”1” /> …
At the app startup, the init order of content providers will be:
Content provider C ->
Content provider B ->
Content providerA. But what if we do not specify the
initOrder? What will be the value? This is not mentioned in the docs, but I tested this and as you probably guess, the value will default to 0 (surprise). More interesting question is: What will be the init order of content providers that have the same
initOrder value?. The answer is: Random, but not really. Confusing, right? Let’s explore this situation in more detail.
If more content providers have the same
initOrder value, the system can’t determine the init order based on this value and thus it selects some order on its own. This order can be both deterministic and nondeterministic depending on the execution context. More specifically, if you run the app with three content providers, which use the same initOrder value, multiple times on one Android version, the init order will always be the same. However, when you run this app multiple times on another Android version, the init order will always be the same on this Android version, but might differ from the previous one. Let’s look at the example that comes from my own testing of this behavior.
In the above table, we can see six different content providers (including
InitializationProvider from the App Startup) and three different devices running different Android versions. Each cell with the number represents the init order of the particular content provider on the particular device when run on one of our apps. Running the app on API 30 and 31 had the same init order of content providers, but on API 28 the order was different. It does not matter how many times you run the app on the particular device, the order is always the same for it.
The solution to the init order problem is simple. We need to be sure that our
InitializationProvider will be executed as the last one, so we can be sure that our initializers can safely access all code initialized by other content providers. In order to achieve this, we need to set the
initOrder value of our
InitializationProvider to be smaller than the
initOrder value of all other content providers. Usually most of the third party content providers do not have the
initOrder specified at all (defaults to 0). In our example only
FirebaseInitProvider sets the
initOrder value to 100. It means that if we want to be almost 100% sure that our content provider will run as the last one, we can set it to
Int.MIN_VALUE (-2147483648). This way only a content provider with the same
initOrder value could be executed later on some Android versions and it is very unlikely that any library will set this
App Startup is a great library for improving app startup performance. It can be also used for transforming your big init code in the
Application class to smaller focused initializers. However, this can lead to the init order issue and if you want to use this reliably, you should specify the smallest possible
initOrder value of your
InitializationProvider to avoid it. Alternatively, it could also be a good idea to just leave your custom init code in the
Application class or leave at least the code that depends on other libraries there.