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 aContext
), and - it’s easy to obtain a reference to the
Application
from anywhere.
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
Application
too 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
BadApp
class that inherits from theApplication
classIn your
AndroidManifest.xml
, add theandroid:name
attribute to yourapplication
(in the same way that you would for an<activity/>
or<service/>
):<application android:name="com.example.pictoblaster.BadApp" > <!-- ... --> </application>
In your
BadApp.java
2:public class BadApp extends Application { private static BadApp sInstance; @Override public void onCreate() { // TODO: Initialize app-wide resources sInstance = this; } public static BadApp getInstance() { return sInstance; } }
Seriously, don’t do this.
Why not?
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
android.net.conn.CONNECTIVITY_CHANGE
broadcast. - 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
BroadcastReceiver
. - Because the PictoBlaster process died earlier, it has to be recreated, which means that the
BadApp
class loads all of its resources into memory – Filters, Cheesy memory-intensive stickers, Fonts, everything. - After
BadApp
has finished initializing, theBroadcastReceiver
finally loads. - The
BroadcastReceiver
notices 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.
Yikes.
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.
2. Maintainability
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:
public class AppWideResource {
public static final AppWideResource INSTANCE = new AppWideResource();
private AppWideResource() {}
// Rest of class implementation goes here...
}
…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:public class AppWideResource { private static AppWideResource sInstance = null; private AppWideResource(Context context) {} // Synchronize this method to prevent the instance from being initialized // twice. public synchronized AppWideResource getInstance(Context context) { if (sInstance == null) { // Call getApplicationContext here because the original context might be // an Activity or Service, which won't be valid for the lifetime of the // static instance. An Application context will be valid for this entire // time. sInstance = new AppWideResource(context.getApplicationContext()); } return sInstance; } }
Likewise, if you need other static resources to initialize your AppWideResource
, just add them as parameters to the AppWideResource#getInstance()
method.
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
ContentProvider
or aBroadcastReceiver
spins up - 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. ↩︎For more on how Android kills processes when memory is low, take a look at ‘Processes and Threads’. ↩︎
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. ↩︎