Going multiprocess on Android
That moment when one Dalvik alone is no longer enough.
Living with memory constraints
There are many aspects that make Android unique as a mobile OS, but sometimes it can be quite tough to approach, especially from a developer standing point.
Take the memory restrictions, for example. With iOS offering virtually no limit on an app’s memory budget (say, 200 MB is not a big deal), Android has heavy limitations that go from 24/32/48 MB for the most recent devices to a minuscule 16 MB for older devices.
That RAM budget is everything your app has got to work with. Meaning, it must be sufficient for loading classes, threads, services, UI resources AND the content your app wants to display. Imagine a photo browsing app with a grid of awesome pictures, or a music player that needs to play in the background: the horror.
To understand why Android poses these restrictions and what solutions it offers to cope with them, we need a tiny little bit of background about what’s happening under the hood.
Android processes: explained!
You should already know by now that Android is based on Linux. As such, each application runs in its own process (with a unique PID): this allows the app to live in an isolated environment, where it cannot be hindered by other applications/processes. Typically, when you want to start an application, Android creates a process (forking the Zygote), spawns the main thread and starts running the main Activity.
What you probably don’t know is that you can specify some components of your app to run on a different process than the one that was used to start the app. Meet this nice little attribute:
The process attribute can be applied to activities, services, content providers and broadcast receivers and specifies what process that particular component should be executed in.
In this example, I’m specifying that the MusicService must be executed in a separate “music” process:
What good is it, then?
In the introduction, I mentioned that each Android app has a memory budget for its execution that cannot be exceeded. To be precise, that limit is enforced in a per-process basis. In other words, each process of an application will have have a dedicated memory budget! (not to mention different rules for its termination, which is even more cool)
Let’s see whether this approach is going to be a good thing or a bad thing. [spoiler: it’s going to be both]
What’s cool about having multiple processes
As I just mentioned, a separate process can take advantage of its own RAM budget, allowing the main process to have more space for its resources.
In addition, processes that run different components are treated differently by the OS. This means that, in conditions where the system is running low on available memory, not all of them are to be killed. Consider this: your music player is running in the background and the music is playing; suddenly, the system needs to free some memory (because Facebook, that’s why). Since the service that plays the music runs on another process, it is extremely likely that the OS is going to kill the main process first (the one that runs mainly your app UI), leaving the music playing in the other process.
On a final note, this looks good for the end user as well! Because each of your app’s processes will have its own entry in the RAM usage screen of the Application Manager, and chances are that one or more of them are going to appear in the “Cached” section (which means they are inactive).
What sucks about having multiple process
Unfortunately, quite a lot. In fact, you are about to learn that having multiple processes is not for the faint of heart.
First of all, processes are meant to be isolated by design (as a security feature), which means that every process will have its own instance of Dalvik VM. In turn, this means that you won’t be able to share data across those instances, at least not in the traditional sense. For example, static fields will have their own value for each process, and not a single one as you are inclined to believe. And this extends to every state of the application.
Does this mean that no inter-communication is possible between two separate processes? No, actually it is possible, and there are several ways of doing that. Most notably, Intents can “travel” across processes, and so can Handlers and Messengers. You can also rely on AIDL (Android Interface Definition Language) and Binder, which is what you usually do when you’re declaring a bound service (but you can do more!).
Do I need multiple processes?
It depends, of course, and you need to read the signs. If your users are experiencing OutOfMemory errors more and more frequently or they are complaining about how your app is extremely RAM hungry, you might consider using one or more separate processes.
The music player example is one of the most common scenarios where a second process can do extremely good to your app, but there are many more. For instance, your app is a client for a cloud storage service: delegating the synchronization service component to a dedicated process seems perfectly reasonable, so even if the UI process gets killed by the system, the service can still run and keep files up to date.
If you think you need it, then I suggest you to play with a small testbed app first: only by actually facing both the advantages and the intrinsic complexity of having multiple processes you will be able to decide whether you really need it, and if so, what’s the best way of handling it without going insane.
I know I have barely scratched the surface of this subject, but I wanted to give you practical advices rather than going through all the theory and working mechanisms that regulate processes at the OS level.