How Android apps get handles to system services

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:

  1. The simplest one is StaticServiceFetcher, which is used by system services that do not need a ContextImpl, and once fetched the service will be cached until the process is killed. It is used for services like WifiP2pManager and JobScheduler, etc.

  2. 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.

  3. The most frequently used one is CachedServiceFetcher, which is used when the system service constructor needs a ContextImpl. Also, each ContextImpl 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.


See also

comments powered by Disqus