Managers and services: the pattern
Every framework feature is built the same way: a cheap client object you hold, a Binder proxy you never see, and a service in system_server that does the work. Learn the pattern once and a hundred APIs become predictable.
getSystemService(Context.CAMERA_SERVICE) returns a CameraManager. That object is not the camera
service. It is not even a thin wrapper around a socket. It is a client-side object holding a Binder
proxy, and every method you call on it is a remote call to a different process.
TL;DR#
- The pattern is:
Contextconstant →XxxManager(client) → Binder proxy →XxxService(server, usually insystem_server). SystemServiceRegistrymaps constants to manager objects. It is a static block in the framework, not a lookup on the server.- The manager object is cheap to hold and is cached per
Context. You rarely need to cache it yourself. - Binder calls are synchronous by default and run on the calling thread. A slow service call is a blocked UI thread.
- Not every manager forwards to
system_server:AudioManagerreaches native code,NfcManagerreaches an app process, and Mainline modules implement their own services.
Mental model#
Think of a company's switchboard numbers.
You keep a short list of extensions in your phone: 2100 for facilities, 2200 for IT, 2300 for accounts. Dialling 2100 does not put facilities in your pocket. It connects you to a desk in another building, and the conversation happens there.
The extension is the Context constant. The desk is the service. The phone is the manager object.
And like a phone, the manager can be held while the desk is closed — dialling then just fails.
Where the analogy breaks#
A phone call is asynchronous: you leave a message. A Binder call is synchronous by default: you are on hold until the other side answers, and if the desk is slow, so are you. That difference is the single most important operational fact about the pattern, and it is why "don't call binder services on the main thread" is not folklore.
The big picture#
Text description of this diagram
A treemap of system_server's services grouped by area: activity and windows, packages and users, power and time, media and audio, connectivity, security and identity, and system and diagnostics. Each leaf is a registered Binder service. The point of the diagram in this context is that the manager you call corresponds to exactly one of these leaves, and the group tells you which part of the platform you are depending on.
Deep dive#
The four parts#
1. The constant. Context.ACTIVITY_SERVICE is a String, "activity". That is not a
coincidence: the constant is the Binder name. Context.WINDOW_SERVICE is "window", and the names are public API, so they are
effectively frozen.1
2. The manager. android.app.ActivityManager is a client class. It holds an
IActivityManager proxy and implements convenience methods on top of it. The class is in
framework.jar, which is why every app has it without shipping it.
3. The registry. SystemServiceRegistry is a static initialiser with one
registerService(...) call per constant. Each call supplies a factory that builds the manager, and
some also supply a "cached" variant that reuses the instance per process. That is why
getSystemService normally returns the same object each time.
4. The service. ActivityManagerService is a Binder subclass published as "activity" in
servicemanager. It implements IActivityManager.Stub.
- SystemServiceRegistry.javaconstant → manager factory, the client half of every service
- ContextImpl.javagetSystemService's implementation, including the cache
- SystemServiceManager.javathe server-side lifecycle every system service follows
What the call looks like on the wire#
// Client side, roughly what the generated proxy does.
public int getRecentTasks(int maxNum, int flags) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(DESCRIPTOR);
data.writeInt(maxNum);
data.writeInt(flags);
mRemote.transact(TRANSACTION_getRecentTasks, data, reply, 0);
reply.readException();
return reply.readInt();
} finally {
reply.recycle();
data.recycle();
}
}Three things are worth noticing. The arguments are flattened into a ;
there is no object graph crossing the boundary, only bytes. The call is blocking on transact unless
the oneway flag is set. And readException() exists because an exception on the server has to be
re-thrown on the client — Binder does not propagate Java exceptions natively, it marshals them.
Why the client is cheap#
A manager object holds a proxy, and a proxy holds a Binder handle — an integer. That is the entire client-side state. There is no connection to establish, no handshake, no buffer to allocate. The kernel looks up the handle in the caller's Binder context and routes the transaction to the owning process.
This is why the pattern scales to a hundred services.2 If each manager needed a socket and a connection thread, an app that touches ten services would pay ten times. Instead it pays a handle each.
The ones that do not fit#
The pattern is not universal, and the exceptions are instructive:
| Manager | Actually reaches | Why it is different |
|---|---|---|
ActivityManager | system_server, binder activity | The canonical case |
PackageManager | system_server, binder package | Canonical, but with a large local cache |
AudioManager | Native AudioFlinger through AudioSystem | Mixing must not round-trip through a Java service |
WifiManager | Mainline Wi-Fi module | The service ships as a module, not in the platform |
NfcManager | An app process (com.android.nfc) | NFC is implemented as an app with a privileged permission |
SensorManager | system_server facade over a native poller | The service is thin; the data path is native |
That last row explains a common confusion: SensorManager is in system_server, but the sensor
samples do not travel over Binder. The service configures a native poller and, on modern Android,
the data goes through shared memory (ASensorEventQueue with a BitTube), not one transaction per
sample.
getSystemService returning null#
It can return null, and the reasons are worth knowing because they are all different:
- The name is wrong. A typo in a string constant returns null silently.
getSystemServicetakes aString, and there is no compile-time check that the string exists. - The service is not available on this device. Some services are optional or gated by hardware.
A device without NFC has an
NfcManagerclass but nothing to connect to. - The process is too early. A
ContentProvider.onCreateorApplication.attachBaseContextmay run before the registry is fully populated for that context. - The service is restricted. Some services are unavailable to apps without a permission and return null instead of throwing, which is a deliberate choice to avoid making the restriction discoverable.
The catalogue#
Below is a cut of this site's service dataset: thirty of the most-used services, with the constant you pass, the manager you get back, the Binder name the service is published under, and the API level the manager class appeared. Filter it to find the one you are looking for.
| Context constant | Manager class | Binder name | Host process | Since |
|---|---|---|---|---|
| ACTIVITY_SERVICE | android.app.ActivityManager | activity | system_server | 1 |
| ACTIVITY_TASK_SERVICE | android.app.ActivityTaskManager | activity_task | system_server | 29 |
| WINDOW_SERVICE | android.view.WindowManager | window | system_server | 1 |
| PACKAGE_SERVICE | android.content.pm.PackageManager | package | system_server | 1 |
| PERMISSION_SERVICE | android.permission.PermissionManager | permission | system_server | 30 |
| APP_OPS_SERVICE | android.app.AppOpsManager | appops | system_server | 19 |
| USER_SERVICE | android.os.UserManager | user | system_server | 17 |
| POWER_SERVICE | android.os.PowerManager | power | system_server | 1 |
| ALARM_SERVICE | android.app.AlarmManager | alarm | system_server | 1 |
| JOBSCHEDULER_SERVICE | android.app.job.JobScheduler | jobscheduler | system_server | 21 |
| NOTIFICATION_SERVICE | android.app.NotificationManager | notification | system_server | 1 |
| INPUT_SERVICE | android.hardware.input.InputManager | input | system_server | 16 |
| DISPLAY_SERVICE | android.hardware.display.DisplayManager | display | system_server | 17 |
| INPUT_METHOD_SERVICE | android.view.inputmethod.InputMethodManager | input_method | system_server | 3 |
| ACCESSIBILITY_SERVICE | android.view.accessibility.AccessibilityManager | accessibility | system_server | 4 |
| CAMERA_SERVICE | android.hardware.camera2.CameraManager | media.camera | system_server | 21 |
| AUDIO_SERVICE | android.media.AudioManager | audio | native | 1 |
| MEDIA_SESSION_SERVICE | android.media.session.MediaSessionManager | media_session | system_server | 21 |
| MEDIA_PROJECTION_SERVICE | android.media.projection.MediaProjectionManager | media_projection | system_server | 21 |
| CONNECTIVITY_SERVICE | android.net.ConnectivityManager | connectivity | system_server | 1 |
| WIFI_SERVICE | android.net.wifi.WifiManager | wifi | mainline | 1 |
| TELEPHONY_SERVICE | android.telephony.TelephonyManager | phone | system_server | 1 |
| VPN_MANAGEMENT_SERVICE | android.net.VpnManager | vpn_management | system_server | 30 |
| LOCATION_SERVICE | android.location.LocationManager | location | system_server | 1 |
| SENSOR_SERVICE | android.hardware.SensorManager | sensorservice | system_server | 1 |
| KEYSTORE_SERVICE | android.security.keystore.KeyStoreManager | android.security.keystore2 | system_server | 1 |
| BIOMETRIC_SERVICE | android.hardware.biometrics.BiometricManager | biometric | system_server | 29 |
| STORAGE_SERVICE | android.os.storage.StorageManager | mount | system_server | 1 |
| CLIPBOARD_SERVICE | android.content.ClipboardManager | clipboard | system_server | 11 |
| CREDENTIAL_SERVICE | android.credentials.CredentialManager | credential | system_server | 34 |
Two columns repay study. The binder name column shows how often the name differs from the
constant: STORAGE_SERVICE maps to "mount", TELEPHONY_SERVICE maps to "phone",
SENSOR_SERVICE maps to "sensorservice". Those names predate the public constants and cannot be
changed without breaking every native client.
The host process column shows that the pattern is about the client API, not about
system_server specifically. A manager can front a native daemon or a module.
Writing your own#
The pattern is not reserved for the platform. A bound Service with an AIDL interface is the same
shape at a smaller scale: the client holds a proxy, the service implements a Stub, and the
arguments are parcelled. What you do not get is the SystemServiceRegistry shortcut, because that
is registered by the framework, not by apps.
How it evolved#
| API | Version | What changed |
|---|---|---|
| 1 | 1.0 |
|
| 4 | 1.6 Donut |
|
| 5 | 2.0 Eclair |
|
| 8 | 2.2 Froyo |
|
| 9 | 2.3 Gingerbread |
|
| 11 | 3.0 Honeycomb |
|
| 13 | 3.2 Honeycomb |
|
| 14 | 4.0 Ice Cream Sandwich |
|
| 16 | 4.1 Jelly Bean |
|
| 17 | 4.2 Jelly Bean |
|
| 18 | 4.3 Jelly Bean |
|
| 19 | 4.4 KitKat |
|
| 20 | 4.4W KitKat (Wear) |
|
| 21 | 5.0 Lollipop |
|
| 22 | 5.1 Lollipop |
|
| 23 | 6.0 Marshmallow |
|
| 24 | 7.0 Nougat |
|
| 26 | 8.0 Oreo |
|
| 27 | 8.1 Oreo |
|
| 28 | 9 Pie |
|
| 29 | 10 |
|
| 30 | 11 |
|
| 31 | 12 |
|
| 32 | 12L |
|
| 33 | 13 Tiramisu |
|
| 34 | 14 Upside Down Cake |
|
| 35 | 15 Vanilla Ice Cream |
|
| 36 | 16 Baklava |
|
| 37 | 17 Cinnamon Bun |
|
The pattern itself has not changed since 2008. What changes is where the server lives. Services
have been split (ActivityTaskManager, PermissionManager), moved into Mainline modules (Wi-Fi,
Bluetooth, MediaProvider), and occasionally moved out of system_server entirely (NFC). The client
API has stayed stable through all of it, which is the entire point of the boundary.
Under the hood#
- SystemServiceRegistry.javathe static map from Context constants to manager factories
- ContextImpl.javagetSystemService and the per-context cache
- Context.javathe constants themselves, which are the Binder names
- ActivityManager.javaa hand-written manager class on top of a generated AIDL proxy
Curiosity corner#
- The Binder name is not always the constant lowercased.
STORAGE_SERVICEis"mount"because the service predates the constant and the name was already used by native clients. getSystemServicecan return an object whose service is not running. The manager exists regardless; the failure happens on the first call, not on the lookup.Context.getSystemServiceis cached per context, not per app. Two contexts in the same process may return two manager objects wrapping the same proxy.
Try it#
adb shell service list | head -20
adb shell service list | grep -E "activity|package|window"
Example output
Found 218 services:
0 android.app.IActivityManager: []
1 android.view.IWindowManager: []
2 android.content.pm.IPackageManager: []service list prints the Binder name and the interface descriptor. It is the ground truth for the
pattern: if a name is not in that list, no amount of getSystemService will reach it.
Check yourself#
Check yourself
How does system_server's process come into existence at boot?
Sources#
Sources
Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.
- docsContext | API referencedeveloper.android.comaccessed 2026-10-27↩
- docsBinder overviewsource.android.comaccessed 2026-10-27↩
- docsAndroid platform architecture (AOSP)source.android.comaccessed 2026-10-27↩
- docsAIDL for HALssource.android.comaccessed 2026-10-27↩
- docsApplication sandboxsource.android.comaccessed 2026-10-27↩