AIDL Arka Uçları

AIDL arka ucu, saplama kodu üretimi için bir hedeftir. AIDL dosyalarını kullanırken, bunları her zaman belirli bir çalışma zamanı ile belirli bir dilde kullanırsınız. Bağlama bağlı olarak, farklı AIDL arka uçları kullanmalısınız.

AIDL aşağıdaki arka uçlara sahiptir:

arka uç Dilim API yüzeyi inşa sistemleri
Java Java SDK/SystemApi (kararlı*) Tümü
NDK C++ libbinder_ndk (kararlı*) aidl_interface
CPP C++ libbinder (kararsız) Tümü
Pas Pas libbinder_rs (kararsız) aidl_interface
  • Bu API yüzeyleri kararlıdır, ancak hizmet yönetimi için olanlar gibi API'lerin çoğu dahili platform kullanımı için ayrılmıştır ve uygulamalar tarafından kullanılamaz. AIDL'nin uygulamalarda nasıl kullanılacağı hakkında daha fazla bilgi için geliştirici belgelerine bakın.
  • Rust arka ucu Android 12'de tanıtıldı; NDK arka ucu, Android 10'dan itibaren kullanıma sunulmuştur.
  • Rust kasası, libbinder_ndk üzerine inşa edilmiştir. APEX'ler, bağlayıcı kasayı sistem tarafındaki diğer herkesle aynı şekilde kullanır. Pas kısmı bir APEX'te paketlenir ve bunun içinde gönderilir. Sistem bölümündeki libbinder_ndk.so bağlıdır.

inşa sistemleri

Arka uca bağlı olarak, AIDL'yi saplama kodunda derlemenin iki yolu vardır. Yapı sistemleri hakkında daha fazla ayrıntı için Soong Modül Referansına bakın.

Çekirdek yapı sistemi

Herhangi bir cc_ veya java_ Android.bp modülünde (veya Android.mk eşdeğerlerinde), .aidl dosyaları kaynak dosyalar olarak belirtilebilir. Bu durumda, AIDL'nin Java/CPP arka uçları kullanılır (NDK arka ucu değil) ve ilgili AIDL dosyalarını kullanacak sınıflar modüle otomatik olarak eklenir. Derleme sistemine o modüldeki AIDL dosyalarının kök yolunu söyleyen local_include_dirs gibi seçenekler, bu modüllerde bir aidl: grubu altında belirtilebilir. Rust arka ucunun yalnızca Rust ile kullanım için olduğunu unutmayın. rust_ modülleri, AIDL dosyalarının kaynak dosyalar olarak belirtilmediği için farklı şekilde işlenir. Bunun yerine, aidl_interface modülü, <aidl_interface name>-rust adında, bağlanabilecek bir rustlib üretir. Daha fazla ayrıntı için Rust AIDL örneğine bakın.

aidl_interface

Bkz. Kararlı AIDL . Bu yapı sistemiyle kullanılan türler yapılandırılmalıdır; yani, doğrudan AIDL'de ifade edilir. Bu, özel parsellenebilirlerin kullanılamayacağı anlamına gelir.

Türler

aidl derleyicisini türler için bir referans uygulaması olarak düşünebilirsiniz. Bir arayüz oluşturduğunuzda, ortaya çıkan arayüz dosyasını görmek için aidl --lang=<backend> ... . aidl_interface modülünü kullandığınızda, çıktıyı out/soong/.intermediates/<path to module>/ içinde görüntüleyebilirsiniz.

Java/AIDL Türü C++ Türü NDK Tipi Pas Tipi
boole bool bool bool
bayt int8_t int8_t i8
karakter karakter16_t karakter16_t u16
int int32_t int32_t i32
uzun int64_t int64_t i64
batmadan yüzmek batmadan yüzmek batmadan yüzmek f32
çift çift çift f64
Sicim android::String16 standart::dize Sicim
android.os.Parcelable android::Parsellenebilir Yok Yok
IBinder android::IBinder ndk::SpAIBinder bağlayıcı::SpIBinder
T[] std::vektör<T> std::vektör<T> İçinde: &T
Çıkış: Vec<T>
bayt[] std::vector<uint8_t> std::vector<int8_t> 1 İçinde: &[u8]
Çıkış: Vec<u8>
Liste<T> std::vektör<T> 2 std::vector<T> 3 İçinde: &[T] 4
Çıkış: Vec<T>
Dosya Tanımlayıcı android::base::unique_fd Yok bağlayıcı::parsel::ParcelFileDescriptor
ParselDosya Tanımlayıcısı android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor bağlayıcı::parsel::ParcelFileDescriptor
arayüz tipi (T) android::sp<T> std::shared_ptr<T> bağlayıcı::Güçlü
parçalanabilir tip (T) T T T
birleşim tipi (T) 5 T T T
T[N] 6 std::dizi<T, N> std::dizi<T, N> [T; N]

1. Android 12 veya sonraki sürümlerde, bayt dizileri uyumluluk nedenleriyle int8_t yerine uint8_t kullanır.

2. C++ arka ucu List<T> 'yi destekler; burada T , String , IBinder , ParcelFileDescriptor veya ayrıştırılabilir öğelerden biridir. Android T'de (AOSP deneysel) veya daha yüksek sürümlerde, T , diziler dışında herhangi bir ilkel olmayan tür (arayüz türleri dahil) olabilir. AOSP, tüm arka uçlarda çalıştıkları için T[] gibi dizi türlerini kullanmanızı önerir.

3. NDK arka ucu List<T> 'yi destekler; burada T , String , ParcelFileDescriptor veya ayrıştırılabilirlerden biridir. Android T'de (AOSP deneysel) veya daha yüksek sürümlerde T , diziler dışında herhangi bir ilkel olmayan tür olabilir.

4. Türler, girdi (argüman) veya çıktı (döndürülen değer) olmalarına bağlı olarak, Rust kodu için farklı şekilde iletilir.

5. Birlik türleri, Android 12 ve sonraki sürümlerde desteklenir.

6. Android T'de (AOSP deneysel) veya daha yüksek sürümlerde, sabit boyutlu diziler desteklenir. Sabit boyutlu diziler birden çok boyuta sahip olabilir (örneğin int[3][4] ). Java arka ucunda, sabit boyutlu diziler dizi türleri olarak temsil edilir.

Yönlülük (giriş/çıkış/giriş)

Fonksiyonlara argüman türlerini belirtirken, bunları in , out veya inout olarak belirtebilirsiniz. Bu, bir IPC çağrısı için hangi yön bilgisinin iletildiğini kontrol eder. in varsayılan yöndür ve arayandan aranana veri aktarıldığını gösterir. out , verilerin aranandan arayana iletildiği anlamına gelir. inout bunların ikisinin birleşimidir. Ancak Android ekibi, inout bağımsız değişken belirtecini kullanmaktan kaçınmanızı önerir. inout sürümlü bir arabirim ve daha eski bir aranan ile kullanırsanız, yalnızca arayanda bulunan ek alanlar varsayılan değerlerine sıfırlanır. Rust ile ilgili olarak, normal bir giriş tipi &mut inout &mut Vec<T> alır ve bir liste giriş tipi &mut Vec<T> inout .

UTF8/UTF16

CPP arka ucu ile dizelerin utf-8 veya utf-16 olmasını seçebilirsiniz. Dizeleri otomatik olarak utf-8'e dönüştürmek için AIDL'de @utf8InCpp String olarak bildirin. NDK ve Rust arka uçları her zaman utf-8 dizeleri kullanır. utf8InCpp ek açıklaması hakkında daha fazla bilgi için bkz . AIDL'deki Açıklamalar .

Null yapılabilirlik

CPP ve NDK arka uçlarına boş değerler göstermek için Java arka ucunda boş @nullable ile açıklama ekleyebilirsiniz. Rust arka ucunda bu @nullable türleri Option<T> olarak gösterilir. Yerel sunucular varsayılan olarak boş değerleri reddeder. Bunun tek istisnası, NDK okumaları ve CPP/NDK yazmaları için her zaman boş olabilen interface ve IBinder türleridir. nullable yapılabilir açıklama hakkında daha fazla bilgi için bkz . AIDL'deki Açıklamalar .

Özel Parsellenebilirler

Çekirdek yapı sistemindeki C++ ve Java arka uçlarında, hedef arka uçta (C++ veya Java'da) manuel olarak uygulanan bir ayrıştırılabilir bildirebilirsiniz.

    package my.package;
    parcelable Foo;

veya C++ başlık bildirimi ile:

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

Daha sonra bu ayrıştırılabiliri AIDL dosyalarında bir tür olarak kullanabilirsiniz, ancak AIDL tarafından oluşturulmaz.

Rust, özel parsellenebilirleri desteklemez.

Varsayılan değerler

Yapılandırılmış ayrıştırılabilirler, temel öğeler, String s ve bu tür diziler için alan başına varsayılan değerler bildirebilir.

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

Java arka ucunda, varsayılan değerler eksik olduğunda, alanlar ilkel türler için sıfır değerleri ve ilkel olmayan türler için null olarak başlatılır.

Diğer arka uçlarda, varsayılan değerler tanımlanmadığında alanlar varsayılan başlatılmış değerlerle başlatılır. Örneğin, C++ arka ucunda, String alanları boş bir dize olarak başlatılır ve List<T> alanları boş bir vector<T> olarak başlatılır. @nullable alanları boş değer alanları olarak başlatılır.

Hata yönetimi

Android işletim sistemi, hizmetlerin hataları bildirirken kullanması için yerleşik hata türleri sağlar. Bunlar, bağlayıcı tarafından kullanılır ve bir bağlayıcı arabirimi uygulayan herhangi bir hizmet tarafından kullanılabilir. Kullanımları AIDL tanımında iyi belgelenmiştir ve herhangi bir kullanıcı tanımlı durum veya dönüş türü gerektirmezler.

Bir AIDL işlevi bir hata bildirdiğinde, işlev çıkış parametrelerini başlatmayabilir veya değiştirmeyebilir. Spesifik olarak, çıktı parametreleri, işlemin kendisinin işlenmesi sırasında meydana gelmesinin aksine, ayrıştırma sırasında hata meydana gelirse değiştirilebilir. Genel olarak, bir inout işlevinden bir hata alınırken, tüm giriş ve out parametrelerinin yanı sıra (bazı arka uçlarda out parametresi gibi davranan) dönüş değerinin belirsiz durumda olduğu düşünülmelidir.

AIDL arabirimi, yerleşik hata türleri tarafından kapsanmayan ek hata değerleri gerektiriyorsa, kullanıcı tarafından tanımlanan hizmete özgü bir hata değerinin eklenmesine izin veren hizmete özgü özel yerleşik hatayı kullanabilirler. . Bu hizmete özgü hatalar tipik olarak AIDL arabiriminde const int veya int -backed enum olarak tanımlanır ve bağlayıcı tarafından ayrıştırılmaz.

Java'da hatalar, android.os.RemoteException gibi istisnalarla eşlenir. Hizmete özel istisnalar için Java, kullanıcı tanımlı hatayla birlikte android.os.ServiceSpecificException kullanır.

Android'deki yerel kod istisnaları kullanmaz. CPP arka ucu android::binder::Status kullanır. NDK arka ucu, ndk::ScopedAStatus kullanır. AIDL tarafından oluşturulan her yöntem, yöntemin durumunu temsil eden bunlardan birini döndürür. Rust arka ucu, NDK ile aynı istisna kodu değerlerini kullanır, ancak bunları kullanıcıya teslim etmeden önce yerel Rust hatalarına ( StatusCode , ExceptionCode ) dönüştürür. Hizmete özgü hatalar için, döndürülen Status veya EX_SERVICE_SPECIFIC , kullanıcı tanımlı hatayla birlikte ScopedAStatus kullanır.

Yerleşik hata türleri aşağıdaki dosyalarda bulunabilir:

arka uç Tanım
Java android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h
Pas android/binder_status.h

Çeşitli arka uçları kullanma

Bu talimatlar Android platform koduna özeldir. Bu örnekler, tanımlanmış bir tür kullanır, my.package.IFoo . Rust arka ucunun nasıl kullanılacağına ilişkin talimatlar için Android Rust Patterns sayfasındaki Rust AIDL örneğine bakın.

Türleri içe aktarma

Tanımlanan tür bir arabirim, bölünebilir veya birleşim olsun, onu Java'da içe aktarabilirsiniz:

    import my.package.IFoo;

Veya CPP arka ucunda:

    #include <my/package/IFoo.h>

Veya NDK arka ucunda (fazladan aidl ad alanına dikkat edin):

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

Veya Rust arka ucunda:

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

Java'da yuvalanmış bir türü içe aktarabilseniz de, CPP/NDK arka uçlarında kök türünün başlığını eklemelisiniz. Örneğin, my/package/IFoo.aidl ( IFoo dosyanın kök türüdür) tanımlanan yuvalanmış bir Bar türünü içe aktarırken, CPP arka ucu (veya <aidl/my/package/IFoo.h> ) için <my/package/IFoo.h> eklemeniz gerekir. <aidl/my/package/IFoo.h> NDK arka ucu için).

Uygulama hizmetleri

Bir hizmeti uygulamak için yerel saplama sınıfından devralmalısınız. Bu sınıf, bağlayıcı sürücüsünden komutları okur ve uyguladığınız yöntemleri yürütür. Bunun gibi bir AIDL dosyanız olduğunu hayal edin:

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

Java'da, bu sınıftan genişletmeniz gerekir:

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

CPP arka ucunda:

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

NDK arka ucunda (fazladan aidl ad alanına dikkat edin):

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

Rust arka ucunda:

    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(())
        }
    }

Kayıt olma ve hizmet alma

Android platformundaki hizmetler genellikle servicemanager yöneticisi sürecine kaydedilir. Aşağıdaki API'lere ek olarak, bazı API'ler hizmeti kontrol eder (yani hizmet mevcut değilse hemen geri dönerler). Kesin ayrıntılar için ilgili servicemanager yöneticisi arayüzünü kontrol edin. Bu işlemler yalnızca platform Android'e karşı derlenirken yapılabilir.

Java'da:

    import android.os.ServiceManager;
    // registering
    ServiceManager.addService("service-name", myService);
    // getting
    myService = IFoo.Stub.asInterface(ServiceManager.getService("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"));

CPP arka ucunda:

    #include <binder/IServiceManager.h>
    // registering
    defaultServiceManager()->addService(String16("service-name"), myService);
    // getting
    status_t err = getService<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"));

NDK arka ucunda (fazladan aidl ad alanına dikkat edin):

    #include <android/binder_manager.h>
    // registering
    status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // getting
    myService = IFoo::fromBinder(SpAIBinder(AServiceManager_getService("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")));

Rust arka ucunda:

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()
}

Bir bağlayıcı barındıran bir hizmet öldüğünde bildirim almayı talep edebilirsiniz. Bu, geri arama proxy'lerinin sızdırılmasını önlemeye veya hata gidermeye yardımcı olabilir. Bu çağrıları bağlayıcı proxy nesnelerinde yapın.

  • Java'da android.os.IBinder::linkToDeath kullanın.
  • CPP arka android::IBinder::linkToDeath .
  • NDK arka AIBinder_linkToDeath kullanın.
  • Rust arka ucunda bir DeathRecipient nesnesi oluşturun ve ardından my_binder.link_to_death(&mut my_death_recipient) . Geri aramanın sahibi DeathRecipient olduğundan, bildirimleri almak istediğiniz sürece bu nesneyi canlı tutmanız gerektiğini unutmayın.

Hizmetler için hata raporları ve hata ayıklama API'si

Hata raporları çalıştığında (örneğin, adb bugreport ile), çeşitli sorunların giderilmesine yardımcı olmak için sistemin her yerinden bilgi toplarlar. AIDL hizmetleri için, hata raporları, bilgilerini hata raporuna dökmek için hizmet yöneticisine kayıtlı tüm hizmetlerde ikili dumpsys kullanır. Ayrıca dumpsys SERVICE [ARGS] içeren bir hizmetten bilgi almak için komut dumpsys kullanabilirsiniz. C++ ve Java arka uçlarında, addService için ek bağımsız değişkenler kullanarak hizmetlerin atılma sırasını kontrol edebilirsiniz. Hata ayıklama sırasında bir hizmetin PID'sini almak için dumpsys --pid SERVICE da kullanabilirsiniz.

Hizmetinize özel çıktı eklemek için, bir AIDL dosyasında tanımlanan diğer herhangi bir IPC yöntemini uygularken, sunucu nesnenizdeki dump yöntemini geçersiz kılabilirsiniz. Bunu yaparken, boşaltmayı android.permission.DUMP uygulama izniyle kısıtlamalı veya boşaltmayı belirli UID'lerle sınırlandırmalısınız.

Java arka ucunda:

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

CPP arka ucunda:

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

NDK arka ucunda:

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

Rust arka ucunda, arabirimi uygularken aşağıdakileri belirtin (varsayılan olmasına izin vermek yerine):

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

Dinamik olarak arayüz tanımlayıcısı alma

Arabirim tanımlayıcısı, bir arabirimin türünü tanımlar. Bu, hata ayıklarken veya bilinmeyen bir bağlayıcınız olduğunda kullanışlıdır.

Java'da, arayüz tanımlayıcısını aşağıdaki gibi kodlarla alabilirsiniz:

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

CPP arka ucunda:

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

NDK ve Rust arka uçları bu işlevi desteklemez.

Statik olarak arayüz tanımlayıcısı alma

Bazen (örneğin @VintfStability servislerini kaydederken), arayüz tanımlayıcısının statik olarak ne olduğunu bilmeniz gerekir. Java'da, aşağıdaki gibi bir kod ekleyerek tanımlayıcıyı alabilirsiniz:

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

CPP arka ucunda:

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

NDK arka ucunda (fazladan aidl ad alanına dikkat edin):

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

Rust arka ucunda:

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

Numaralandırma Aralığı

Yerel arka uçlarda, bir numaralandırmanın alabileceği olası değerleri yineleyebilirsiniz. Kod boyutuyla ilgili hususlar nedeniyle bu, şu anda Java'da desteklenmemektedir.

MyEnum tanımlanan bir numaralandırma MyEnum için yineleme aşağıdaki gibi sağlanır.

CPP arka ucunda:

    ::android::enum_range<MyEnum>()

NDK arka ucunda:

   ::ndk::enum_range<MyEnum>()

Rust arka ucunda:

    MyEnum::enum_range()

Konu yönetimi

Bir işlemdeki her libbinder örneği, bir iş parçacığı havuzunu korur. Çoğu kullanım durumu için bu, tüm arka uçlar arasında paylaşılan tam olarak bir iş parçacığı havuzu olmalıdır. Bunun tek istisnası, satıcı kodunun /dev/vndbinder ile konuşmak için başka bir libbinder kopyasını yükleyebilmesidir. Bu, ayrı bir bağlayıcı düğümde olduğundan, iş parçacığı havuzu paylaşılmaz.

Java arka ucu için, iş parçacığı havuzu yalnızca boyut olarak artabilir (zaten başlatıldığından beri):

    BinderInternal.setMaxThreads(<new larger value>);

CPP arka ucu için aşağıdaki işlemler kullanılabilir:

    // 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();

Benzer şekilde, NDK arka ucunda:

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

Rust arka ucunda:

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