Un backend AIDL est une cible pour la génération de code bouchon. Lorsque vous utilisez des fichiers AIDL, utilisez-les toujours dans un langage avec un environnement d'exécution spécifique. En fonction de le contexte, vous devez utiliser différents backends AIDL.
Dans le tableau suivant, la stabilité de la surface de l'API fait référence à la capacité
pour compiler le code sur cette surface d'API, de manière à ce qu'il puisse être
livré indépendamment du binaire system.img
libbinder.so
.
AIDL comporte les backends suivants:
Backend | Langue | Surface de l'API | Créer des systèmes |
---|---|---|---|
Java | Java | SDK/SystemApi (stable*) | tous |
NDK | C++ | libbinder_ndk (stable*) | interface_idl |
CPP | C++ | libbinder (instable) | tous |
Rust | Rust | libbinder_rs (stable*) | interface_idl |
- Ces surfaces d'API sont stables, mais bon nombre des API, comme celles de service sont réservés à une utilisation interne de la plate-forme et ne sont pas accessibles applications. Pour en savoir plus sur l'utilisation d'AIDL dans les applications, consultez documentation pour les développeurs.
- Le backend Rust a été introduit dans Android 12. la Le backend du NDK est disponible à partir d'Android 10.
- La caisse Rust est construite sur
libbinder_ndk
, ce qui lui permet d'être stable et portable. Les apex utilisent la caisse de rangement de la même manière que n'importe qui d’autre du côté du système. La portion Rust est intégrée dans un APEX et expédiée qu'il contient. Cela dépend delibbinder_ndk.so
sur la partition système.
Créer des systèmes
Selon le backend, il existe deux façons de compiler AIDL sous forme de bouchon du code source. Pour en savoir plus sur les systèmes de compilation, consultez Documentation de référence du module Soong
Système de compilation principal
Dans tout module Android.bp cc_
ou java_
(ou dans leurs équivalents Android.mk
),
Les fichiers .aidl
peuvent être spécifiés en tant que fichiers sources. Dans ce cas, le code Java/CPP
d'AIDL sont utilisés (et non le backend du NDK), et les classes à utiliser
les fichiers AIDL correspondants
sont automatiquement ajoutés au module. Options
tel que local_include_dirs
, qui indique au système de compilation le chemin d'accès racine
Les fichiers AIDL de ce module peuvent être spécifiés dans ces modules sous une règle aidl:
groupe. Notez que le backend Rust ne doit être utilisé qu'avec Rust. rust_
modules sont
gérés différemment, car les fichiers AIDL ne sont pas spécifiés en tant que fichiers sources.
À la place, le module aidl_interface
génère une rustlib
appelée
<aidl_interface name>-rust
, qui peuvent être associées. Pour en savoir plus, consultez
l'exemple Rust AIDL.
interface_idl
Les types utilisés avec ce système de compilation doivent être structurés. Pour être structuré, les parcelables doivent contenir des champs directement et ne doivent pas être des déclarations de type définies directement dans les langues cibles. Pour savoir comment AIDL structuré s'intègre la version stable d'AIDL, consultez la page AIDL structuré ou stable.
Types
Vous pouvez considérer le compilateur aidl
comme implémentation de référence pour les types.
Lorsque vous créez une interface, appelez aidl --lang=<backend> ...
pour afficher
le fichier d'interface obtenu. 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 de NDK | Type de rouille |
---|---|---|---|
booléen | Booléen | Booléen | Booléen |
octet | int8_t | int8_t | i8 |
car. | car16_t | car16_t | U16 |
int | int32_t | int32_t | i32 |
long | int64_t | int64_t | I64 |
float | float | float | F32 |
double | double | double | F64 |
Chaîne | android::String16 | std::chaîne | Chaîne |
android.os.Parcelable | android::Parcelable (Parcelable) | N/A | N/A |
IBinder | android::IBinder | ndk::SpAIBinder | binder::SpIBinder |
M[] | std::vecteur<T> | std::vecteur<T> | Entrée: &[T] Sortie: Vec<T> |
byte[] | std::vecteur<uint8_t> | std::vecteur<int8_t>1 | Entrée: &[u8] Sortie: Vec<u8> |
Liste<T> | std::vecteur<T>2 | std::vecteur<T>3 | Entrée: &[T]4 Sortie: Vec<T> |
FileDescriptor | 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>7 | binder::Forte |
type parcelable (T) | T | T | T |
Type d'union (T)5 | T | T | T |
V[N] 6 | std::tableau<T, N> | std::tableau<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>
, où T
est l'une des valeurs suivantes : String
,
IBinder
, ParcelFileDescriptor
ou parcelable. Sur 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. L'AOSP vous recommande
utilisent des types de tableaux tels que T[]
, car ils fonctionnent dans tous les backends.
3. Le backend du NDK est compatible avec List<T>
, où T
est l'une des valeurs suivantes : String
,
ParcelFileDescriptor
ou parcelable. Sous Android 13
ou supérieur, T
peut correspondre à 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 compatibles avec Android 12 et plus élevée.
6. Dans Android 13 ou version ultérieure, les tableaux de taille fixe sont
compatibles. Les tableaux à taille fixe peuvent avoir plusieurs dimensions (par exemple, int[3][4]
).
Dans le backend Java, les tableaux de taille fixe sont représentés par des types de tableaux.
7. Pour instancier un objet de liaison SharedRefBase
, utilisez
SharedRefBase::make\<My\>(... args ...)
Cette fonction crée
std::shared_ptr\<T\>
objet
qui est également géré en interne, au cas où le classeur appartenait à un autre
processus. Lorsque vous créez l'objet d'une autre manière, la propriété est double.
Direction (entrée/sortie/entrée)
Lorsque vous spécifiez les types d'arguments des fonctions, vous pouvez spécifier
comme in
, out
ou inout
. Cela permet de contrôler la direction dans laquelle les informations
transmis pour un appel IPC. in
est le sens par défaut et indique que les données sont
transmis de l'appelant à l'appelé. out
signifie que les données sont transmises depuis
appelé à l'appelant. inout
est la combinaison des deux. Toutefois,
L'équipe Android vous recommande d'éviter d'utiliser le spécificateur d'argument inout
.
Si vous utilisez inout
avec une interface avec gestion des versions et un appel plus ancien, le
Les valeurs par défaut des champs supplémentaires qui ne sont présents que dans l'appelant sont rétablies.
valeurs. En ce qui concerne Rust, un type inout
normal reçoit &mut Vec<T>
, et
un type de liste inout
reçoit &mut Vec<T>
.
interface IRepeatExamples {
MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
MyParcelable RepeatParcelableWithIn(in MyParcelable token);
void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
void RepeatParcelableWithInOut(inout MyParcelable param);
}
UTF-8/UTF-16
Avec le backend CPP, vous pouvez choisir si les chaînes sont utf-8 ou utf-16. Déclarer
chaînes en tant que @utf8InCpp String
dans AIDL pour les convertir automatiquement en utf-8.
Les backends NDK et Rust utilisent toujours des chaînes utf-8. Pour en savoir plus sur
l'annotation utf8InCpp
, consultez la section Annotations dans AIDL.
Possibilité de valeur nulle
Vous pouvez annoter des types qui peuvent avoir une valeur nulle dans le backend Java avec @nullable
.
pour exposer les valeurs nulles aux backends CPP et NDK. Dans le backend Rust,
Les types @nullable
sont exposés en tant que 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 peut toujours être nulle pour les lectures du NDK et les écritures CPP/NDK. Pour en savoir plus,
sur l'annotation nullable
, consultez
Annotations dans AIDL
Parcelables personnalisés
Un parcelable est un élément parcelable mis en œuvre manuellement dans une cible. backend. Utilisez des parcelables personnalisés uniquement si vous souhaitez prendre en charge d'autres pour un parcelable existant qui ne peut pas être modifié.
Pour déclarer une parcelle personnalisée afin d'en informer AIDL, l'AIDL la déclaration parcelable se présente comme suit:
package my.pack.age;
parcelable Foo;
Par défaut, cette méthode déclare un fragmentable Java où my.pack.age.Foo
est un code Java
pour implémenter l'interface Parcelable
.
Pour déclarer un backend CPP personnalisé parcelable 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
se présente comme suit:
#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 déclarer un NDK personnalisé parcelable 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
se présente comme suit:
#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 (version expérimentale AOSP), pour la déclaration d'une règle Rust personnalisée
parcelable 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
se présente comme suit:
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 fragmentable comme type dans les fichiers AIDL, mais il ne sera pas
générés par AIDL. Fournir les opérateurs <
et ==
pour le backend CPP/NDK
parcelables personnalisés pour les utiliser dans union
.
Valeurs par défaut
Les parcelables structurés peuvent déclarer des valeurs par défaut par champ pour les primitives,
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
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 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.
Gestion des exceptions
Le système d'exploitation Android fournit des types d'erreurs intégrés pour les services à utiliser lors de la création de rapports les erreurs. Ceux-ci sont utilisés par la liaison et peuvent être utilisés par tout service implémentant une interface de liaison. Leur utilisation est bien documentée dans la définition AIDL et ne nécessitent aucun statut ni type renvoyé défini par l'utilisateur.
Paramètres de sortie comportant des erreurs
Lorsqu'une fonction AIDL signale une erreur, il est possible qu'elle ne s'initialise pas ou
modifier les paramètres de sortie. Plus précisément, les paramètres de sortie peuvent être modifiés si le
se produit lors du décodage, et non lors du traitement.
de la transaction elle-même. En général, lorsqu'un message d'erreur AIDL est généré
tous les paramètres inout
et out
, ainsi que la valeur renvoyée (qui
agit comme un paramètre out
dans certains backends) doit être considéré comme
un état indéfini.
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és d'une manière particulière. Par exemple, EX_UNSUPPORTED_OPERATION
et
Vous pouvez utiliser des EX_ILLEGAL_ARGUMENT
pour décrire la condition d'erreur, mais
EX_TRANSACTION_FAILED
ne doit pas être utilisé, car il est traité de manière spéciale par le paramètre
de l'infrastructure sous-jacente. Consultez les définitions spécifiques aux backends pour en savoir plus
des 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'erreurs intégrés, ils peuvent alors utiliser la fonction
qui permet d'inclure une valeur d'erreur spécifique au service
défini par l'utilisateur. Ces erreurs spécifiques aux services sont généralement définies
l'interface AIDL en tant que enum
sauvegardée sur const int
ou int
, et ne sont pas analysées par
binder.
En Java, les erreurs sont mappées à des exceptions, telles que android.os.RemoteException
. Pour
exceptions spécifiques au service, Java utilise android.os.ServiceSpecificException
.
ainsi que l'erreur définie par l'utilisateur.
Le code natif dans Android n'utilise pas d'exceptions. Le backend CPP utilise
android::binder::Status
Le backend du NDK utilise ndk::ScopedAStatus
. Toutes les
générée par AIDL renvoie l'une de ces valeurs, représentant le statut
. 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
les livrer à l'utilisateur. Pour les erreurs spécifiques au service, la valeur renvoyée
Status
ou ScopedAStatus
utilise EX_SERVICE_SPECIFIC
avec l'élément
définie par l'utilisateur.
Les types d'erreurs intégrés se trouvent dans les fichiers suivants:
Backend | Définition |
---|---|
Java | android/os/Parcel.java |
CPP | binder/Status.h |
NDK | android/binder_status.h |
Rust | android/binder_status.h |
Utiliser différents backends
Ces instructions sont spécifiques au code de la plate-forme Android. Ces exemples utilisent
type défini, my.package.IFoo
. Pour obtenir des instructions sur
l'utilisation du backend Rust,
voir l'exemple Rust AIDL
sur les motifs Rust d'Android
.
Types d'importation
Que le type défini soit une interface, un parcelable ou une union, vous pouvez importer en Java:
import my.package.IFoo;
Ou dans le backend CPP:
#include <my/package/IFoo.h>
Ou dans le backend du 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 qu'il soit possible d'importer un type imbriqué en Java, dans les backends CPP/NDK, vous devez
inclure l’en-tête pour son type de racine. Par exemple, lors de l'importation d'un type imbriqué
Bar
défini dans my/package/IFoo.aidl
(IFoo
est le type de racine du
), vous devez inclure <my/package/IFoo.h>
pour le backend CPP (ou
<aidl/my/package/IFoo.h>
pour le backend du NDK).
Implémenter des services
Pour implémenter un service, vous devez hériter de la classe bouchon native. Ce cours lit les commandes du pilote de liaison et exécute les méthodes que vous mettre en œuvre. Imaginez que vous avez 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 du 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 Async Rust:
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 plate-forme Android sont généralement enregistrés auprès du servicemanager
processus. Outre les API ci-dessous, certaines d'entre elles vérifient
service (c'est-à-dire qu'elles sont renvoyées immédiatement si le service n'est pas disponible).
Pour en savoir plus, consultez l'interface servicemanager
correspondante. Ces
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 du 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 Rust asynchrone, avec un environnement d'exécution à thread unique:
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
}
L'une des différences majeures par rapport aux autres options est que nous n'appelons pas
join_thread_pool
lors de l'utilisation de Rust asynchrone et d'un environnement d'exécution à thread unique C'est
car vous devez donner à Tokio un thread lui permettant d'exécuter les tâches générées. Dans
dans cet exemple, le thread principal remplira cet objectif. Toutes les tâches générées à l'aide de
tokio::spawn
s'exécutera sur le thread principal.
Dans le backend Rust asynchrone, avec un environnement d'exécution 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 l'environnement d'exécution multithread Tokio, les tâches générées ne s'exécutent pas sur le
thread. Par conséquent, il est plus logique d'appeler join_thread_pool
sur la partie principale
afin que le thread principal
ne soit pas seulement inactif. Vous devez encapsuler l'appel
block_in_place
pour quitter le contexte asynchrone.
Lien vers la mort
Vous pouvez demander à recevoir une notification lorsqu'un service hébergeant une liaison meurt. Cela peut permettre d'éviter les fuites de proxys de rappel ou de faciliter la récupération des erreurs. Effectuez ces appels sur des objets proxy de liaison.
- En Java, utilisez
android.os.IBinder::linkToDeath
. - Dans le backend CPP, utilisez
android::IBinder::linkToDeath
. - Dans le backend du NDK, utilisez
AIBinder_linkToDeath
. - Dans le backend Rust, créez un objet
DeathRecipient
, puis appelezmy_binder.link_to_death(&mut my_death_recipient)
Étant donné que le paramètreDeathRecipient
possède le rappel. Vous devez conserver cet objet actif aussi longtemps car vous souhaitez recevoir des notifications.
Informations sur l'appelant
Lors de la réception d'un appel de liaison du noyau, les informations sur l'appelant sont disponibles dans plusieurs API. Le PID (ou Process ID) fait référence à l'identifiant de processus Linux du qui envoie une transaction. L'UID (ou User ID) fait référence au ID utilisateur Linux. Lors de la réception d'un appel à sens unique, le PID d'appel est de 0. Quand ? en dehors d'un contexte de transaction de liaison, ces fonctions renvoient le PID et l'UID du processus actuel.
Dans le backend Java:
... = Binder.getCallingPid();
... = Binder.getCallingUid();
Dans le backend CPP:
... = IPCThreadState::self()->getCallingPid();
... = IPCThreadState::self()->getCallingUid();
Dans le backend du NDK:
... = AIBinder_getCallingPid();
... = AIBinder_getCallingUid();
Dans le backend Rust, lors de l'implémentation de l'interface, spécifiez les éléments suivants : (au lieu de l'autoriser par défaut):
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
Rapports de bugs et API de débogage pour les services
Lorsque des rapports de bug sont générés (par exemple, avec adb bugreport
), ils collectent
des informations de tout le système afin d’aider
à déboguer divers problèmes.
Pour les services AIDL, les rapports de bug utilisent le binaire dumpsys
sur tous les services
enregistré auprès du gestionnaire de services afin de vider ses informations
rapport de bug. 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,
peuvent contrôler l'ordre dans lequel les services sont vidés à l'aide d'arguments supplémentaires ;
à addService
. Vous pouvez également utiliser dumpsys --pid SERVICE
pour obtenir le PID d'un
pendant le débogage.
Pour ajouter une sortie personnalisée à votre service, vous pouvez remplacer le dump
dans votre objet serveur, comme lorsque vous mettez en œuvre toute autre méthode d'IPC
définies dans un fichier AIDL. Vous devez alors limiter le vidage à l'application.
l'autorisation android.permission.DUMP
ou limiter le vidage à des UID spécifiques.
Dans le backend 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 backend du 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 les éléments suivants : (au lieu de l'autoriser par défaut):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
Obtenir dynamiquement le descripteur de l'interface
Le descripteur d'interface identifie le type d'une interface. C'est utile lors du débogage ou en cas de liaison inconnue.
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();
Le NDK et les backends Rust ne sont pas compatibles avec cette fonctionnalité.
Obtenir le descripteur d'interface de manière statique
Parfois (par exemple, lorsque vous enregistrez des services @VintfStability
), vous devez parfois
savoir ce qu'est le descripteur
d'interface de manière statique. En Java, vous pouvez obtenir
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 du 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 itérer les valeurs possibles d'une énumération. activé. En raison des considérations liées à la taille du code, cela n'est 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 CPP:
::android::enum_range<MyEnum>()
Dans le backend du NDK:
::ndk::enum_range<MyEnum>()
Dans le backend Rust:
MyEnum::enum_values()
Gestion des threads
Chaque instance de libbinder
dans un processus gère un pool de threads. Pour la plupart
dans tous les cas d'utilisation, il doit s'agir d'un seul pool de threads, partagé entre tous les backends.
Seule exception : le code du fournisseur peut charger une autre copie de libbinder
.
pour parler à /dev/vndbinder
. Comme il s'agit d'un nœud de liaison distinct,
Threadpool n'est pas partagé.
Pour le backend Java, la taille du pool de threads ne peut qu'augmenter déjà commencées):
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 du 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 Rust asynchrone, vous avez besoin de deux pools de threads: binder et Tokio.
Cela signifie que les applications utilisant Rust
async nécessitent des considérations spéciales,
en particulier lorsqu'il s'agit d'utiliser join_thread_pool
. Consultez la section sur
enregistrement de services pour plus d'informations à ce sujet.
Noms réservés
C++, Java et Rust réservent certains noms en tant que mots-clés ou pour des noms spécifiques à un langage
utiliser. Bien qu'AIDL n'applique pas de restrictions basées sur les règles linguistiques, l'utilisation
de champs ou de types correspondant à un nom réservé peuvent entraîner une compilation
pour C++ ou Java. Pour Rust, le champ ou le type est renommé à l'aide du
"identifiant brut" , accessible à l'aide du 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 les liaisons non ergonomiques ou l'é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 tout en restant compatibles avec le protocole ; vous devrez peut-être mettre à jour du code pour continuer à développer, mais tous les programmes déjà créés continueront et l'interopérabilité.
Noms à éviter: