Moteurs AIDL

Un backend AIDL est une cible pour la génération de code stub. Lorsque vous utilisez des fichiers AIDL, vous les utilisez toujours dans un langage particulier avec un runtime spécifique. Selon le contexte, vous devez utiliser différents backends AIDL.

AIDL a les backends suivants :

Back-end Langue Surface de l'API Construire des systèmes
Java Java SDK/SystemApi (stable*) tous
NDK C++ libbinder_ndk (stable*) aidl_interface
RPC C++ libbinder (instable) tous
Rouiller Rouiller libbinder_rs (instable) aidl_interface
  • Ces surfaces d'API sont stables, mais de nombreuses API, telles que celles de gestion des services, sont réservées à une utilisation interne de la plateforme et ne sont pas disponibles pour les applications. Pour plus d'informations sur l'utilisation d'AIDL dans les applications, consultez la documentation du développeur .
  • Le backend Rust a été introduit dans Android 12 ; le backend NDK est disponible depuis Android 10.
  • La caisse Rust est construite sur libbinder_ndk . Les APEX utilisent la caisse de reliure de la même manière que n'importe qui d'autre du côté du système. La partie Rust est regroupée dans un APEX et expédiée à l'intérieur de celui-ci. Cela dépend du libbinder_ndk.so sur la partition système.

Construire des systèmes

Selon le backend, il existe deux manières de compiler AIDL en code stub. Pour plus de détails sur les systèmes de build, consultez la référence du module Soong .

Système de construction de base

Dans n'importe quel module cc_ ou java_ Android.bp (ou dans leurs équivalents Android.mk ), les fichiers .aidl peuvent être spécifiés comme fichiers source. Dans ce cas, les backends Java/CPP de AIDL sont utilisés (pas le backend NDK), et les classes permettant d'utiliser les fichiers AIDL correspondants sont automatiquement ajoutées au module. Des options telles que local_include_dirs , qui indique au système de build le chemin racine des fichiers AIDL dans ce module, peuvent être spécifiées dans ces modules sous un groupe aidl: Notez que le backend Rust est uniquement destiné à être utilisé avec Rust. Les modules rust_ sont gérés différemment dans la mesure où les fichiers AIDL ne sont pas spécifiés comme fichiers source. Au lieu de cela, le module aidl_interface produit une rustlib appelée <aidl_interface name>-rust qui peut être liée. Pour plus de détails, consultez l' exemple Rust AIDL .

aidl_interface

Les types utilisés avec ce système de build doivent être structurés. Pour être structurés, les parcelables doivent contenir directement des champs et non être des déclarations de types définis directement dans les langages cibles. Pour savoir comment l'AIDL structuré s'intègre à l'AIDL stable, voir AIDL structuré versus stable .

Les types

Vous pouvez considérer le compilateur aidl comme une implémentation de référence pour les types. Lorsque vous créez une interface, appelez aidl --lang=<backend> ... pour voir le fichier d'interface résultant. Lorsque vous utilisez le module aidl_interface , vous pouvez afficher la sortie dans out/soong/.intermediates/<path to module>/ .

Type Java/AIDL Type C++ Type NDK Type de rouille
booléen bouffon bouffon bouffon
octet int8_t int8_t i8
carboniser char16_t char16_t u16
int int32_t int32_t i32
long int64_t int64_t i64
flotter flotter flotter f32
double double double f64
Chaîne Android : String16 std :: chaîne Chaîne
android.os.Parcelable android :: Parcelable N / A N / A
Classeur I Android ::IBinder ndk ::SpAIBinder classeur ::SpIBinder
T[] std :: vecteur <T> std :: vecteur <T> Dans : &[T]
Sortie : Vec<T>
octet[] std :: vecteur <uint8_t> std::vecteur<int8_t> 1 Dans : &[u8]
Sortie : Vec<u8>
Liste<T> std::vecteur<T> 2 std::vecteur<T> 3 Dans : &[T] 4
Sortie : Vec<T>
Descripteur de fichier Android :: base :: unique_fd N / A binder::parcel::ParcelFileDescriptor
ParcelFileDescriptor android :: os :: ParcelFileDescriptor ndk :: ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
type d'interface (T) android::sp<T> std::shared_ptr<T> classeur : Fort
type colisable (T) T T T
type d'union (T) 5 T T T
T[N] 6 std::array<T, N> std::array<T, N> [T ; N]

1. Sous Android 12 ou version ultérieure, les tableaux d'octets utilisent uint8_t au lieu de int8_t pour des raisons de compatibilité.

2. Le backend C++ prend en charge List<T>T est l'un des String , IBinder , ParcelFileDescriptor ou parcelable. Sous Android 13 ou version ultérieure, T peut être n’importe quel type non primitif (y compris les types d’interface), à ​​l’exception des tableaux. AOSP vous recommande d'utiliser des types de tableaux comme T[] , car ils fonctionnent dans tous les backends.

3. Le backend NDK prend en charge List<T>T est l'un des String , ParcelFileDescriptor ou parcelable. Sous Android 13 ou version ultérieure, T peut être n’importe quel type non primitif, à l’exception des tableaux.

4. Les types sont transmis différemment pour le code Rust selon qu'ils sont une entrée (un argument) ou une sortie (une valeur renvoyée).

5. Les types d'union sont pris en charge dans Android 12 et versions ultérieures.

6. Sous Android 13 ou version ultérieure, les tableaux de taille fixe sont pris en charge. Les tableaux de taille fixe peuvent avoir plusieurs dimensions (par exemple int[3][4] ). Dans le backend Java, les tableaux de taille fixe sont représentés sous forme de types de tableaux.

Directionnalité (entrée/sortie/entrée)

Lorsque vous spécifiez les types d'arguments des fonctions, vous pouvez les spécifier comme in , out ou inout . Cela contrôle dans quelle direction les informations sont transmises pour un appel IPC. in est la direction par défaut et indique que les données sont transmises de l'appelant à l'appelé. out signifie que les données sont transmises de l'appelé à l'appelant. inout est la combinaison des deux. Cependant, l'équipe Android vous recommande d'éviter d'utiliser le spécificateur d'argument inout . Si vous utilisez inout avec une interface versionnée et un appelé plus ancien, les champs supplémentaires présents uniquement dans l'appelant sont réinitialisés à leurs valeurs par défaut. Par rapport à Rust, un type inout normal reçoit &mut Vec<T> , et un type list inout reçoit &mut Vec<T> .

UTF8/UTF16

Avec le backend CPP, vous pouvez choisir si les chaînes sont utf-8 ou utf-16. Déclarez les chaînes comme @utf8InCpp String dans AIDL pour les convertir automatiquement en utf-8. Les backends NDK et Rust utilisent toujours des chaînes utf-8. Pour plus d'informations sur l'annotation utf8InCpp , consultez Annotations dans AIDL .

Nullabilité

Vous pouvez annoter les types qui peuvent être nuls dans le backend Java avec @nullable pour exposer les valeurs nulles aux backends CPP et NDK. Dans le backend Rust, ces types @nullable sont exposés sous la forme Option<T> . Les serveurs natifs rejettent les valeurs nulles par défaut. Les seules exceptions à cette règle sont les types interface et IBinder , qui peuvent toujours être nuls pour les lectures NDK et les écritures CPP/NDK. Pour plus d'informations sur l'annotation nullable , consultez Annotations dans AIDL .

Colis personnalisés

Un colisable personnalisé est un colisable implémenté manuellement dans un backend cible. Utilisez des colis personnalisés uniquement lorsque vous essayez d'ajouter la prise en charge d'autres langues pour un colis personnalisé existant qui ne peut pas être modifié.

Afin de déclarer un colisable personnalisé afin que AIDL en soit informé, la déclaration colisable AIDL ressemble à ceci :

    package my.pack.age;
    parcelable Foo;

Par défaut, cela déclare un Parcelable Java où my.pack.age.Foo est une classe Java implémentant l'interface Parcelable .

Pour une déclaration d'un backend CPP personnalisé colisable dans AIDL, utilisez cpp_header :

    package my.pack.age;
    parcelable Foo cpp_header "my/pack/age/Foo.h";

L'implémentation C++ dans my/pack/age/Foo.h ressemble à ceci :

    #include <binder/Parcelable.h>

    class MyCustomParcelable : public android::Parcelable {
    public:
        status_t writeToParcel(Parcel* parcel) const override;
        status_t readFromParcel(const Parcel* parcel) override;

        std::string toString() const;
        friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
        friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
    };

Pour une déclaration d'un colisable NDK personnalisé dans AIDL, utilisez ndk_header :

    package my.pack.age;
    parcelable Foo ndk_header "android/pack/age/Foo.h";

L'implémentation du NDK dans android/pack/age/Foo.h ressemble à ceci :

    #include <android/binder_parcel.h>

    class MyCustomParcelable {
    public:

        binder_status_t writeToParcel(AParcel* _Nonnull parcel) const;
        binder_status_t readFromParcel(const AParcel* _Nonnull parcel);

        std::string toString() const;

        friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
        friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
    };

Dans Android 15 (AOSP expérimental), pour déclarer un colisable Rust personnalisé dans AIDL, utilisez rust_type :

package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";

L'implémentation de Rust dans rust_crate/src/lib.rs ressemble à ceci :

use binder::{
    binder_impl::{BorrowedParcel, UnstructuredParcelable},
    impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
    StatusCode,
};

#[derive(Clone, Debug, Eq, PartialEq)]
struct Foo {
    pub bar: String,
}

impl UnstructuredParcelable for Foo {
    fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
        parcel.write(&self.bar)?;
        Ok(())
    }

    fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
        let bar = parcel.read()?;
        Ok(Self { bar })
    }
}

impl_deserialize_for_unstructured_parcelable!(Foo);
impl_serialize_for_unstructured_parcelable!(Foo);

Vous pouvez ensuite utiliser ce colisable comme type dans les fichiers AIDL, mais il ne sera pas généré par AIDL. Fournissez des opérateurs < et == pour les parcelles personnalisées du backend CPP/NDK afin de les utiliser dans union .

Les valeurs par défaut

Les parcelles structurées peuvent déclarer des valeurs par défaut par champ pour les primitives, les String et les tableaux de ces types.

    parcelable Foo {
      int numField = 42;
      String stringField = "string value";
      char charValue = 'a';
      ...
    }

Dans le backend Java, lorsque les valeurs par défaut sont manquantes, les champs sont initialisés avec des valeurs nulles pour les types primitifs et null pour les types non primitifs.

Dans d'autres backends, les champs sont initialisés avec des valeurs initialisées par défaut lorsque les valeurs par défaut ne sont pas définies. Par exemple, dans le backend C++, les champs String sont initialisés comme une chaîne vide et les champs List<T> sont initialisés comme un vector<T> vide. Les champs @nullable sont initialisés en tant que champs de valeur nulle.

La gestion des erreurs

Le système d'exploitation Android fournit des types d'erreurs intégrés que les services peuvent utiliser lors du signalement d'erreurs. Ceux-ci sont utilisés par binder et peuvent être utilisés par n’importe quel service implémentant une interface de binder. Leur utilisation est bien documentée dans la définition AIDL et ils ne nécessitent aucun statut ni type de retour défini par l'utilisateur.

Paramètres de sortie avec des erreurs

Lorsqu'une fonction AIDL signale une erreur, la fonction ne peut pas initialiser ou modifier les paramètres de sortie. Plus précisément, les paramètres de sortie peuvent être modifiés si l'erreur se produit lors du dégroupage plutôt que lors du traitement de la transaction elle-même. En général, lors de l'obtention d'une erreur d'une fonction AIDL, tous les paramètres inout et out ainsi que la valeur de retour (qui agit comme un paramètre out dans certains backends) doivent être considérés comme étant dans un état indéfini.

Quelles valeurs d'erreur utiliser

La plupart des valeurs d'erreur intégrées peuvent être utilisées dans n'importe quelle interface AIDL, mais certaines sont traitées d'une manière spéciale. Par exemple, EX_UNSUPPORTED_OPERATION et EX_ILLEGAL_ARGUMENT peuvent être utilisés lorsqu'ils décrivent la condition d'erreur, mais EX_TRANSACTION_FAILED ne doit pas être utilisé car il est traité de manière spéciale par l'infrastructure sous-jacente. Consultez les définitions spécifiques au backend pour plus d’informations sur ces valeurs intégrées.

Si l'interface AIDL nécessite des valeurs d'erreur supplémentaires qui ne sont pas couvertes par les types d'erreur intégrés, elle peut alors utiliser l'erreur intégrée spéciale spécifique au service qui permet l'inclusion d'une valeur d'erreur spécifique au service définie par l'utilisateur. . Ces erreurs spécifiques au service sont généralement définies dans l'interface AIDL comme une enum const int ou int et ne sont pas analysées par le classeur.

En Java, les erreurs correspondent à des exceptions, telles que android.os.RemoteException . Pour les exceptions spécifiques au service, Java utilise android.os.ServiceSpecificException avec l'erreur définie par l'utilisateur.

Le code natif sous Android n'utilise pas d'exceptions. Le backend CPP utilise android::binder::Status . Le backend NDK utilise ndk::ScopedAStatus . Chaque méthode générée par AIDL en renvoie un, représentant l'état de la méthode. Le backend Rust utilise les mêmes valeurs de code d'exception que le NDK, mais les convertit en erreurs Rust natives ( StatusCode , ExceptionCode ) avant de les transmettre à l'utilisateur. Pour les erreurs spécifiques au service, le Status ou ScopedAStatus renvoyé utilise EX_SERVICE_SPECIFIC avec l'erreur définie par l'utilisateur.

Les types d'erreurs intégrés peuvent être trouvés dans les fichiers suivants :

Back-end Définition
Java android/os/Parcel.java
RPC binder/Status.h
NDK android/binder_status.h
Rouiller android/binder_status.h

Utiliser divers backends

Ces instructions sont spécifiques au code de la plateforme Android. Ces exemples utilisent un type défini, my.package.IFoo . Pour obtenir des instructions sur l’utilisation du backend Rust, consultez l’ exemple Rust AIDL sur la page Android Rust Patterns .

Importation de types

Que le type défini soit une interface, un parcellaire ou une union, vous pouvez l'importer en Java :

import my.package.IFoo;

Ou dans le backend du CPP :

#include <my/package/IFoo.h>

Ou dans le backend NDK (notez l'espace de noms aidl supplémentaire) :

#include <aidl/my/package/IFoo.h>

Ou dans le backend Rust :

use my_package::aidl::my::package::IFoo;

Bien que vous puissiez importer un type imbriqué en Java, dans les backends CPP/NDK, vous devez inclure l'en-tête de son type racine. Par exemple, lors de l'importation d'un type Bar imbriqué défini dans my/package/IFoo.aidl ( IFoo est le type racine du fichier), vous devez inclure <my/package/IFoo.h> pour le backend CPP (ou <aidl/my/package/IFoo.h> pour le backend NDK).

Services de mise en œuvre

Pour implémenter un service, vous devez hériter de la classe stub native. Cette classe lit les commandes du pilote de classeur et exécute les méthodes que vous implémentez. Imaginez que vous ayez un fichier AIDL comme celui-ci :

    package my.package;
    interface IFoo {
        int doFoo();
    }

En Java, vous devez étendre à partir de cette classe :

    import my.package.IFoo;
    public class MyFoo extends IFoo.Stub {
        @Override
        int doFoo() { ... }
    }

Dans le backend du CPP :

    #include <my/package/BnFoo.h>
    class MyFoo : public my::package::BnFoo {
        android::binder::Status doFoo(int32_t* out) override;
    }

Dans le backend NDK (notez l'espace de noms aidl supplémentaire) :

    #include <aidl/my/package/BnFoo.h>
    class MyFoo : public aidl::my::package::BnFoo {
        ndk::ScopedAStatus doFoo(int32_t* out) override;
    }

Dans le backend Rust :

    use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFoo};
    use binder;

    /// This struct is defined to implement IRemoteService AIDL interface.
    pub struct MyFoo;

    impl Interface for MyFoo {}

    impl IFoo for MyFoo {
        fn doFoo(&self) -> binder::Result<()> {
           ...
           Ok(())
        }
    }

Ou avec Rust asynchrone :

    use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFooAsyncServer};
    use binder;

    /// This struct is defined to implement IRemoteService AIDL interface.
    pub struct MyFoo;

    impl Interface for MyFoo {}

    #[async_trait]
    impl IFooAsyncServer for MyFoo {
        async fn doFoo(&self) -> binder::Result<()> {
           ...
           Ok(())
        }
    }

S'inscrire et obtenir des services

Les services de la plateforme Android sont généralement enregistrés auprès du processus servicemanager . En plus des API ci-dessous, certaines API vérifient le service (ce qui signifie qu'elles reviennent immédiatement si le service n'est pas disponible). Vérifiez l’interface servicemanager correspondante pour plus de détails exacts. Ces opérations ne peuvent être effectuées que lors de la compilation sur la plateforme Android.

En Java :

    import android.os.ServiceManager;
    // registering
    ServiceManager.addService("service-name", myService);
    // return if service is started now
    myService = IFoo.Stub.asInterface(ServiceManager.checkService("service-name"));
    // waiting until service comes up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForService("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForDeclaredService("service-name"));

Dans le backend du CPP :

    #include <binder/IServiceManager.h>
    // registering
    defaultServiceManager()->addService(String16("service-name"), myService);
    // return if service is started now
    status_t err = checkService<IFoo>(String16("service-name"), &myService);
    // waiting until service comes up (new in Android 11)
    myService = waitForService<IFoo>(String16("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = waitForDeclaredService<IFoo>(String16("service-name"));

Dans le backend NDK (notez l'espace de noms aidl supplémentaire) :

    #include <android/binder_manager.h>
    // registering
    binder_exception_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // return if service is started now
    myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_checkService("service-name")));
    // is a service declared in the VINTF manifest
    // VINTF services have the type in the interface instance name.
    bool isDeclared = AServiceManager_isDeclared("android.hardware.light.ILights/default");
    // wait until a service is available (if isDeclared or you know it's available)
    myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_waitForService("service-name")));

Dans le backend Rust :

use myfoo::MyFoo;
use binder;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;

fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_binder(
        my_service,
        BinderFeatures::default(),
    );
    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
    // Does not return - spawn or perform any work you mean to do before this call.
    binder::ProcessState::join_thread_pool()
}

Dans le backend asynchrone Rust, avec un runtime monothread :

use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;

#[tokio::main(flavor = "current_thread")]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");

    // Sleeps forever, but does not join the binder threadpool.
    // Spawned tasks will run on this thread.
    std::future::pending().await
}

Une différence importante par rapport aux autres options est que nous n'appelons pas join_thread_pool lors de l'utilisation de Rust asynchrone et d'un runtime monothread. En effet, vous devez donner à Tokio un fil de discussion sur lequel il peut exécuter les tâches générées. Dans cet exemple, le thread principal remplira cet objectif. Toutes les tâches générées à l'aide de tokio::spawn s'exécuteront sur le thread principal.

Dans le backend asynchrone Rust, avec un runtime multithread :

use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;

#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");

    // Sleep forever.
    tokio::task::block_in_place(|| {
        binder::ProcessState::join_thread_pool();
    });
}

Avec le runtime Tokio multithread, les tâches générées ne s'exécutent pas sur le thread principal. Par conséquent, il est plus logique d'appeler join_thread_pool sur le thread principal afin que le thread principal ne soit pas simplement inactif. Vous devez envelopper l'appel dans block_in_place pour quitter le contexte asynchrone.

Vous pouvez demander à recevoir une notification lorsqu'un service hébergeant un classeur meurt. Cela peut aider à éviter les fuites de proxys de rappel ou à faciliter la récupération des erreurs. Effectuez ces appels sur les objets proxy de classeur.

  • En Java, utilisez android.os.IBinder::linkToDeath .
  • Dans le backend CPP, utilisez android::IBinder::linkToDeath .
  • Dans le backend NDK, utilisez AIBinder_linkToDeath .
  • Dans le backend Rust, créez un objet DeathRecipient , puis appelez my_binder.link_to_death(&mut my_death_recipient) . Notez que, comme DeathRecipient est propriétaire du rappel, vous devez conserver cet objet en vie aussi longtemps que vous souhaitez recevoir des notifications.

Informations sur l'appelant

Lors de la réception d'un appel du classeur du noyau, les informations sur l'appelant sont disponibles dans plusieurs API. Le PID (ou Process ID) fait référence à l'ID de processus Linux du processus qui envoie une transaction. L'UID (ou User ID) fait référence à l'ID utilisateur Linux. Lors de la réception d'un appel aller simple, le PID appelant est 0. En dehors d'un contexte de transaction de classeur, ces fonctions renvoient le PID et l'UID du processus en cours.

Dans le back-end Java :

    ... = Binder.getCallingPid();
    ... = Binder.getCallingUid();

Dans le backend du CPP :

    ... = IPCThreadState::self()->getCallingPid();
    ... = IPCThreadState::self()->getCallingUid();

Dans le back-end NDK :

    ... = AIBinder_getCallingPid();
    ... = AIBinder_getCallingUid();

Dans le backend Rust, lors de l'implémentation de l'interface, spécifiez ce qui suit (au lieu de lui permettre d'utiliser la valeur par défaut) :

    ... = ThreadState::get_calling_pid();
    ... = ThreadState::get_calling_uid();

Rapports de bogues et API de débogage pour les services

Lorsque les rapports de bogues sont exécutés (par exemple, avec adb bugreport ), ils collectent des informations partout dans le système pour faciliter le débogage de divers problèmes. Pour les services AIDL, les rapports de bogues utilisent les dumpsys binaires sur tous les services enregistrés auprès du gestionnaire de services pour transférer leurs informations dans le rapport de bogues. Vous pouvez également utiliser dumpsys sur la ligne de commande pour obtenir des informations d'un service avec dumpsys SERVICE [ARGS] . Dans les backends C++ et Java, vous pouvez contrôler l'ordre dans lequel les services sont sauvegardés en utilisant des arguments supplémentaires pour addService . Vous pouvez également utiliser dumpsys --pid SERVICE pour obtenir le PID d'un service lors du débogage.

Pour ajouter une sortie personnalisée à votre service, vous pouvez remplacer la méthode dump dans votre objet serveur comme si vous implémentiez toute autre méthode IPC définie dans un fichier AIDL. Ce faisant, vous devez limiter le dumping à l'autorisation de l'application android.permission.DUMP ou limiter le dumping à des UID spécifiques.

Dans le back-end Java :

    @Override
    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
        @Nullable String[] args) {...}

Dans le backend du CPP :

    status_t dump(int, const android::android::Vector<android::String16>&) override;

Dans le back-end NDK :

    binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;

Dans le backend Rust, lors de l'implémentation de l'interface, spécifiez ce qui suit (au lieu de lui permettre d'utiliser la valeur par défaut) :

    fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>

Obtenir dynamiquement le descripteur d'interface

Le descripteur d'interface identifie le type d'une interface. Ceci est utile lors du débogage ou lorsque vous disposez d'un classeur inconnu.

En Java, vous pouvez obtenir le descripteur d'interface avec du code tel que :

    service = /* get ahold of service object */
    ... = service.asBinder().getInterfaceDescriptor();

Dans le backend du CPP :

    service = /* get ahold of service object */
    ... = IInterface::asBinder(service)->getInterfaceDescriptor();

Les backends NDK et Rust ne prennent pas en charge cette fonctionnalité.

Obtenir statiquement le descripteur d'interface

Parfois (par exemple lors de l'enregistrement des services @VintfStability ), vous devez savoir quel est le descripteur d'interface de manière statique. En Java, vous pouvez obtenir le descripteur en ajoutant du code tel que :

    import my.package.IFoo;
    ... IFoo.DESCRIPTOR

Dans le backend du CPP :

    #include <my/package/BnFoo.h>
    ... my::package::BnFoo::descriptor

Dans le backend NDK (notez l'espace de noms aidl supplémentaire) :

    #include <aidl/my/package/BnFoo.h>
    ... aidl::my::package::BnFoo::descriptor

Dans le backend Rust :

    aidl::my::package::BnFoo::get_descriptor()

Plage d'énumération

Dans les backends natifs, vous pouvez parcourir les valeurs possibles qu'une énumération peut prendre. En raison de considérations de taille de code, cela n'est actuellement pas pris en charge en Java.

Pour une énumération MyEnum définie dans AIDL, l'itération est fournie comme suit.

Dans le backend du CPP :

    ::android::enum_range<MyEnum>()

Dans le back-end NDK :

   ::ndk::enum_range<MyEnum>()

Dans le backend Rust :

    MyEnum::enum_values()

Gestion des fils de discussion

Chaque instance de libbinder dans un processus maintient un pool de threads. Pour la plupart des cas d'utilisation, il doit s'agir d'exactement un pool de threads, partagé entre tous les backends. La seule exception à cela est lorsque le code du fournisseur peut charger une autre copie de libbinder pour communiquer avec /dev/vndbinder . Puisqu'il s'agit d'un nœud de classeur distinct, le pool de threads n'est pas partagé.

Pour le backend Java, le pool de threads ne peut qu'augmenter en taille (puisqu'il est déjà démarré) :

    BinderInternal.setMaxThreads(<new larger value>);

Pour le backend CPP, les opérations suivantes sont disponibles :

    // set max threadpool count (default is 15)
    status_t err = ProcessState::self()->setThreadPoolMaxThreadCount(numThreads);
    // create threadpool
    ProcessState::self()->startThreadPool();
    // add current thread to threadpool (adds thread to max thread count)
    IPCThreadState::self()->joinThreadPool();

De même, dans le backend NDK :

    bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
    ABinderProcess_startThreadPool();
    ABinderProcess_joinThreadPool();

Dans le backend Rust :

    binder::ProcessState::start_thread_pool();
    binder::add_service(“myservice”, my_service_binder).expect(“Failed to register service?”);
    binder::ProcessState::join_thread_pool();

Avec le backend asynchrone Rust, vous avez besoin de deux pools de threads : binder et Tokio. Cela signifie que les applications utilisant Rust asynchrone nécessitent des considérations particulières, notamment en ce qui concerne l'utilisation de join_thread_pool . Voir la section sur l'enregistrement des services pour plus d'informations à ce sujet.

Noms réservés

C++, Java et Rust réservent certains noms comme mots-clés ou pour une utilisation spécifique au langage. Bien que l'AIDL n'applique pas de restrictions basées sur les règles du langage, l'utilisation de noms de champs ou de types correspondant à un nom réservé peut entraîner un échec de compilation pour C++ ou Java. Pour Rust, le champ ou le type est renommé en utilisant la syntaxe "raw identifier", accessible via le préfixe r# .

Nous vous recommandons d'éviter d'utiliser des noms réservés dans vos définitions AIDL lorsque cela est possible afin d'éviter des liaisons peu ergonomiques ou un échec pur et simple de la compilation.

Si vous avez déjà des noms réservés dans vos définitions AIDL, vous pouvez renommer les champs en toute sécurité tout en restant compatible avec le protocole ; vous devrez peut-être mettre à jour votre code pour continuer à créer, mais tous les programmes déjà construits continueront à interagir.

Noms à éviter : * Mots-clés C++ * Mots-clés Java * Mots-clés Rust