The Journey of Launching An Android Activity
What happens in Android, stays in Android
You're an Android developer; you know very well how to launch an Android activity and by the time you are reading this you probably did it a zillion times. But have you ever wondered why it's done this way? Why do we use intents to create an activity object? What makes an activity object unique from any other Java object? After all, it’s still a Java object, right? So, where, when, and how does the new Activity()
statement get executed? What happens before an activity gets created? In this article, we’ll take a behind-the-scenes look at what happens when you launch an Android activity. This discussion assumes you’re familiar with Java, object-oriented programming (OOP) principles, and Android development fundamentals.
Let’s start by creating a project with an activity and override the default constructor. The simplest way to check the path to a constructor is by placing a breakpoint on it. It will enable us to walk through all the calls and explore the implemented code to find exactly what we want. Executing the project will result in the following:
In any Unix-based operating system, the first thing the kernel does is to execute init process. Init is the root/parent of all processes executing on Linux.
As we can see at the very bottom of the debugger frames, Android internally calls something called ZygoteInit
What is ZygoteInit
As the name suggests, ZygoteInit
is the genesis of the Android application. It gets its name from a dictionary definition: “The initial cell formed when a new organism is produced."
Android at its core has a process called Zygote
, which starts at init. It’s a special Android OS process that enables shared code across Dalvik/Art VM. This process is a “warmed-up” process, meaning it preloads all the classes and resources that an app may potentially need at runtime into the system’s memory. It then forks itself to start a well-managed process called SystemServer
which initializes all core platform services it houses. One of the core services that gets started by SystemServer
, which is particularly relevant here, is ActivityManagerService
.
ActivityManagerService: Where the Magic Happens
ActivityManagerService is responsible for creating new Activity thread processes, maintaining the Activity lifecycle, and managing the activity stack. During its startup, it performs three main steps:
1. Collect Target Activity Information
Full implementation:
ActivityManagerService.resolveActivityInfo()
The first step is to collect information about the target activity. This is done by calling resolveIntent()
method on some hidden implementation for IPackageManager
interface. The target information is then saved back into the intent object to avoid redoing this step.
2. Check User Privileges
Full implementation:
ActivityManagerService.grantUriPermissionLocked()
The next step is to check if the user has sufficient privileges to invoke the target activity. This is typically done by calling grantUriPermissionLocked()
which ensures:
The target package has a valid UID associated with it.
The UID is used to check if the target package can grant permission to access the URI by calling
checkGrantUriPermissionLocked()
After ensuring that the caller has all the necessary permissions,
grantUriPermissionUncheckedLocked()
grants them to the target package.
3. Activity Object Instantiation
Now to the juicy stuff, it’s time to check if the ProcessRecord already exists for the process. If the ProcessRecord is null, the ActivityManagerService must create a new process to instantiate the activity—duh. The instantiation is done with the help of ActivityThread
class.
At this point, all required permissions have been granted, all the information about the target activity has been stored in the intent object, and the system has made sure that the user has all the privileges to invoke the target activity. Now it’s time to instantiate and launch the target Activity. This phase can be divided into three sub-phases:
3.1. Get the Environment Ready
First things first, let’s get the environment ready. It does so by calling handleLaunchActivity()
. This ensures that configurations are up to date, the graphics environment is initialized, and the WindowManager
is set up.
3.2. Retrieve the Previously Collected Information
Full implementation:
ActivityThread: performLaunchActivity()
Next, previously collected information, such as ComponentName is retrieved, and the baseContext is created to prepare for instantiation.
3.3. The Instantiation
Full implementation:
AppComponentFactory.instantiateActivity()
This is where it gets interesting. Android uses the help of Java's reflection library to execute the new Activity()
statement. It does so by calling newActivity()
method on the Instrumentation
object which then delegates the action to theAppComponentFactory
object by calling instantiateActivity()
method.
After instantiation,ActivityThread
proceeds by calling callActivityOnCreate()
. And this is the start of what we all know as Activity Life Cycle 😄
TL;DR
This article delves into the behind-the-scenes process of launching Android activities, exploring why intents are used and what makes an Activity object unique. It covers the role of the Zygote process, the initialization of core services by SystemServer, and the crucial functions of ActivityManagerService. The article explains how the system collects target activity information, checks user privileges, and instantiates the Activity object using Java reflection. By the end, you'll understand the detailed steps involved in the activity lifecycle from creation to launch.