Communication Inter-Processus sur Android : Architecture Binder et Guide AIDL

Isolation des processus et nécessité de l'IPC

Dans un système d'exploitation moderne, chaque processus dispose d'un espace mémoire virtuel dédié. Cette isolation empêche tout accès direct aux données ou au code d'un autre processus, garantissant ainsi la stabilité et la sécurité du système. Bien que cette séparation soit cruciale, les applications modernes nécessitent fréquemment d'échanger des informations, d'orchestrer des tâches ou de partager des ressources. C'est ici qu'intervient la Communication Inter-Processus (IPC), un ensemble de mécanismes permettant à des entités isolées d'interagir de manière contrôlée.

Hérédité Linux et adaptations du framework Android

Le noyau Linux propose traditionnellement six canaux de communication : les tubes (pipes), les signaux, les sémaphores, les files de messages, la mémoire partagée et les sockets réseau. Bien que fonctionnels, ces mécanismes impliquent souvent deux copies de données (utilisateur vers noyau, puis noyau vers utilisateur) et une gestion complexe des autorisations. Android, tout en s'appuyant sur ce noyau, a introduit des abstractions spécifiques optimisées pour l'écosystème mobile et la sécurité applicative.

Composants Android dédiés aux échanges

Le framework Android expose quatre paradigmes principaux pour l'IPC :

  • Activités (Intent implicites) : Convient aux transitions d'interface utilisateur entre applications. Le lancement via un action standard permet de déléguer des tâches (lecture web, composition SMS, appels) sans connaître l'implémentation cible.
  • ContentProvider : Spécialement conçu pour le partage structuré de données. Il expose des bases de données ou des fichiers via des URI, avec un système de permissions granulaires. L'accès aux données sous-jacentes doit s'effectuer hors du thread principal pour éviter les blocages UI.
  • BroadcastReceiver : Mécanisme de diffusion unidirectionnelle. Déconseillé pour les interactions synchrones ou critiques en raison de sa latence variable, de l'impossibilité de garantir une réponse immédiate, et de la difficulté à identifier l'émetteur ou à gérer les timeouts (ANR).
  • Service : Peut être démarré de manière découplée (startService) pour des tâches de fond, ou lié (bindService) pour un échange bidirectionnel étroit. Le couplage direct offre un contrôle précis sur le cycle de vie et les transferts de données.

Pour les interactions à l'intérieur d'une même application, ces mécanismes IPC introduisent une surcharge inutile. Privilégier les Handler, les interfaces fonctionnelles, LocalBroadcastManager, ou des architectures réactives reste bien plus performant et maintenable.

Anatomie du mécanisme Binder

Binder est le pilier de la communication sur Android. Issu du projet OpenBinder, il remplace les IPC classiques par une architecture Client/Serveur optimisée au niveau du noyau. Contrairement aux sockets qui nécessitent deux copies de données, Binder n'en effectue qu'une seule grâce au mappage mémoire (mmap). Le driver du noyau gère directement le transit des paquets Parcel, ce qui réduit drastiquement la latence. De plus, Binder intègre nativement la vérification des identifiants de processus (UID/PID) et des permissions, offrant un niveau de sécurité inégalé par les mécanismes POSIX standards.

Mise en œuvre concrète avec AIDL

Android Interface Definition Language (AIDL) est un outil de compilation qui génère automatiquement le code Java/Kotlin nécessaire pour manipuler Binder. Il abstrait la sérialisation des données, la gestion des threads et la création du proxy distant. Voici une implémentation fonctionnelle d'un service de gestion de configuration.

1. Définition de l'interface distante

Créez un fichier IConfigurationManager.aidl dans le répertoire aidl du projet serveur :

package fr.dev.ipcdemo.server;

interface IConfigurationManager {
    void synchronizeSettings(int configurationId, boolean isActive);
    boolean isServiceActive(int configurationId);
}

2. Implémentation côté serveur

Le service expose un Binder héritant de la classe Stub générée :

public class ConfigurationService extends Service {
    private boolean activeState = false;
    private static final String TAG = "ConfigService";

    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "Lien Binder établi");
        return new ServiceBinder();
    }

    private final class ServiceBinder extends IConfigurationManager.Stub {
        @Override
        public void synchronizeSettings(int configurationId, boolean isActive) throws RemoteException {
            Log.d(TAG, "Synchronisation reçue pour l'ID: " + configurationId);
            activeState = isActive;
        }

        @Override
        public boolean isServiceActive(int configurationId) throws RemoteException {
            return activeState;
        }
    }
}

Déclarez ce service dans le manifeste avec android:exported="true" et une action explicite pour le routage.

3. Connexion et invocation côté client

Le client récupère le proxy distant via ServiceConnection :

public class RemoteClientActivity extends AppCompatActivity {
    private IConfigurationManager managerProxy;
    private boolean isConnected = false;

    private final ServiceConnection linkHandler = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            managerProxy = IConfigurationManager.Stub.asInterface(service);
            isConnected = true;
            initiateDataSync();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            managerProxy = null;
            isConnected = false;
        }
    };

    public void attachService() {
        Intent serviceIntent = new Intent();
        serviceIntent.setPackage("fr.dev.ipcdemo.server");
        serviceIntent.setAction("fr.dev.ipcdemo.SYNC_ACTION");
        bindService(serviceIntent, linkHandler, Context.BIND_AUTO_CREATE);
    }

    private void initiateDataSync() {
        try {
            managerProxy.synchronizeSettings(42, true);
            boolean status = managerProxy.isServiceActive(42);
            Log.i("Client", "État distant : " + status);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

Gestion des rappels asynchrones (Callbacks)

Dans les scénarios où le serveur doit notifier le client d'un événement sans que celui-ci n'interroge continuellement, AIDL supporte les interfaces de rappel. Le processus nécessite trois étapes :

  1. Déclarer une interface de callback dans un fichier .aidl séparé (ex: ISyncListener.aidl).
  2. Ajouter des méthodes d'enregistrement et de désenregistrement dans l'interface principale.
  3. Utiliser RemoteCallbackList<t> côté serveur pour stocker les listeners de manière thread-safe et gérer automatiquement la déconnexion des clients fantômes.

Extrait d'implémentation côté serveur :

private RemoteCallbackList<ISyncListener> listeners = new RemoteCallbackList<>();

private void notifyClients(String payload) {
    int count = listeners.beginBroadcast();
    for (int i = 0; i < count; i++) {
        try {
            listeners.getBroadcastItem(i).onStateUpdated(payload);
        } catch (RemoteException e) {
            // Le client distant est tombé, le système le nettoiera automatiquement
        }
    }
    listeners.finishBroadcast();
}

@Override
public void registerListener(ISyncListener callback) throws RemoteException {
    if (callback != null) listeners.register(callback);
}

@Override
public void unregisterListener(ISyncListener callback) throws RemoteException {
    if (callback != null) listeners.unregister(callback);
}

Côté client, l'implémentation du listener s'effectue en étendant ISyncListener.Stub. L'appel à registerListener doit précéder toute opération susceptible de déclencher une notification, et la désinscription doit impérativement être déclenchée lors de la destruction du cycle de vie de l'activité ou de la désconnexion du service pour prévenir les fuites de mémoire et les appels sur des références invalides.

Étiquettes: Android IPC Binder AIDL service

Publié le 14 septembre à 09h39