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 une langue particulière avec un runtime spécifique. Selon le contexte, vous devez utiliser différents backends AIDL.
AIDL a les backends suivants :
Backend | Langue | Surface 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 pour la gestion des services, sont réservées à une utilisation interne de la plate-forme et ne sont pas disponibles pour les applications. Pour plus d'informations sur l'utilisation d'AIDL dans les applications, consultez la documentation destinée aux développeurs .
- Le backend Rust a été introduit dans Android 12 ; le backend NDK est disponible depuis Android 10.
- La caisse Rust est construite au-dessus de
libbinder_ndk
. Les APEX utilisent la caisse de classeur de la même manière que n'importe qui d'autre du côté système. La partie Rust est regroupée dans un APEX et expédiée à l'intérieur de celui-ci. Cela dépend dulibbinder_ndk.so
sur la partition système.
Construire des systèmes
Selon le backend, il existe deux façons de compiler AIDL en code stub. Pour plus de détails sur les systèmes de construction, 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 d'AIDL sont utilisés (pas le backend NDK), et les classes pour utiliser les fichiers AIDL correspondants sont automatiquement ajoutées au module. Des options telles que local_include_dirs
, qui indique au système de construction le chemin racine vers les 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, voir l' exemple Rust AIDL .
aidl_interface
Voir AIDL stable . Les types utilisés avec ce système de construction doivent être structurés ; c'est-à-dire exprimé directement en AIDL. Cela signifie que les parcelables personnalisés ne peuvent pas être utilisés.
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, 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 | bourdonner | bourdonner | bourdonner |
octet | int8_t | int8_t | i8 |
carboniser | char16_t | char16_t | u16 |
entier | int32_t | int32_t | i32 |
longue | int64_t | int64_t | i64 |
flotter | flotter | flotter | f32 |
double | double | double | f64 |
Chaîne | Android :: Chaîne16 | std ::chaîne | Chaîne |
android.os.Colisable | Android :: Colisable | N / A | N / A |
IBinder | 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 | classeur::parcel::ParcelFileDescriptor |
ParcelFileDescriptor | android :: os :: ParcelFileDescriptor | ndk :: ScopedFileDescriptor | classeur::parcel::ParcelFileDescriptor |
type d'interface (T) | Android :: sp<T> | std ::shared_ptr<T> | liant::Fort |
type colisable (T) | J | J | J |
type union (T) 5 | J | J | J |
T[N] 6 | std::array<T, N> | std::array<T, N> | [T ; N] |
1. Dans Android 12 ou supérieur, 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>
où T
est l'un de String
, IBinder
, ParcelFileDescriptor
ou parcelable. Dans Android 13 ou supérieur, 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 tableau comme T[]
, car ils fonctionnent dans tous les backends.
3. Le backend NDK prend en charge List<T>
où T
est l'un des String
, ParcelFileDescriptor
ou parcelable. Dans Android 13 ou supérieur, T
peut être n'importe quel type non primitif, à l'exception des tableaux.
4. Les types sont passés 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 supérieur.
6. Dans Android 13 ou supérieur, 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 tableau.
Directionnalité (entrée/sortie/entrée)
Lorsque vous spécifiez les types des arguments des fonctions, vous pouvez les spécifier comme in
, out
ou inout
. Cela contrôle la direction dans laquelle 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 ancien appelé, les champs supplémentaires présents uniquement dans l'appelant sont réinitialisés à leurs valeurs par défaut. En ce qui concerne Rust, un type inout
normal reçoit &mut Vec<T>
, et un type inout
liste reçoit &mut Vec<T>
.
UTF8/UTF16
Avec le backend CPP, vous pouvez choisir si les chaînes sont au format 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 null dans le backend Java avec @nullable
pour exposer les valeurs null aux backends CPP et NDK. Dans le backend Rust, ces types @nullable
sont exposés en tant que Option<T>
. Les serveurs natifs rejettent les valeurs nulles par défaut. Les seules exceptions à cela sont les types d' 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
Dans les backends C++ et Java du système de génération principal, vous pouvez déclarer un parcelable qui est implémenté manuellement dans un backend cible (en C++ ou en Java).
package my.package;
parcelable Foo;
ou avec la déclaration d'en-tête C++ :
package my.package;
parcelable Foo cpp_header "my/package/Foo.h";
Ensuite, vous pouvez utiliser ce parcelable comme type dans les fichiers AIDL, mais il ne sera pas généré par AIDL.
Rust ne prend pas en charge les parcelables personnalisés.
Les valeurs par défaut
Les parcelables structurés 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 en tant que 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 en tant que chaîne vide et les champs List<T>
sont initialisés en tant que 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 pour les services à 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 binder. Leur utilisation est bien documentée dans la définition AIDL et ils ne nécessitent aucun statut ou type de retour défini par l'utilisateur.
Paramètres de sortie avec erreurs
Lorsqu'une fonction AIDL signale une erreur, la fonction peut ne 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 au lieu de se produire 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, ils peuvent utiliser l'erreur intégrée spécifique au service spécial 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 sous la forme d'une enum
const int
ou int
et ne sont pas analysées par binder.
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 d'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 une, 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
utilise EX_SERVICE_SPECIFIC
avec l'erreur définie par l'utilisateur.
Les types d'erreur intégrés se trouvent dans les fichiers suivants :
Backend | Définition |
---|---|
Java | android/os/Parcel.java |
RPC | binder/Status.h |
NDK | android/binder_status.h |
Rouiller | android/binder_status.h |
Utilisation de divers backends
Ces instructions sont spécifiques au code de la plate-forme 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, une parcelle ou une union, vous pouvez l'importer dans Java :
import my.package.IFoo;
Ou dans le backend CPP :
#include <my/package/IFoo.h>
Ou dans le backend NDK (notez l'espace de noms extra aidl
):
#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é dans Java, dans les backends CPP/NDK, vous devez inclure l'en-tête pour son type racine. Par exemple, lors de l'importation d'un type imbriqué Bar
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 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 supplémentaire aidl
):
#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(())
}
}
S'inscrire et obtenir des services
Les services de la plate-forme 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 de servicemanager
correspondante pour les détails exacts. Ces opérations ne peuvent être effectuées que lors de la compilation sur la plate-forme 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 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 supplémentaire aidl
):
#include <android/binder_manager.h>
// registering
status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
// return if service is started now
myService = IFoo::fromBinder(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(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()
}
Lien avec la mort
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 des objets proxy binder.
- 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 appelezmy_binder.link_to_death(&mut my_death_recipient)
. Notez que puisque leDeathRecipient
possède le rappel, vous devez garder cet objet actif aussi longtemps que vous souhaitez recevoir des notifications.
Informations sur l'appelant
Lors de la réception d'un appel de 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 unidirectionnel, 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 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 l'autoriser 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 de tout le système pour aider au débogage de divers problèmes. Pour les services AIDL, les rapports de bogue utilisent les dumpsys
binaires sur tous les services enregistrés auprès du gestionnaire de services pour vider leurs informations dans le rapport de bogue. 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 vidé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 de 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 vidage à l'autorisation d'application android.permission.DUMP
ou restreindre le vidage à 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 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 l'autoriser 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 avez un classeur inconnu.
En Java, vous pouvez obtenir le descripteur d'interface avec un code tel que :
service = /* get ahold of service object */
... = service.asBinder().getInterfaceDescriptor();
Dans le backend 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 (comme 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 CPP :
#include <my/package/BnFoo.h>
... my::package::BnFoo::descriptor
Dans le backend NDK (notez l'espace de noms supplémentaire aidl
):
#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 pas pris en charge en Java actuellement.
Pour une énumération MyEnum
définie dans AIDL, l'itération est fournie comme suit.
Dans le backend CPP :
::android::enum_range<MyEnum>()
Dans le back-end NDK :
::ndk::enum_range<MyEnum>()
Dans le backend Rust :
MyEnum::enum_range()
Gestion des fils
Chaque instance de libbinder
dans un processus gère un pool de threads. Dans 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 parler à /dev/vndbinder
. Comme il s'agit d'un nœud de classeur séparé, 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();
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 qu'AIDL n'applique pas de restrictions basées sur des règles de langage, l'utilisation de noms de champ ou de type 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 en utilisant le préfixe r#
.
Nous vous recommandons d'éviter d'utiliser des noms réservés dans vos définitions AIDL dans la mesure du possible afin d'éviter des liaisons non ergonomiques ou un échec de compilation pur et simple.
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 à construire, mais tous les programmes déjà construits continueront à interagir.
Noms à éviter : * Mots-clés C++ * Mots -clés Java * Mots-clés Rust
,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 une langue particulière avec un runtime spécifique. Selon le contexte, vous devez utiliser différents backends AIDL.
AIDL a les backends suivants :
Backend | Langue | Surface 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 pour la gestion des services, sont réservées à une utilisation interne de la plate-forme et ne sont pas disponibles pour les applications. Pour plus d'informations sur l'utilisation d'AIDL dans les applications, consultez la documentation destinée aux développeurs .
- Le backend Rust a été introduit dans Android 12 ; le backend NDK est disponible depuis Android 10.
- La caisse Rust est construite au-dessus de
libbinder_ndk
. Les APEX utilisent la caisse de classeur de la même manière que n'importe qui d'autre du côté système. La partie Rust est regroupée dans un APEX et expédiée à l'intérieur de celui-ci. Cela dépend dulibbinder_ndk.so
sur la partition système.
Construire des systèmes
Selon le backend, il existe deux façons de compiler AIDL en code stub. Pour plus de détails sur les systèmes de construction, 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 d'AIDL sont utilisés (pas le backend NDK), et les classes pour utiliser les fichiers AIDL correspondants sont automatiquement ajoutées au module. Des options telles que local_include_dirs
, qui indique au système de construction le chemin racine vers les 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, voir l' exemple Rust AIDL .
aidl_interface
Voir AIDL stable . Les types utilisés avec ce système de construction doivent être structurés ; c'est-à-dire exprimé directement en AIDL. Cela signifie que les parcelables personnalisés ne peuvent pas être utilisés.
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, 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 | bourdonner | bourdonner | bourdonner |
octet | int8_t | int8_t | i8 |
carboniser | char16_t | char16_t | u16 |
entier | int32_t | int32_t | i32 |
longue | int64_t | int64_t | i64 |
flotter | flotter | flotter | f32 |
double | double | double | f64 |
Chaîne | Android :: Chaîne16 | std ::chaîne | Chaîne |
android.os.Colisable | Android :: Colisable | N / A | N / A |
IBinder | 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 | classeur::parcel::ParcelFileDescriptor |
ParcelFileDescriptor | android :: os :: ParcelFileDescriptor | ndk :: ScopedFileDescriptor | classeur::parcel::ParcelFileDescriptor |
type d'interface (T) | Android :: sp<T> | std ::shared_ptr<T> | liant::Fort |
type colisable (T) | J | J | J |
type union (T) 5 | J | J | J |
T[N] 6 | std::array<T, N> | std::array<T, N> | [T ; N] |
1. Dans Android 12 ou supérieur, 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>
où T
est l'un de String
, IBinder
, ParcelFileDescriptor
ou parcelable. Dans Android 13 ou supérieur, 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 tableau comme T[]
, car ils fonctionnent dans tous les backends.
3. Le backend NDK prend en charge List<T>
où T
est l'un des String
, ParcelFileDescriptor
ou parcelable. Dans Android 13 ou supérieur, T
peut être n'importe quel type non primitif, à l'exception des tableaux.
4. Les types sont passés 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 supérieur.
6. Dans Android 13 ou supérieur, 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 tableau.
Directionnalité (entrée/sortie/entrée)
Lorsque vous spécifiez les types des arguments des fonctions, vous pouvez les spécifier comme in
, out
ou inout
. Cela contrôle la direction dans laquelle 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 ancien appelé, les champs supplémentaires présents uniquement dans l'appelant sont réinitialisés à leurs valeurs par défaut. En ce qui concerne Rust, un type inout
normal reçoit &mut Vec<T>
, et un type inout
liste reçoit &mut Vec<T>
.
UTF8/UTF16
Avec le backend CPP, vous pouvez choisir si les chaînes sont au format 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 null dans le backend Java avec @nullable
pour exposer les valeurs null aux backends CPP et NDK. Dans le backend Rust, ces types @nullable
sont exposés en tant que Option<T>
. Les serveurs natifs rejettent les valeurs nulles par défaut. Les seules exceptions à cela sont les types d' 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
Dans les backends C++ et Java du système de génération principal, vous pouvez déclarer un parcelable qui est implémenté manuellement dans un backend cible (en C++ ou en Java).
package my.package;
parcelable Foo;
ou avec la déclaration d'en-tête C++ :
package my.package;
parcelable Foo cpp_header "my/package/Foo.h";
Ensuite, vous pouvez utiliser ce parcelable comme type dans les fichiers AIDL, mais il ne sera pas généré par AIDL.
Rust ne prend pas en charge les parcelables personnalisés.
Les valeurs par défaut
Les parcelables structurés 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 en tant que 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 en tant que chaîne vide et les champs List<T>
sont initialisés en tant que 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 pour les services à 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 binder. Leur utilisation est bien documentée dans la définition AIDL et ils ne nécessitent aucun statut ou type de retour défini par l'utilisateur.
Paramètres de sortie avec erreurs
Lorsqu'une fonction AIDL signale une erreur, la fonction peut ne 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 au lieu de se produire 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, ils peuvent utiliser l'erreur intégrée spécifique au service spécial 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 sous la forme d'une enum
const int
ou int
et ne sont pas analysées par binder.
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 d'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 une, 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
utilise EX_SERVICE_SPECIFIC
avec l'erreur définie par l'utilisateur.
Les types d'erreur intégrés se trouvent dans les fichiers suivants :
Backend | Définition |
---|---|
Java | android/os/Parcel.java |
RPC | binder/Status.h |
NDK | android/binder_status.h |
Rouiller | android/binder_status.h |
Utilisation de divers backends
Ces instructions sont spécifiques au code de la plate-forme 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, une parcelle ou une union, vous pouvez l'importer dans Java :
import my.package.IFoo;
Ou dans le backend CPP :
#include <my/package/IFoo.h>
Ou dans le backend NDK (notez l'espace de noms extra aidl
):
#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é dans Java, dans les backends CPP/NDK, vous devez inclure l'en-tête pour son type racine. Par exemple, lors de l'importation d'un type imbriqué Bar
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 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 supplémentaire aidl
):
#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(())
}
}
S'inscrire et obtenir des services
Les services de la plate-forme 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 de servicemanager
correspondante pour les détails exacts. Ces opérations ne peuvent être effectuées que lors de la compilation sur la plate-forme 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 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 supplémentaire aidl
):
#include <android/binder_manager.h>
// registering
status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
// return if service is started now
myService = IFoo::fromBinder(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(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()
}
Lien avec la mort
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 des objets proxy binder.
- 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 appelezmy_binder.link_to_death(&mut my_death_recipient)
. Notez que puisque leDeathRecipient
possède le rappel, vous devez garder cet objet actif aussi longtemps que vous souhaitez recevoir des notifications.
Informations sur l'appelant
Lors de la réception d'un appel de 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 unidirectionnel, le PID appelant est 0. En dehors d'un contexte de transaction de liaison, ces fonctions renvoient le PID et l'UID du processus en cours.
Dans le back-end Java :
... = Binder.getCallingPid();
... = Binder.getCallingUid();
Dans le backend 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 l'autoriser 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 de tout le système pour aider au débogage de divers problèmes. For AIDL services, bugreports use the binary dumpsys
on all services registered with the service manager to dump their information into the bugreport. You can also use dumpsys
on the commandline to get information from a service with dumpsys SERVICE [ARGS]
. In the C++ and Java backends, you can control the order in which services get dumped by using additional arguments to addService
. You can also use dumpsys --pid SERVICE
to get the PID of a service while debugging.
To add custom output to your service, you can override the dump
method in your server object like you are implementing any other IPC method defined in an AIDL file. When doing this, you should restrict dumping to the app permission android.permission.DUMP
or restrict dumping to specific UIDs.
In the Java backend:
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {...}
In the CPP backend:
status_t dump(int, const android::android::Vector<android::String16>&) override;
In the NDK backend:
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
In the Rust backend, when implementing the interface, specify the following (instead of allowing it to default):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
Dynamically getting interface descriptor
The interface descriptor identifies the type of an interface. This is useful when debugging or when you have an unknown binder.
In Java, you can get the interface descriptor with code such as:
service = /* get ahold of service object */
... = service.asBinder().getInterfaceDescriptor();
In the CPP backend:
service = /* get ahold of service object */
... = IInterface::asBinder(service)->getInterfaceDescriptor();
The NDK and Rust backends don't support this functionality.
Statically getting interface descriptor
Sometimes (such as when registering @VintfStability
services), you need to know what the interface descriptor is statically. In Java, you can get the descriptor by adding code such as:
import my.package.IFoo;
... IFoo.DESCRIPTOR
In the CPP backend:
#include <my/package/BnFoo.h>
... my::package::BnFoo::descriptor
In the NDK backend (notice the extra aidl
namespace):
#include <aidl/my/package/BnFoo.h>
... aidl::my::package::BnFoo::descriptor
In the Rust backend:
aidl::my::package::BnFoo::get_descriptor()
Enum Range
In native backends, you can iterate over the possible values an enum can take on. Due to code size considerations, this isn't supported in Java currently.
For an enum MyEnum
defined in AIDL, iteration is provided as follows.
In the CPP backend:
::android::enum_range<MyEnum>()
In the NDK backend:
::ndk::enum_range<MyEnum>()
In the Rust backend:
MyEnum::enum_range()
Thread management
Every instance of libbinder
in a process maintains one threadpool. For most use cases, this should be exactly one threadpool, shared across all backends. The only exception to this is when vendor code might load another copy of libbinder
to talk to /dev/vndbinder
. Since this is on a separate binder node, the threadpool isn't shared.
For the Java backend, the threadpool can only increase in size (since it is already started):
BinderInternal.setMaxThreads(<new larger value>);
For the CPP backend, the following operations are available:
// 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();
Similarly, in the NDK backend:
bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
ABinderProcess_startThreadPool();
ABinderProcess_joinThreadPool();
In the Rust backend:
binder::ProcessState::start_thread_pool();
binder::add_service(“myservice”, my_service_binder).expect(“Failed to register service?”);
binder::ProcessState::join_thread_pool();
Reserved Names
C++, Java, and Rust reserve some names as keywords or for language-specific use. While AIDL doesn't enforce restrictions based on language rules, using field or type names that matching a reserved name might result in a compilation failure for C++ or Java. For Rust, the field or type is renamed using the "raw identifier" syntax, accessible using the r#
prefix.
We recommend that you avoid using reserved names in your AIDL definitions where possible to avoid unergonomic bindings or outright compilation failure.
If you already have reserved names in your AIDL definitions, you can safely rename fields while remaining protocol compatible; you may need to update your code to continue building, but any already built programs will continue to interoperate.
Names to avoid: * C++ Keywords * Java Keywords * Rust Keywords