Have you ever wondered why you don’t create instances of Activity subclasses yourself, but have the system do it for you? As you may know, Android is based on a Linux kernel. However, since the Linux kernel was created mainly for computers, there needed to be some modifications to it for it to work best on a mobile device.
- OOM Killer – Out of Memory Killer. Because users on a phone don’t explicitly close apps like you do on a computer, this is a modification to how memory is managed so that when memory is required for some higher priority process, lower priority processes are killed.
- Wakelocks – These are supposed to guarantee that the phone uses as little battery as possible, and that the phone stays awake as little as possible. It also is used to put the CPU into a dormant state as often as possible. Wakelocks is a counter. So when you’re doing most things, the Wake Locks counter has a size of 0, meaning there’s no process from preventing the phone from idling. However, when you’re watching a full screen video, for example, Wake Locks has a size of 2 – one for the screen and one for the audio. These prevent the phone from idling.
When you first boot an Android phone, the kernel launches something called the Init process. The init process is a binary picked up by the kernel that’s run on startup. It has a list of settings and definitions to startup processes, and wakes up all other parts of the operating system. This is therefore the root of all other processes. Some of these processes we know, like adbd, installd, logd, zygote. These are all daemons, meaning they’re run as a background process – the user doesn’t interact with them. As mentioned, Init launches some pretty important processes that the Android OS will make use of. One of these is called Zygote.
This is the base of all other applications. It was designed to share and save memory and reduce app startup time. Zygote is essentially a runtime environment that’s ready to start running an app. Each application is run in its own runtime environment. Each time you launch an app, Zygote forks itself, meaning it clones itself and all its loaded memory, and the app is run on that forked Zygote process. Zygote also has preloaded resources and preloaded classes that can be used to run any app. This is also good for memory management, since the preloaded resources/preloaded classes only have to be initialised once and then cloned later on.
Usually with Linux, all apps run under the same user ID – e.g. Jordan Dixon. With Android, this was modified so that every app gets run under a different, unique user ID. This means memory can’t be shared, corrupt files, or touch other processes. This is obviously good so that malicious apps can’t corrupt your other apps in this way, but it presents some challenges for how applications can communicate. This is where something called IBinder comes in play.
When you ask for layout inflator, or an instance of an Activity, or access to a service, these are all system calls. Because each app is sandboxed, and the only thing that can see all processes is the kernel, when you request a system service like layout inflator, that request goes all the way down to the kernel, which calls the correct process on your behalf, that process then returns the result, and the result is passes back to you. So now we know this, why do Activities have to be managed by the system? There are other processes that get created by the Init process that handle this for you. These are all bundled in System Services.
This is also created by the init process. It contains three important managers – Activity Manager, Window Manager, Package Manager.
Window Manager keeps track of windows. It also is responsible for animations and transitions, and it sometimes does this by snapshotting the current view and presenting the user with that while the animation happens underneath. Window Manager is also the process responsible for handling system level gestures. So if you don’t consume a gesture, that gesture walks all the way up the tree, and if it still hasn’t been consumed, Window Manager will decide what to do with it.
Package Manager is the process responsible for installing new applications. It’s also responsible for looking at the manifest files of each application. When the phone first starts up, Package Manager goes through all of the manifest files in each of your applications and compiles a huge list of every activity in each application, and all the possible intent that your installed applications can perform. This is used for when you start an Intent in your application – Package Manager will resolve it for you. It can match activities by name, or by action.
Activity Manager, unsurprisingly, manages applications (activity, services, content providers). It’s responsible for creating/destroying activities if need, and manages all the lifecycle events that get called on your activities. Activity Manager also handles killing rogue activities that are taking too much memory, and configuration changes.
Activity Manager will pile up our applications into data structures. It uses a stack (there can be multiple stacks, e.g. split screen), and inside each stack will be a task which correlates to one or more process. Inside each task can be one or more activities. For each activity that’s created, Activity Manager will create a record for it, including what it is, where it is, what state it’s in, what window it has, etc.
If you were to have a share screen in the CoolApp process, and then that opens an activity on the BestApp process, that record is placed in the CoolApp task but it points to the Best process, rather than the CoolApp process.
It also manages more lifecycles. For example, even on split screen, it ensures that there’s only ever one resumed activity. All the rest must be paused or stopped. It decides what activities have visibility. It decides thread priority, and gives/takes away certain permissions from apps if they’re stopped/resumed. It also is responsible for giving each process and Out of Memory score which will be used by OOM to decide what to kill later on.
Starting a new Activity
The package manager is asked, please give me all activities that match my name or can perform a certain action. Then a new ActivityRecord inside the Activity Manager service will be created which has information about this activity. The Activity Manager service will talk to the Window Manager service to do things such as get a window, create a transaction, animation, etc. Once the activity record is ready and it’s ready to be shown and created, it’s created and resumed at the same time. Now we figure out if we have a process to attach to or if we need to start a new one. If a new one needs to be created, we call Process.start(). In Process.start(), we create a new fork from Zygote (startViaZygote()). In the Activity Manager service, when the Zygote calls back, we then bind the new application to that process.