We all have used Context.getSystemService() to get a handle to a system service. But have you ever wondered how it works behind the scene? In this article, we’ll dig into the code and try to find out.
System Services
System services play an important role in the Android platform, providing low-level functions of the hardware and the kernel to higher-level applications through the framework APIs.
For example, LocationManager provides access to the system’s location service to the application. However, as part of the running application, LocationManager
itself can’t do much. Therefore, it acts as a proxy, forwarding the application’s request to the corresponding system service, LocationManagerService, and passing the responses back.
Context.getSystemService()
So let’s start with Context.getSystemService()
. When calling from an Activity
, the code looks like below:
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
if (getBaseContext() == null) {
throw new IllegalStateException(
"System services not available to Activities before onCreate()");
}
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}
It tries to return a local reference of WindowManager
or SearchManager
, then asks its base class, ContextThemeWrapper
, to handle it. There, it tries to return a local reference of LayoutInflater
, then asks the base context to handle it.
Usually, the actual base context is ContextImpl (same for Application
and Service
), which gets system service from SystemServiceRegistry
:
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
SystemServiceRegistry
SystemServiceRegistry is a registry for all system services that can be returned by Context.getSystemService()
. Internally, it uses two maps to hold the information:
private static final Map<Class<?>, String> SYSTEM_SERVICE_NAMES =
new ArrayMap<Class<?>, String>();
private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new ArrayMap<String, ServiceFetcher<?>>();
The SYSTEM_SERVICE_NAMES
maintains a map from service class to service names, used by Context.getServiceName() and Context.getSystemService() by class. The latter essentially first gets service name by calling Context.getServiceName()
, then returns the service by calling Contetxt.getSystemService()
by name.
The SYSTEM_SERVICE_FETCHERS
maintains a map from service name to a ServiceFetcher
, which is used to get the actually system service. Here, the service name is the one we pass to Context.getSystemService()
, such as Context.LOCATION_SERVICE.
The two maps are initialized when the class is first loaded, and never changed after that. The static initialization looks like below:
final class SystemServiceRegistry {
static {
registerService(Context.LOCATION_SERVICE, LocationManager.class,
new CachedServiceFetcher<LocationManager>() {
@Override
public LocationManager createService(ContextImpl ctx) throws ServiceNotFoundException {
IBinder b = ServiceManager.getServiceOrThrow(Context.LOCATION_SERVICE);
return new LocationManager(ctx, ILocationManager.Stub.asInterface(b));
}});
...
}
/**
* Statically registers a system service with the context.
* This method must be called during static initialization only.
*/
private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
...
}
The registry’s getSystemService()
method simply finds the ServiceFetcher
from the map, and uses it to get the system service:
final class SystemServiceRegistry {
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
...
}
ServiceFetcher
ServiceFetcher
is a simple base interface for fetching system services:
static abstract interface ServiceFetcher<T> {
T getService(ContextImpl ctx);
}
It has three implementations for different use cases:
The simplest one is
StaticServiceFetcher
, which is used by system services that do not need aContextImpl
, and once fetched the service will be cached until the process is killed. It is used for services like WifiP2pManager and JobScheduler, etc.The next one is
StaticApplicationContextServiceFetcher
, which also only creates one service instance per process, and caches the service reference until the process is killed. However, it passes the application context of the app to the created service. Currently, it’s only used by ConnectivityManager.The most frequently used one is
CachedServiceFetcher
, which is used when the system service constructor needs aContextImpl
. Also, eachContextImpl
will keep references to all system services it fetches.
Service Manager
As seen in the previous section, ServiceFetcher
calls ServiceManager.getService() to get a reference to a system service.
public final class ServiceManager {
/**
* Returns a reference to a service with the given name.
*
* @param name the name of the service to get
* @return a reference to the service, or <code>null</code> if the service doesn't exist
*/
@UnsupportedAppUsage
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return Binder.allowBlocking(rawGetService(name));
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
...
}
Here, sCache
is initialized when the process is launched and bound by the activity manager. It only caches “well known” services, such as window service, alarm service, etc. A full list of cached services can be found here.
For services not cached, it calls getIServiceManager().getService(name)
to get it. Here, the IServiceManager talks to the system’s Service Manager service over IPC to get a handle to the system service.
The Service Manager service is a central registry for all system services. It also provides an addService()
for system services to register themselves. Usually, system services are started and registered by SystemServer.
Conclusion
Hopefully, this article can give you some idea on how Android apps get handles to system services. We probably don’t need to know the details for most of the time, but it’s always useful to know how things work, especially when debugging some interesting bugs.