Mastering Android IPC: The Hidden Design Patterns Behind the Binder Mechanism.

Mastering Android IPC: The Hidden Design Patterns Behind the Binder Mechanism.

We have reached the third stage of our journey into the deep, hidden, and secret world of Android. In this stage, we continue our exploration of Android's primary IPC mechanism, the Binder.

In the previous article, we discussed how the IBinder interface acts as a channel for remote procedure calls (RPC) between different processes in Android, enabling apps to communicate with each other and with system services (1.).

In this article, we will look under the hood to see how the IBinder mechanism extensively uses design patterns such as Proxy, Mediator, and Bridge.

We will delve into how these patterns are implemented within the IBinder system, focusing on ActivityManager and ActivityManagerService as key examples.

But before we open the hood of IBinder to explore the patterns, we need to revisit the Android Architecture to understand where ActivityManager and ActivityManagerService are located.


Android architecture (I've highlighted


As we can see from the image above the architecture is divided into several layers, each with a specific role and responsibilities.

When we will discuss Android System Services, we will analyze all the layers in detail (we've already covered the Linux Kernel level and the Android Runtime level in the first article of this series (7.)). For now, our focus is solely on examining the two adjacent layers: Android Framework and Android System Services, and only in relation to the aspects that are relevant to our purposes.

The Android Framework is the layer that provides the APIs for app developers to interact with the underlying hardware and system services. It is a set of Java-based libraries that developers use to build their applications. Activity Manager is a key component of the Android Framework, it manages the lifecycle of activities and the back stack.

Below the Android Framework there is Android System Services layer. This layer is primarily composed of native services that run within their own processes or within the system_server process. These services are crucial for the functioning of the Android OS.

The system_server process hosts many critical system services written in Java and C++. It is one of the first processes started by the init process during system boot. Services hosted by system_server include:

  • Activity Manager Service (AMS): Manages activity lifecycle, task stack, and applications.
  • Window Manager Service (WMS): Manages windows and their hierarchy.
  • Package Manager Service (PMS): Handles application package management.
  • Power Manager Service: Manages power states and wake locks.
  • Location Manager Service: Provides location data to applications.
  • and many more ...

As we continue this journey into the hidden world of Android, we will dedicate an entire article to AMS, as well as to WMS and PMS, given their importance in the Android system.

But how does the interaction between Android Framework and Android System Services happen?

The Android Framework uses Binder IPC (Inter-Process Communication) to interact with the underlying system services. Here's how this interaction typically works:

  1. Application Requests: When an application makes a request (e.g., starting an activity, accessing location data), it uses the APIs provided by the Android Framework.
  2. Framework Layer: The request is handled by the framework's managers (e.g., Activity Manager, Location Manager, etc.), which package the request into a Parcel object.
  3. Binder IPC: The Parcel object is sent through Binder IPC to the appropriate system service hosted in "system_server" or another native service.
  4. System Service Handling: The system service processes the request, interacts with the necessary hardware or lower-level components, and sends a response back to the framework.
  5. Framework Response: The framework processes the response and delivers the result back to the application.

The separation between the Android Framework and the Android System Services ensures a modular and efficient design. The framework provides a high-level API for application developers, while the system services handle the low-level, system-critical operations. This division of responsibilities allows for a clean separation of concerns, facilitating better maintainability and scalability of the Android operating system.


And now, after this brief analysis of the Framework and System Services layers and their interaction, let’s delve deeper into the two components we will use to uncover the hidden patterns in Android’s Binder mechanism: ActivityManager (Framework) and ActivityManagerService (System Services). They are related but serve distinct roles within the Android architecture.


ActivityManager (Framework Level).

The ActivityManager class is part of the Android Framework and provides a high-level API for interacting with the activities, processes, and the overall app lifecycle. This class is what application developers interact with when they want to perform operations related to activity management. Its responsibilities are:

  • API for Applications: ActivityManager exposes methods that allow applications to interact with the activity stack, retrieve information about running tasks, services, and other app components.
  • Client-Side Proxy (we smell patterns !! ??): It acts as a client-side proxy that communicates with the ActivityManagerService using Binder IPC. When an app calls a method on ActivityManager, it sends a request to ActivityManagerService to perform the actual operation.

Here is an example of usage in an application:

ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);

int maxNum = 10; // The maximum number of entries to return in the list

List<ActivityManager.RunningTaskInfo> tasks = activityManager.getRunningTasks(maxNum);
        


ActivityManagerService (System Services Level).

The ActivityManagerService (AMS) resides within the system_server process and is a core part of the Android System Services layer. It is responsible for managing activities, tasks, and processes at a system level. Its responsibilities are:

  • Lifecycle Management: Manages the lifecycle of all activities and applications, including starting, stopping, resuming, and pausing activities.
  • Process Management: Manages processes, including starting and killing application processes based on system memory and other resources.
  • Task and Back Stack Management: Maintains the back stack of activities, handling navigation between activities.
  • System-Level Operations: Performs system-level operations that are not exposed directly to applications, such as memory management and system resource allocation.


Interaction between ActivityManager and ActivityManagerService.

When an application interacts with the ActivityManager (Framework Level), the following steps occur:

  1. Application Call: The application makes a call to a method in the ActivityManager.
  2. Framework Handling: The ActivityManager creates a request and sends it to the ActivityManagerService using Binder IPC.
  3. Binder IPC: The request is sent through the Binder driver to the ActivityManagerService running in the system_server process.
  4. Service Processing: The ActivityManagerService processes the request. It performs the necessary operations, such as querying the running tasks.
  5. Response: The ActivityManagerService sends the result back through Binder IPC to the ActivityManager.
  6. Returning the Result: The ActivityManager receives the result and returns it to the application.

Here is the sequence diagram of the interaction described above.


sequence diagram of the interaction between ActivityManager and ActivityManagerService.


In summary, ActivityManager and ActivityManagerService have a client-server relationship: this separation allows for a clean and modular design where the framework provides a simple interface for developers, while the underlying services handle the complex and resource-intensive operations required by the operating system.


Well, readers, now we have all the knowledge we need to understand the rest of the article. We can finally open the hood on IBinder to uncover the hidden patterns ????.

We have already seen how the IBinder mechanism in Android is a sophisticated framework that enables seamless interaction between applications and system services, hiding the complexities of IPC and allowing developers to interact with remote services as if they were local.

The design patterns Proxy, Mediator, and Bridge are fundamental to its architecture, each playing a crucial role in ensuring flexibility, maintainability, and scalability.

By examining the ActivityManager and ActivityManagerService, we can see how these patterns are utilized to create a robust and efficient communication system in Android.


Analyzing the mechanisms of Android Binder the first pattern that is found is the Proxy Pattern.

The Proxy Pattern provides a surrogate or placeholder for another object to control access to it. In the series of articles I wrote about patterns I dedicated three articles to the Proxy Pattern where you can find many examples of Proxy application taken from the Android context (2.), (3.), (4.).

Below is the diagram of the Proxy Pattern structure as reported in Design Patterns of GoF (6.), if you are interested you can find a detailed description of this pattern structure in (2.) e (6.).


Proxy Pattern structure.


When an Android app interacts with the ActivityManager system service, it doesn't directly communicate with the service's implementation. Instead, it communicates with a proxy object that implements the same interface as the ActivityManagerService. This proxy forwards method calls to the actual service implementation across process boundaries via IPC.

Here is an example of how the Proxy Pattern is applied in IBinder:

Consider the ActivityManager class. When an app requests the ActivityManager service, the Android framework provides it with a proxy object that implements the IActivityManager interface, as you can see in the code snippet below. All the source code I will show is taken from the Android Open Source Project, version 12.1.0_r11.

// ActivityManager.java (simplified)
public class ActivityManager {
    private final IActivityManager mService;

    public ActivityManager(IActivityManager service) {
        mService = service;
    }

    public List<ActivityManager.RunningTaskInfo> getRunningTasks(int maxNum) {
        try {
            return mService.getTasks(maxNum, 0);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}        

In this example, IActivityManager is the interface that defines the methods of the ActivityManagerService. The actual implementation resides in a remote process, but the ActivityManager class holds a reference to the proxy object (mService), which implements IActivityManager.

The client code interacts with ActivityManager as if it were invoking methods directly on ActivityManagerService, but in reality, the proxy forwards the calls via IPC.


The second pattern encountered among Android Binder mechanisms is the Mediator.

The Mediator Pattern centralizes complex communication and control between related objects. In the IBinder mechanism, the Binder class acts as a mediator, managing communication between different components (6.).

Below is the diagram of the Mediator Pattern structure as reported in Design Patterns of GoF (6.), if you are interested you can find a detailed description of this pattern structure in (2.).


Mediator Pattern structure.


The Binder class serves as the central hub through which all IPC requests pass. It coordinates communication between clients (apps) and service providers (system services). When a client makes a request, the Binder manages the routing of this request to the appropriate service.

Here is an example of how the Mediator Pattern is applied in IBinder:

ActivityManagerService is a core system service that manages activities and their lifecycles. It extends the Binder class, which acts as a mediator, handling requests from clients and passing them to the appropriate methods within ActivityManagerService, as you can see in the code snippet below.

// ActivityManagerService.java (simplified)
public class ActivityManagerService extends IActivityManager.Stub {

    @Override
    public List<ActivityManager.RunningTaskInfo> getTasks(int maxNum, int flags) {
        // Actual implementation of getting running tasks
        return getRunningTasks(maxNum);
    }

    private List<ActivityManager.RunningTaskInfo> getRunningTasks(int maxNum) {
        // Implementation detail
    }
}        

In this example, ActivityManagerService extends IActivityManager.Stub, which is a subclass of Binder. Therefore Binder acts as a Mediator that facilitates communication between the ActivityManager proxy and the ActivityManagerService implementation.


And finally, the last pattern that we find in the mechanisms of Android Binder is the Bridge.

The Bridge Pattern decouples an abstraction from its implementation, allowing them to vary independently. In the series of articles I wrote about patterns I dedicated one article to the Bridge Pattern (5.).

Below is the diagram of the Bridge Pattern structure as reported in Design Patterns of GoF (6.), if you are interested you can find a detailed description of this pattern structure in (5.) e (6.).


Bridge Pattern structure.


In the context of IBinder, this pattern is evident in how Android separates the interface of system services from their implementation using AIDL (Android Interface Definition Language).

AIDL is used to define the interface that both the client and the service implement. The actual implementation of the service is separated and can evolve independently from the interface.

Here is an example of how the Bridge Pattern is applied in IBinder:

The IActivityManager interface is defined using AIDL, and is the bridge between the ActivityManager client and the ActivityManagerService implementation.

// IActivityManager.aidl
interface IActivityManager {
    List<ActivityManager.RunningTaskInfo> getTasks(int maxNum, int flags);
}        

The implementation of IActivityManager is within the ActivityManagerService:

// ActivityManagerService.java (simplified)
public class ActivityManagerService extends IActivityManager.Stub {

    @Override
    public List<ActivityManager.RunningTaskInfo> getTasks(int maxNum, int flags) {
        return getRunningTasks(maxNum);
    }

    private List<ActivityManager.RunningTaskInfo> getRunningTasks(int maxNum) {
        // Implementation detail for retrieving running tasks
    }
}        

The client (ActivityManager) interacts with the ActivityManagerService through the IActivityManager interface, enabling the ActivityManagerService to evolve independently from the client's code.


Below, you can see an interesting UML diagram that I’ve taken from Jim Huang's work (https://www.dre.vanderbilt.edu/~schmidt/cs282/PDFs/android-binder-ipc.pdf.).


https://www.dre.vanderbilt.edu/~schmidt/cs282/PDFs/android-binder-ipc.pdf


This diagram attempts to represent the coexistence of the three patterns: Proxy, Mediator, and Bridge. In our case, the caller is the application, and the callee is the System Services. You’ll notice the interface, which in our case is IActivityManager, implemented by both the proxy class (acts as the Proxy Pattern), which in our case is the ActivityManager class, and the stub class (acts as the Mediator pattern), which in our case is the ActivityManagerService class. Additionally, note that the interface is an auto-generated AIDL type (acts as the Bridge Pattern).



After having seen the three patterns separately, let's now look at them together in action in a real case.

In the context of the lifecycle of an Android Activity, let's analyze everything that lies behind the transition of an Activity to the Pause state (Hidden Activity).

In Android, an Activity is a fundamental component that represents a single screen with a user interface. The lifecycle of an Activity is carefully managed by the Android system, with transitions between different states such as Created, Started, Resumed, Paused, and Stopped. One of the key transitions is when an Activity moves to the Paused state.

This process involves communication between the Activity and the system services, specifically the ActivityManagerService, facilitated by the IBinder mechanism. We will explore what happens under the hood during this transition, focusing on the roles played by the Proxy, Mediator, and Bridge design patterns.

When an Activity goes into the Paused state, it typically means that the Activity is partially obscured by another Activity (such as a dialog or a new Activity being launched). The system needs to ensure that the Activity’s state is preserved, and it prepares to stop interacting with the user. This transition involves several steps where the IBinder mechanism and the ActivityManagerService play crucial roles.

Let's start our exploration of transitioning an Activity to the Paused State by detailing all the steps:


1. Activity Requests to Enter the Paused State.

The process begins when an Activity needs to pause. This could happen because another Activity is being launched in front of it. The ActivityManager class (in the application process) is responsible for requesting this transition.

// ExampleActivity.java (simplified)
@Override
protected void onPause() {
    super.onPause();
    // Application-specific logic for pausing the activity
}        

When onPause() is called, the framework handles the transition by communicating with the ActivityManagerService.


2. Proxy Pattern: Communicating with ActivityManagerService.

The ActivityManager class in the application process does not directly control the Activity lifecycle. Instead, it communicates with the ActivityManagerService, which resides in a separate system process. This communication is facilitated by the IActivityManager interface, which is implemented by a proxy object in the ActivityManager.

When the Activity transitions to Paused, the ActivityManager proxy sends a message to the ActivityManagerService to notify it of the state change.

// ActivityManager.java (simplified)
public class ActivityManager {
    private final IActivityManager mService;

    public ActivityManager(IActivityManager service) {
        mService = service;
    }

    void pauseActivity(IBinder token) {
        try {
            mService.activityPaused(token);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}        

Here, mService is the proxy object implementing IActivityManager. The pauseActivity() method invokes activityPaused() on the proxy, which forwards the call via IPC to the actual ActivityManagerService.


3. Mediator Pattern: ActivityManagerService Coordinates the Transition.

Once the ActivityManagerService receives the request to pause the Activity, it acts as the mediator. The ActivityManagerService coordinates this state change, ensuring the Activity’s state is preserved and other system components are informed of the transition.

// ActivityManagerService.java (simplified)
public class ActivityManagerService extends IActivityManager.Stub {

    @Override
    public void activityPaused(IBinder token) {
        // Locate the ActivityRecord associated with the token
        ActivityRecord r = findActivityRecord(token);

        // Transition the Activity to the Paused state
        r.setState(ActivityState.PAUSED);

        // Notify other components as needed
        notifyActivityPaused(r);
    }
}        

In the activityPaused() method, the ActivityManagerService updates the internal state of the Activity (represented by ActivityRecord) to PAUSED. This change is coordinated centrally by ActivityManagerService, which acts as the mediator to manage the lifecycle transition and maintain system consistency.


4. Bridge Pattern: Decoupling Interface and Implementation.

Throughout this process, the Bridge pattern is in play via the AIDL-defined IActivityManager interface. This interface decouples the ActivityManager (client-side) from the ActivityManagerService (server-side) implementation.

The IActivityManager interface serves as the contract between the client (the app) and the system service. It ensures that the ActivityManagerService can evolve or be replaced without affecting the client code, as long as the interface remains consistent.

// IActivityManager.aidl
interface IActivityManager {
    void activityPaused(in IBinder token);
}        

The actual implementation of activityPaused() resides in the ActivityManagerService, but the client (ActivityManager) interacts with it through the AIDL-generated proxy, maintaining a clean separation of concerns.


To summarize, here’s a sequence of events that occurs when an Activity transitions to the Paused state:

  1. Activity Requests Pause: The onPause() method in the Activity triggers the need to pause the Activity.
  2. Proxy Sends Request: The ActivityManager (client-side) uses the IActivityManager proxy to send a pause request to the ActivityManagerService.
  3. Mediator Coordinates Transition: The ActivityManagerService (server-side) receives the request and coordinates the state transition to Paused, ensuring that the system is aware of the change.
  4. Bridge Maintains Decoupling: The AIDL-generated IActivityManager interface acts as a bridge, decoupling the client from the implementation details of the ActivityManagerService.

Here is the sequence diagram of the interaction described above.


Activity Pause Transition using IBinder and ActivityManagerService.


In summary, the example just seen tells us that the transition of an Android Activity to the Paused state involves a sophisticated interaction between the application and the system services, with the IBinder mechanism playing a pivotal role. The Proxy, Mediator, and Bridge design patterns work together to facilitate seamless communication and lifecycle management:

  • Proxy Pattern: Enables client-side components to communicate with remote services as if they were local.
  • Mediator Pattern: Coordinates the state transitions and ensures system-wide consistency through ActivityManagerService.
  • Bridge Pattern: Decouples the interface (IActivityManager) from its implementation, allowing for flexible and maintainable code.

The careful orchestration of these design patterns ensures that even complex lifecycle events, like pausing an Activity, are handled efficiently and transparently.


We have reached the end of the third episode of our journey into the Android operating system.

In the next episode we will see the Android Startup Process in detail.

I remind you my newsletter "Sw Design & Clean Architecture"? : https://lnkd.in/eUzYBuEX where you can find my previous articles and where you can register, if you have not already done, so you will be notified when I publish new articles.

Thanks for reading my article, and I hope you have found the topic useful,

Feel free to leave any feedback.

Your feedback is very appreciated.

Thanks again.

Stefano


References:

1. S.Santilli: "https://www.dhirubhai.net/pulse/from-android-basics-binder-mastery-journey-through-ipc-santilli-ypm1f/"

2. S.Santilli: "https://www.dhirubhai.net/pulse/proxy-design-pattern-right-those-people-who-act-behalf-santilli/"

3. S.Santilli: "https://www.dhirubhai.net/pulse/remote-dictionary-interesting-application-proxy-pattern-santilli/"

4. S.Santilli: "https://www.dhirubhai.net/pulse/shared-memory-fastest-way-share-data-between-android-stefano-santilli/"

5. S.Santilli: "https://www.dhirubhai.net/pulse/bridge-pattern-how-transform-classes-multiple-aspects-santilli/"

6. Gamma, Helm, Johnson, Vlissides, “Design Patterns”, Addison Wesley (2° Edition October 2002).

7. S.Santilli: "https://www.dhirubhai.net/pulse/android-unique-linux-distribution-stefano-santilli-dqipf/"

要查看或添加评论,请登录

Stefano Santilli的更多文章

社区洞察

其他会员也浏览了