Backend AIDL jest celem generowania kodu pośredniczącego. Korzystając z plików AIDL, zawsze używasz ich w określonym języku z określonym środowiskiem wykonawczym. W zależności od kontekstu należy używać różnych backendów AIDL.
AIDL ma następujące backendy:
Zaplecze | Język | Powierzchnia API | Buduj systemy |
---|---|---|---|
Jawa | Jawa | SDK/SystemApi (stabilny*) | wszystko |
NDK | C++ | libbinder_ndk (stabilny*) | interfejs_pomocy |
CPP | C++ | libbinder (niestabilny) | wszystko |
Rdza | Rdza | libbinder_rs (niestabilny) | interfejs_pomocy |
- Te powierzchnie API są stabilne, ale wiele interfejsów API, na przykład do zarządzania usługami, jest zarezerwowanych do użytku na platformie wewnętrznej i nie jest dostępnych dla aplikacji. Aby uzyskać więcej informacji na temat korzystania z AIDL w aplikacjach, zapoznaj się z dokumentacją dla programistów .
- Backend Rust został wprowadzony w Androidzie 12; backend NDK jest dostępny od Androida 10.
- Skrzynia Rust jest zbudowana na
libbinder_ndk
. APEX używają skrzynki segregatorów w taki sam sposób, jak wszyscy inni po stronie systemu. Część Rust jest pakowana w APEX i wysyłana do środka. To zależy odlibbinder_ndk.so
na partycji systemowej.
Buduj systemy
W zależności od zaplecza istnieją dwa sposoby kompilacji AIDL do kodu pośredniczącego. Aby uzyskać więcej informacji na temat systemów kompilacji, zobacz informacje o module Soong .
Podstawowy system budowy
W dowolnym module java_
cc_
(lub w ich odpowiednikach w Android.mk
) pliki .aidl
można określić jako pliki źródłowe. W tym przypadku używane są backendy AIDL Java/CPP (nie backend NDK), a klasy do korzystania z odpowiednich plików AIDL są automatycznie dodawane do modułu. Opcje takie jak local_include_dirs
, które informują system kompilacji o ścieżce głównej do plików AIDL w tym module, można określić w tych modułach w grupie aidl:
Pamiętaj, że backend Rust jest przeznaczony tylko do użytku z Rust. moduły rust_
są obsługiwane inaczej, ponieważ pliki AIDL nie są określone jako pliki źródłowe. Zamiast tego moduł aidl_interface
tworzy rustlib
o nazwie <aidl_interface name>-rust
, z którą można połączyć. Aby uzyskać więcej informacji, zobacz przykład Rust AIDL .
interfejs_pomocy
Zobacz Stabilny AIDL . Typy używane w tym systemie kompilacji muszą mieć strukturę; to znaczy wyrażone bezpośrednio w AIDL. Oznacza to, że niestandardowe paczki nie mogą być używane.
typy
Kompilator aidl
można uznać za implementację referencyjną dla typów. Kiedy tworzysz interfejs, aidl --lang=<backend> ...
aby zobaczyć wynikowy plik interfejsu. Korzystając z modułu aidl_interface
, możesz wyświetlić dane wyjściowe w out/soong/.intermediates/<path to module>/
.
Typ Java/AIDL | Typ C++ | Typ NDK | Rodzaj rdzy |
---|---|---|---|
logiczna | bool | bool | bool |
bajt | int8_t | int8_t | i8 |
zwęglać | char16_t | char16_t | u16 |
int | int32_t | int32_t | i32 |
długi | int64_t | int64_t | i64 |
pływak | pływak | pływak | f32 |
podwójnie | podwójnie | podwójnie | f64 |
Strunowy | Android::String16 | std::ciąg | Strunowy |
android.os.Parcelable | android::Paczkowalny | Nie dotyczy | Nie dotyczy |
IBinder | android::IBinder | ndk::SpAIBinder | binder::SpIBinder |
T[] | std::wektor<T> | std::wektor<T> | w: &[T] Out: Vec<T> |
bajt[] | std::wektor<uint8_t> | std::wektor<int8_t> 1 | w: &[u8] Out: Vec<u8> |
Lista<T> | std::wektor<T> 2 | std::wektor<T> 3 | W: &[T] 4 Out: Vec<T> |
FileDescriptor | android::base::unique_fd | Nie dotyczy | segregator::paczka::PaczkaPlikDeskryptor |
ParcelFileDescriptor | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | segregator::paczka::PaczkaPlikDeskryptor |
typ interfejsu (T) | android::sp<T> | std::shared_ptr<T> | spoiwo::Mocne |
typ paczkowany (T) | T | T | T |
typ złącza (T) 5 | T | T | T |
T[N] 6 | std::tablica<T, N> | std::tablica<T, N> | [T; N] |
1. W Androidzie 12 lub nowszym tablice bajtowe używają uint8_t zamiast int8_t ze względu na kompatybilność.
2. Backend C++ obsługuje List<T>
, gdzie T
jest jednym z String
, IBinder
, ParcelFileDescriptor
lub parcelable. W Androidzie 13 lub nowszym T
może być dowolnym typem nieprymitywnym (w tym typami interfejsów) z wyjątkiem tablic. AOSP zaleca używanie typów tablicowych, takich jak T[]
, ponieważ działają one we wszystkich backendach.
3. Backend NDK obsługuje List<T>
, gdzie T
jest jednym z String
, ParcelFileDescriptor
lub parcelable. W systemie Android 13 lub nowszym T
może być dowolnym typem nieprymitywnym z wyjątkiem tablic.
4. Typy są przekazywane w różny sposób dla kodu Rust w zależności od tego, czy są one wejściowe (argument), czy wyjściowe (zwrócona wartość).
5. Typy Unii są obsługiwane w Androidzie 12 i nowszych.
6. W systemie Android 13 lub nowszym obsługiwane są tablice o stałym rozmiarze. Tablice o stałym rozmiarze mogą mieć wiele wymiarów (np. int[3][4]
). W zapleczu Java tablice o stałym rozmiarze są reprezentowane jako typy tablic.
Kierunkowość (wejście/wyjście/wyjście)
Podczas określania typów argumentów funkcji można określić je jako in
, out
lub inout
. To kontroluje, w którym kierunku informacje są przekazywane dla połączenia IPC. in
jest domyślnym kierunkiem i wskazuje, że dane są przekazywane od dzwoniącego do odbiorcy. out
oznacza, że dane są przekazywane od odbiorcy do dzwoniącego. inout
to połączenie obu tych elementów. Jednak zespół systemu Android zaleca unikanie używania specyfikatora argumentów inout
. Jeśli użyjesz inout
z wersjonowanym interfejsem i starszym odbiorcą, dodatkowe pola, które są obecne tylko w wywołującym, zostaną zresetowane do wartości domyślnych. W odniesieniu do Rust, normalny typ inout
otrzymuje &mut Vec<T>
, a typ list inout
otrzymuje &mut Vec<T>
.
UTF8/UTF16
Dzięki backendowi CPP możesz wybrać, czy ciągi mają być utf-8, czy utf-16. Zadeklaruj ciągi znaków jako @utf8InCpp String
w AIDL, aby automatycznie przekonwertować je na utf-8. Backendy NDK i Rust zawsze używają łańcuchów utf-8. Aby uzyskać więcej informacji na temat adnotacji utf8InCpp
, zobacz Adnotacje w AIDL .
Możliwość zerowania
Możesz dodawać adnotacje do typów, które mogą być puste w zapleczu Java za pomocą @nullable
, aby ujawnić wartości puste dla zaplecza CPP i NDK. W zapleczu Rusta te typy @nullable
są widoczne jako Option<T>
. Serwery natywne domyślnie odrzucają wartości null. Jedynymi wyjątkami są typy interface
i IBinder
, które zawsze mogą mieć wartość null dla odczytów NDK i zapisów CPP/NDK. Aby uzyskać więcej informacji na temat adnotacji nullable
wartość null, zobacz Adnotacje w AIDL .
Paczki niestandardowe
W backendach C++ i Java w głównym systemie kompilacji możesz zadeklarować parcelable, który jest implementowany ręcznie w docelowym backendzie (w C++ lub w Javie).
package my.package;
parcelable Foo;
lub z deklaracją nagłówka C++:
package my.package;
parcelable Foo cpp_header "my/package/Foo.h";
Następnie możesz użyć tego parcelable jako typu w plikach AIDL, ale nie zostanie on wygenerowany przez AIDL.
Rust nie obsługuje niestandardowych paczek.
Wartości domyślne
Strukturyzowane parcelables mogą deklarować wartości domyślne dla poszczególnych pól dla prymitywów, String
i tablic tego typu.
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
W zapleczu Java, gdy brakuje wartości domyślnych, pola są inicjowane jako wartości zerowe dla typów pierwotnych i null
dla typów innych niż podstawowe.
W innych zapleczach pola są inicjowane domyślnymi wartościami inicjowanymi, gdy wartości domyślne nie są zdefiniowane. Na przykład w zapleczu C++ pola String
są inicjowane jako pusty ciąg, a pola List<T>
są inicjowane jako pusty vector<T>
. Pola @nullable
są inicjowane jako pola o wartości null.
Obsługa błędów
System operacyjny Android udostępnia wbudowane typy błędów, których usługi mogą używać podczas zgłaszania błędów. Są one używane przez program wiążący i mogą być używane przez dowolne usługi implementujące interfejs programu wiążącego. Ich użycie jest dobrze udokumentowane w definicji AIDL i nie wymagają żadnego statusu zdefiniowanego przez użytkownika ani typu zwracanego.
Parametry wyjściowe z błędami
Gdy funkcja AIDL zgłasza błąd, funkcja może nie zainicjować ani zmodyfikować parametrów wyjściowych. W szczególności parametry wyjściowe mogą zostać zmodyfikowane, jeśli błąd wystąpi podczas rozpakowywania, a nie podczas przetwarzania samej transakcji. Ogólnie rzecz biorąc, podczas uzyskiwania błędu z funkcji AIDL wszystkie parametry inout
i out
, a także wartość zwracana (która działa jak parametr out
w niektórych backendach) powinny być traktowane jako w stanie nieokreślonym.
Których wartości błędów użyć
Wiele wbudowanych wartości błędów może być używanych w dowolnych interfejsach AIDL, ale niektóre są traktowane w specjalny sposób. Na przykład EX_UNSUPPORTED_OPERATION
i EX_ILLEGAL_ARGUMENT
można użyć, gdy opisują stan błędu, ale EX_TRANSACTION_FAILED
nie wolno używać, ponieważ jest traktowany specjalnie przez podstawową infrastrukturę. Sprawdź definicje specyficzne dla zaplecza, aby uzyskać więcej informacji na temat tych wbudowanych wartości.
Jeśli interfejs AIDL wymaga dodatkowych wartości błędów, które nie są objęte wbudowanymi typami błędów, może użyć specjalnego wbudowanego błędu specyficznego dla usługi, który umożliwia włączenie wartości błędu specyficznej dla usługi zdefiniowanej przez użytkownika . Te błędy specyficzne dla usługi są zwykle definiowane w interfejsie AIDL jako enum
typu const int
lub int
i nie są analizowane przez program wiążący.
W Javie błędy są mapowane na wyjątki, takie jak android.os.RemoteException
. W przypadku wyjątków specyficznych dla usług Java używa wyjątku android.os.ServiceSpecificException
wraz z błędem zdefiniowanym przez użytkownika.
Natywny kod w Androidzie nie używa wyjątków. Zaplecze CPP używa android::binder::Status
. Zaplecze NDK używa ndk::ScopedAStatus
. Każda metoda wygenerowana przez AIDL zwraca jeden z nich, reprezentujący status metody. Backend Rust używa tych samych wartości kodów wyjątków co NDK, ale konwertuje je na natywne błędy Rust ( StatusCode
, ExceptionCode
) przed dostarczeniem ich do użytkownika. W przypadku błędów specyficznych dla usługi zwrócony Status
lub ScopedAStatus
używa EX_SERVICE_SPECIFIC
wraz z błędem zdefiniowanym przez użytkownika.
Wbudowane typy błędów można znaleźć w następujących plikach:
Zaplecze | Definicja |
---|---|
Jawa | android/os/Parcel.java |
CPP | binder/Status.h |
NDK | android/binder_status.h |
Rdza | android/binder_status.h |
Korzystanie z różnych backendów
Te instrukcje dotyczą kodu platformy Android. Te przykłady używają zdefiniowanego typu, my.package.IFoo
. Aby uzyskać instrukcje dotyczące korzystania z backendu Rust, zobacz przykład Rust AIDL na stronie Android Rust Patterns .
Importowanie typów
Niezależnie od tego, czy zdefiniowany typ jest interfejsem, pakietowalnym czy unią, możesz go zaimportować w Javie:
import my.package.IFoo;
Lub w zapleczu CPP:
#include <my/package/IFoo.h>
Lub w zapleczu NDK (zwróć uwagę na dodatkową aidl
nazw pomocy):
#include <aidl/my/package/IFoo.h>
Lub w zapleczu Rusta:
use my_package::aidl::my::package::IFoo;
Chociaż można zaimportować typ zagnieżdżony w Javie, w backendach CPP/NDK należy dołączyć nagłówek dla jego typu głównego. Na przykład podczas importowania zagnieżdżonego typu Bar
zdefiniowanego w my/package/IFoo.aidl
( IFoo
jest typem głównym pliku) należy dołączyć <my/package/IFoo.h>
dla backendu CPP (lub <aidl/my/package/IFoo.h>
dla zaplecza NDK).
Wdrażanie usług
Aby zaimplementować usługę, musisz dziedziczyć z natywnej klasy pośredniczącej. Ta klasa odczytuje polecenia ze sterownika bindera i wykonuje zaimplementowane metody. Wyobraź sobie, że masz taki plik AIDL:
package my.package;
interface IFoo {
int doFoo();
}
W Javie musisz rozszerzyć tę klasę:
import my.package.IFoo;
public class MyFoo extends IFoo.Stub {
@Override
int doFoo() { ... }
}
W zapleczu CPP:
#include <my/package/BnFoo.h>
class MyFoo : public my::package::BnFoo {
android::binder::Status doFoo(int32_t* out) override;
}
W zapleczu NDK (zwróć uwagę na dodatkową aidl
nazw pomocy):
#include <aidl/my/package/BnFoo.h>
class MyFoo : public aidl::my::package::BnFoo {
ndk::ScopedAStatus doFoo(int32_t* out) override;
}
W zapleczu Rusta:
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(())
}
}
Rejestracja i uzyskiwanie usług
Usługi na platformie Android są zwykle rejestrowane w procesie servicemanager
. Oprócz poniższych interfejsów API niektóre interfejsy API sprawdzają usługę (co oznacza, że zwracają ją natychmiast, jeśli usługa jest niedostępna). Sprawdź odpowiedni interfejs servicemanager
, aby uzyskać szczegółowe informacje. Te operacje można wykonać tylko podczas kompilacji na platformę Android.
w Javie:
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"));
W zapleczu 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"));
W zapleczu NDK (zwróć uwagę na dodatkową aidl
nazw pomocy):
#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")));
W zapleczu Rusta:
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()
}
Łączenie ze śmiercią
Możesz poprosić o powiadomienie, gdy usługa hostująca segregator przestanie działać. Może to pomóc w uniknięciu wycieków proxy wywołania zwrotnego lub pomóc w naprawie błędów. Wykonaj te wywołania na obiektach proxy programu wiążącego.
- W Javie użyj
android.os.IBinder::linkToDeath
. - W zapleczu CPP użyj
android::IBinder::linkToDeath
. - W zapleczu NDK użyj
AIBinder_linkToDeath
. - W backendzie Rust utwórz obiekt
DeathRecipient
, a następnie wywołajmy_binder.link_to_death(&mut my_death_recipient)
. Zauważ, że ponieważDeathRecipient
jest właścicielem wywołania zwrotnego, musisz utrzymywać ten obiekt aktywny tak długo, jak chcesz otrzymywać powiadomienia.
Informacje o dzwoniącym
Podczas odbierania wywołania spinacza jądra informacje o dzwoniącym są dostępne w kilku interfejsach API. PID (lub identyfikator procesu) odnosi się do identyfikatora procesu systemu Linux procesu, który wysyła transakcję. Identyfikator UID (lub identyfikator użytkownika) odnosi się do identyfikatora użytkownika systemu Linux. Podczas odbierania wywołania jednokierunkowego wywołujący identyfikator PID wynosi 0. Gdy znajdują się poza kontekstem transakcji spinacza, te funkcje zwracają identyfikatory PID i UID bieżącego procesu.
W zapleczu Javy:
... = Binder.getCallingPid();
... = Binder.getCallingUid();
W zapleczu CPP:
... = IPCThreadState::self()->getCallingPid();
... = IPCThreadState::self()->getCallingUid();
W backendzie NDK:
... = AIBinder_getCallingPid();
... = AIBinder_getCallingUid();
W zapleczu Rust, podczas implementacji interfejsu, określ następujące elementy (zamiast zezwalania na ustawienie domyślne):
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
Raporty o błędach i debugowanie API dla usług
Kiedy raporty o błędach są uruchamiane (na przykład za pomocą adb bugreport
), zbierają informacje z całego systemu, aby pomóc w debugowaniu różnych problemów. W przypadku usług AIDL raporty o błędach używają binarnych dumpsys
we wszystkich usługach zarejestrowanych przez menedżera usług, aby zrzucić ich informacje do raportu o błędzie. Możesz także użyć dumpsys
w wierszu poleceń, aby uzyskać informacje z usługi z dumpsys SERVICE [ARGS]
. W backendach C++ i Java możesz kontrolować kolejność, w jakiej usługi są zrzucane, używając dodatkowych argumentów do addService
. Możesz także użyć dumpsys --pid SERVICE
, aby uzyskać PID usługi podczas debugowania.
Aby dodać niestandardowe dane wyjściowe do swojej usługi, możesz zastąpić metodę dump
w swoim obiekcie serwera, tak jak implementujesz dowolną inną metodę IPC zdefiniowaną w pliku AIDL. Robiąc to, powinieneś ograniczyć zrzut do uprawnień aplikacji android.permission.DUMP
lub ograniczyć zrzut do określonych identyfikatorów UID.
W zapleczu Javy:
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {...}
W zapleczu CPP:
status_t dump(int, const android::android::Vector<android::String16>&) override;
W backendzie NDK:
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
W zapleczu Rust, podczas implementacji interfejsu, określ następujące elementy (zamiast zezwalania na ustawienie domyślne):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
Dynamiczne pobieranie deskryptora interfejsu
Deskryptor interfejsu identyfikuje typ interfejsu. Jest to przydatne podczas debugowania lub gdy masz nieznany program wiążący.
W Javie możesz uzyskać deskryptor interfejsu z kodem takim jak:
service = /* get ahold of service object */
... = service.asBinder().getInterfaceDescriptor();
W zapleczu CPP:
service = /* get ahold of service object */
... = IInterface::asBinder(service)->getInterfaceDescriptor();
Backendy NDK i Rust nie obsługują tej funkcjonalności.
Statyczne pobieranie deskryptora interfejsu
Czasami (na przykład podczas rejestracji usług @VintfStability
) musisz wiedzieć, jaki jest statyczny deskryptor interfejsu. W Javie możesz uzyskać deskryptor, dodając kod, taki jak:
import my.package.IFoo;
... IFoo.DESCRIPTOR
W zapleczu CPP:
#include <my/package/BnFoo.h>
... my::package::BnFoo::descriptor
W zapleczu NDK (zwróć uwagę na dodatkową aidl
nazw pomocy):
#include <aidl/my/package/BnFoo.h>
... aidl::my::package::BnFoo::descriptor
W zapleczu Rusta:
aidl::my::package::BnFoo::get_descriptor()
Zakres wyliczeniowy
W natywnych backendach możesz iterować nad możliwymi wartościami, które może przyjąć enum. Ze względu na rozmiar kodu nie jest to obecnie obsługiwane w Javie.
Dla wyliczenia MyEnum
zdefiniowanego w AIDL iteracja jest zapewniona w następujący sposób.
W zapleczu CPP:
::android::enum_range<MyEnum>()
W backendzie NDK:
::ndk::enum_range<MyEnum>()
W zapleczu Rusta:
MyEnum::enum_range()
Zarządzanie wątkami
Każda instancja libbinder
w procesie utrzymuje jedną pulę wątków. W większości przypadków użycia powinna to być dokładnie jedna pula wątków, współdzielona przez wszystkie backendy. Jedynym wyjątkiem jest sytuacja, w której kod dostawcy może załadować inną kopię libbinder
, aby porozmawiać z /dev/vndbinder
. Ponieważ znajduje się to w osobnym węźle wiążącym, pula wątków nie jest udostępniana.
W przypadku zaplecza Java pula wątków może tylko zwiększać rozmiar (ponieważ jest już uruchomiona):
BinderInternal.setMaxThreads(<new larger value>);
W przypadku zaplecza CPP dostępne są następujące operacje:
// 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();
Podobnie w backendzie NDK:
bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
ABinderProcess_startThreadPool();
ABinderProcess_joinThreadPool();
W zapleczu Rusta:
binder::ProcessState::start_thread_pool();
binder::add_service(“myservice”, my_service_binder).expect(“Failed to register service?”);
binder::ProcessState::join_thread_pool();
Nazwy zastrzeżone
C++, Java i Rust rezerwują niektóre nazwy jako słowa kluczowe lub do użytku specyficznego dla języka. Chociaż AIDL nie wymusza ograniczeń opartych na regułach językowych, użycie nazw pól lub typów, które pasują do zastrzeżonej nazwy, może spowodować błąd kompilacji dla C++ lub Java. W Rust nazwa pola lub typu jest zmieniana przy użyciu składni „surowego identyfikatora”, dostępnej za pomocą przedrostka r#
.
Zalecamy unikanie używania zastrzeżonych nazw w definicjach AIDL, jeśli to możliwe, aby uniknąć nieergonomicznych powiązań lub całkowitego niepowodzenia kompilacji.
Jeśli masz już zarezerwowane nazwy w definicjach AIDL, możesz bezpiecznie zmieniać nazwy pól, zachowując przy tym zgodność z protokołami; może być konieczne zaktualizowanie kodu, aby kontynuować budowanie, ale wszelkie już utworzone programy będą nadal współdziałać.
Nazwy, których należy unikać: * Słowa kluczowe C++ * Słowa kluczowe Java * Słowa kluczowe Rust