Lots of developers love the
Application class as a container for application-wide resources, because:
- it’s a singleton, so it’s easy to ensure that each of these global resources is only initialized once
- it has access to a
Context(in fact, it is a
- it’s easy to obtain a reference to the
This, however, is actually a really bad idea that will only cause you pain, for two main reasons:
- It creates memory pressure on the rest of the operating system when your app is invoked for simple tasks, and
- It gives
Applicationtoo much scope, which makes your code harder to maintain.
I’m going to explain these reasons in detail, but first, I’ll show you how not to do app-level state, using an example app that I just made up. It’s called PictoBlaster, because it allows you to apply filters to pictures and then ‘blast’ them to your friends1.
How not to do it
It’s really easy to go down the route of putting resources in your
Application class (again, please don’t do this):
- Write a
BadAppclass that inherits from the
- In your
AndroidManifest.xml, add the
android:nameattribute to your
application(in the same way that you would for an
- In your
Seriously, don’t do this.
1. Memory Pressure
Developers often forget that
BadApp is loaded into memory when Android initializes your process, and before any other component can be loaded. That’s not a big problem for apps that only have a single Activity, but as soon as you start adding other components (other Activities, Services, BroadcastReceivers, or ContentProviders),
BadApp has to load all its dependencies before any of those can be started. This is especially true for components that are supposed to be lightweight, such as BroadcastReceivers and ContentProviders.
Loading those resources burdens the operating system unnecessarily. Consider the following scenario:
- A user decides they want to send a picture from PictoBlaster to a friend, so they press the “Send” button in the app.
- PictoBlaster detects that it’s not connected to the network, so it queues up the message for when it receives an
- The user opens up another app, and PictoBlaster’s process gets killed because PictoBlaster is not in use any more.
So far, so good. The problem occurs when the PictoBlaster process has to be loaded again for what should be a lightweight component:
- The user’s phone detects a network connection change, and notifies PictorBlaster’s
- Because the PictoBlaster process died earlier, it has to be recreated, which means that the
BadAppclass loads all of its resources into memory – Filters, Cheesy memory-intensive stickers, Fonts, everything.
BadApphas finished initializing, the
BroadcastReceivernotices that the phone has reconnected, reads the file from disk, sends it over the network, and exits.
- The system then detects that PictoBlaster is no longer in use, and frees up all the memory again.
Not only is this a waste of the users’ battery (how long did it take to initialize all that stuff?), if BadApp initializes enough stuff, it can also create memory pressure that causes other apps to be shut down3. This is especially true if the user’s loaded another memory-intensive app in the meantime.
The root problem: eager initialization
The root problem here is that resources are loaded eagerly on process start, whether they’re going to get used or not. This is almost never something you want. In a resource-constrained mobile environment, it wastes battery and memory, which can kill other apps that the user may be interacting with. That’s a terrible user experience.
It is possible to work around this by making all of your Application-level resources lazy-loaded using an explicit lazy initialization mechanism, such as Guava’s
Suppliers.memoize() or Apache Commons’
LazyInitializer. This workaround still doesn’t solve the maintainability problem, though.
The other major problem with initializing all your resources in your
Application is that it makes it easy to give your
Application too much scope. Once there’s a precedent for putting app-wide resources in your
Application class, other developers on your team will put them there too. Then, you’ve got a class that owns loads of different resources.4
A side effect of giving your
Application too much scope is that if you ever want to reuse code that uses these dependencies, pulling the code out into a separate library isn’t a simple task - you’ve got to decouple your app-wide resources from the
Application object first.
A solution: the good old-fashioned static instance
I think that the real reason why a lot of developers like the
Application class so much is that programmers have often been taught that static members are evil. Actually, static members are extremely useful whenever you’re working with resources that are scoped to a process (the app, in this case). The
Application class represents exactly the same concept, it just allows developers to avoid the icky feeling they get from using static members instead.
Java’s semantics for static members are pretty useful. Static members are initialized when the class is loaded, which means that they’re loaded the first time you use a static member or instantiate the class. This gives you lazy-loading for free, without having to write boilerplate yourself. The ‘static instance’ approach is one that Android framework engineers have recommended in the past.
The idea is to define a class like this:
…and then reference
AppWideResource.INSTANCE whenever you need one.
There are a couple of disadvantages to this approach:
- In order to make code that uses the singleton testable, you need to be consistent about passing static instances around as constructor parameters, instead of referring to the static instance directly (this concept is known as Dependency Injection). To be clear, the hard part of this isn’t passing static instances as parameters, it’s the consistency bit. Consistency is especially difficult on large or distributed teams.
- It’s more boilerplate when you need access to the application-level
Context. The Android documentation for Application hints at a solution like this:
Likewise, if you need other static resources to initialize your
AppWideResource, just add them as parameters to the
The structure of this approach means that:
- Resources are lazy-loaded, which means your app won’t chew through a users’ memory or battery every time a
- You’re establishing a pattern of making your app-wide resources isolated and distributed, which improves their modularity and reusability for the future.
Our metrics look like hockey sticks and we just closed a Series A for $20m, probably. Our logo is the pair of emoji 📷🚀. ↩
An alternative to the
BadApp#getInstance()method is to use
(BadApp) context.getApplicationContext(). This isn’t any better. ↩
A really good indication that an object has too much scope is when you find yourself mocking things out in tests that seem completely unrelated. If you find yourself doing this, it might be time to rethink your design. ↩