AIDL arka uçları

AIDL arka ucu, taslak kod oluşturma hedefidir. AIDL dosyalarını her zaman belirli bir dil ve belirli bir çalışma zamanında kullanırsınız. Bağlama bağlı olarak farklı AIDL arka uçları kullanmanız gerekir.

Aşağıdaki tabloda API yüzeyinin kararlılığı, kodun system.img libbinder.so ikilisinden bağımsız olarak yayınlanabileceği şekilde bu API yüzeyine göre derlenebilme özelliğini ifade eder.

AIDL'nin aşağıdaki arka uçları vardır:

Arka uç Dil API yüzeyi Derleme sistemleri
Java Java SDK/SystemApi (stabil*) tümü
NDK C++ libbinder_ndk (stable*) aidl_interface
ABM C++ libbinder (dengeli değil) tümü
Rust Rust libbinder_rs (stable*) aidl_interface
  • Bu API yüzeyleri kararlıdır ancak hizmet yönetimi için olanlar gibi API'lerin çoğu, şirket içi 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 dokümanlarını inceleyin.
  • Rust arka uç, Android 12'de kullanıma sunulmuştur. NDK arka ucu ise Android 10'dan beri kullanılabilir.
  • Rust paketi, libbinder_ndk üzerine inşa edildiğinden kararlı ve taşınabilirdir. APEX'ler, bağlayıcı paketini sistem tarafındaki diğer kullanıcılarla aynı şekilde kullanır. Rust kısmı bir APEX'e paketlenir ve içinde gönderilir. Bu, sistem bölümündeki libbinder_ndk.so'e bağlıdır.

Derleme sistemleri

Arka uça bağlı olarak AIDL'yi stub koduna derlemenin iki yolu vardır. Derleme sistemleri hakkında daha fazla bilgi için Soong Modülü Referansı başlıklı makaleyi inceleyin.

Temel derleme sistemi

Herhangi bir cc_ veya java_ Android.bp modülünde (veya Android.mk eşdeğerlerinde) .aidl dosyaları kaynak dosya olarak belirtilebilir. Bu durumda, AIDL'nin Java/CPP arka uçları (NDK arka ucu değil) kullanılır ve ilgili AIDL dosyalarını kullanacak sınıflar modüle otomatik olarak eklenir. Derleme sistemine ilgili modüldeki AIDL dosyalarının kök yolunu bildiren local_include_dirs gibi seçenekler, bu modüllerde bir aidl: grubu altında belirtilebilir. Rust arka ucunun yalnızca Rust ile kullanılabileceğini unutmayın. rust_ modülleri, AIDL dosyalarının kaynak dosya olarak belirtilmemesi nedeniyle farklı şekilde ele alınır. Bunun yerine aidl_interface modülü, bağlanılabilen <aidl_interface name>-rust adlı bir rustlib oluşturur. Daha fazla bilgi için Rust AIDL örneğine bakın.

aidl_interface

Bu derleme sistemiyle kullanılan türler yapılandırılmış olmalıdır. Yapılandırılmış olmak için paketlenebilirler doğrudan alanlar içermeli ve doğrudan hedef dillerde tanımlanan türlerin bildirimleri olmamalıdır. Yapılandırılmış AIDL'nin kararlı AIDL ile nasıl uyumlu olduğu hakkında bilgi edinmek için Yapılandırılmış ve kararlı AIDL başlıklı makaleyi inceleyin.

Türler

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

Java/AIDL Türü C++ Türü NDK Türü Pas Türü
boole bool bool bool
bayt8 int8_t int8_t i8
char char16_t char16_t u16
int int32_t int32_t i32
uzun int64_t int64_t i64
float float float f32
çift çift çift f64
Dize android::String16 std::string Giriş: &str
Çıkış: Dize
android.os.Parcelable android::Parcelable Yok Yok
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder
T[] std::vector<T> std::vector<T> Giriş: &[T]
Çıkış: Vec<T>
byte[] std::vector<uint8_t> std::vector<int8_t>1 Giriş: &[u8]
Çıkış: Vec<u8>
Liste<T> std::vector<T>2 std::vector<T>3 Giriş: &[T]4
Çıkış: Vec<T>
FileDescriptor android::base::unique_fd Yok Yok
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
arayüz türü (T) android::sp<T> std::shared_ptr<T>7 binder::Strong
paketlenebilir tür (T) T T T
birleştirme türü (T)5 T T T
T[N] 6 std::array<T, N> std::array<T, N> [T; N]

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

2. C++ arka ucu, T'nin String, IBinder, ParcelFileDescriptor veya parcelable değerlerinden biri olduğu List<T> değerini destekler. Android 13 veya sonraki sürümlerde T, diziler hariç temel olmayan herhangi bir tür (arayüz türleri dahil) olabilir. AOSP, tüm arka uçlarda çalıştığı için T[] gibi dizi türlerini kullanmanızı önerir.

3. NDK arka ucu, T String, ParcelFileDescriptor veya parcelable değerlerinden biri olduğunda List<T> değerini destekler. Android 13 veya sonraki sürümlerde T, diziler dışında ilkel olmayan herhangi bir tür olabilir.

4. Rust kodunda türler, giriş (bir bağımsız değişken) veya çıkış (döndürülen değer) olmalarına bağlı olarak farklı şekilde iletilir.

5. Birleştirme türleri Android 12 ve sonraki sürümlerde desteklenir.

6. Android 13 veya sonraki sürümlerde sabit boyutlu diziler desteklenir. Sabit boyutlu diziler birden fazla boyuta sahip olabilir (ör. int[3][4]). Java arka ucunda sabit boyutlu diziler dizi türleri olarak temsil edilir.

7. Bir bağlayıcı SharedRefBase nesnesi oluşturmak için SharedRefBase::make\<My\>(... args ...) işlevini kullanın. Bu işlev, bir std::shared_ptr\<T\> nesnesi oluşturur. Bu nesne, bağlayıcının başka bir sürece ait olması durumunda da dahili olarak yönetilir. Nesneyi başka yöntemlerle oluşturmak çift sahipliğe neden olur.

8. Java/AIDL byte[] türüne de bakın.

Yön (in/out/inout)

İşlevlere ait bağımsız değişken türlerini belirtirken bunları in, out veya inout olarak belirtebilirsiniz. Bu, bir IPC çağrısı için bilgilerin hangi yönde iletileceğini kontrol eder. in, varsayılan yöndür ve verilerin arayandan aranana iletildiğini gösterir. out, verilerin aranan kullanıcıdan arayan kullanıcıya aktarıldığı anlamına gelir. inout, bu ikisinin birleşimidir. Ancak Android ekibi, bağımsız değişken belirteci inout'yi kullanmaktan kaçınmanızı önerir. inout'ü sürümlü bir arayüzle ve eski bir arananla kullanıyorsanız yalnızca arayanda bulunan ek alanlar varsayılan değerlerine sıfırlanır. Rust'ta normal bir inout türü &mut Vec<T> alır ve liste inout türü &mut Vec<T> alır.

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

UTF8/UTF16

CPP arka ucu ile dizelerin utf-8 mi yoksa utf-16 mı olacağını seçebilirsiniz. Dizeleri otomatik olarak utf-8'e dönüştürmek için AIDL'de @utf8InCpp String olarak tanımlayın. NDK ve Rust arka uçları her zaman utf-8 dizelerini kullanır. utf8InCpp ek açıklaması hakkında daha fazla bilgi için AIDL'deki ek açıklamalar başlıklı makaleyi inceleyin.

Boş değer atanabilirlik

Boş olabilecek türleri @nullable ile ek açıklamayla belirtebilirsiniz. nullable ek açıklaması hakkında daha fazla bilgi için AIDL'deki ek açıklamalar başlıklı makaleyi inceleyin.

Özel paketlenebilir ürünler

Özel paketlenebilir, hedef arka uçta manuel olarak uygulanan bir paketlenebilir öğedir. Özel paketlenebilir öğeleri yalnızca değiştirilemeyen mevcut bir özel paketlenebilir öğe için diğer dillere destek eklemeye çalışırken kullanın.

AIDL'nin bilmesi için özel bir parcelable tanımlamak amacıyla AIDL parcelable beyanı şu şekilde görünür:

    package my.pack.age;
    parcelable Foo;

Bu, varsayılan olarak my.pack.age.Foo'ın Parcelable arayüzünü uygulayan bir Java sınıfı olduğu bir Java parcelable tanımlar.

AIDL'de özel bir CPP arka uç paketi beyanı için cpp_header kullanın:

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

my/pack/age/Foo.h'teki C++ uygulaması şu şekilde görünür:

    #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);
    };

AIDL'de özel bir NDK parcelable tanımlamak için ndk_header kullanın:

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

android/pack/age/Foo.h'teki NDK uygulaması şu şekilde görünür:

    #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);
    };

Android 15'te, AIDL'de özel bir Rust parcelable'ı beyan etmek için rust_type kullanın:

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

rust_crate/src/lib.rs'teki Rust uygulaması şu şekilde görünür:

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

Ardından bu parcelable'ı AIDL dosyalarında bir tür olarak kullanabilirsiniz ancak AIDL tarafından oluşturulmaz. union'de kullanmak üzere CPP/NDK arka uç özel paketlenebilirleri için < ve == operatörleri sağlayın.

Varsayılan değerler

Yapılandırılmış paketlenebilirler, ilkel türler, String'ler ve bu türlerin dizileri için alan başına varsayılan değerler tanımlayabilir.

    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ğeri, 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 olarak başlatılmış değerlerle başlatılır. Örneğin, C++ arka ucunda String alanları boş bir dize olarak, List<T> alanları ise boş bir vector<T> olarak başlatılır. @nullable alanları, null değer alanları olarak başlatılır.

Birlikler

AIDL birlikleri etiketlenir ve özellikleri tüm arka uçlarda benzerdir. Varsayılan olarak ilk alanın varsayılan değeriyle oluşturulurlar ve bunlarla etkileşime geçmenin dile özgü bir yolu vardır.

    union Foo {
      int intField;
      long longField;
      String stringField;
      MyParcelable parcelableField;
      ...
    }

Java Örneği

    Foo u = Foo.intField(42);              // construct

    if (u.getTag() == Foo.intField) {      // tag query
      // use u.getIntField()               // getter
    }

    u.setSringField("abc");                // setter

C++ ve NDK Örneği

    Foo u;                                            // default constructor

    assert (u.getTag() == Foo::intField);             // tag query
    assert (u.get<Foo::intField>() == 0);             // getter

    u.set<Foo::stringField>("abc");                   // setter

    assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)

Pas Örneği

Rust'ta birimler enum olarak uygulanır ve açıklayıcı alıcı ve ayarlayıcılara sahip değildir.

    let mut u = Foo::Default();              // default constructor
    match u {                                // tag match + get
      Foo::IntField(x) => assert!(x == 0);
      Foo::LongField(x) => panic!("Default constructed to first field");
      Foo::StringField(x) => panic!("Default constructed to first field");
      Foo::ParcelableField(x) => panic!("Default constructed to first field");
      ...
    }
    u = Foo::StringField("abc".to_string()); // set

Hata işleme

Android OS, hizmetlerin hataları bildirmek için kullanabileceği yerleşik hata türleri sağlar. Bunlar binder tarafından kullanılır ve binder arayüzü uygulayan tüm hizmetler tarafından kullanılabilir. Bu tür yöntemlerin kullanımı AIDL tanımında ayrıntılı olarak açıklanmıştır ve kullanıcı tanımlı bir durum veya dönüş türü gerektirmez.

Hata içeren çıkış parametreleri

Bir AIDL işlevi hata bildirdiğinde işlev, çıkış parametrelerini başlatamayabilir veya değiştiremeyebilir. Özellikle, hata işlemin işlenmesi sırasında değil de paketin açılması sırasında meydana gelirse çıkış parametreleri değiştirilebilir. Genel olarak, bir AIDL işlevinden hata alındığında tüm inout ve out parametrelerinin yanı sıra döndürülen değerin (bazı arka uçlarda out parametresi gibi davranır) belirsiz durumda olduğu kabul edilmelidir.

Kullanılacak hata değerleri

Yerleşik hata değerlerinin çoğu herhangi bir AIDL arayüzünde kullanılabilir ancak bazıları özel bir şekilde işlenir. Örneğin, hata durumunu tanımlarken EX_UNSUPPORTED_OPERATION ve EX_ILLEGAL_ARGUMENT kullanılmasında sakınca yoktur ancak temel altyapı tarafından özel olarak ele alındığı için EX_TRANSACTION_FAILED kullanılmamalıdır. Bu yerleşik değerler hakkında daha fazla bilgi için arka uçla ilgili tanımları kontrol edin.

AIDL arayüzü, yerleşik hata türleri tarafından kapsanmayan ek hata değerleri gerektiriyorsa kullanıcı tarafından tanımlanan hizmete özel bir hata değerinin eklenmesine izin veren hizmete özel özel yerleşik hatayı kullanabilir. Hizmete özgü bu hatalar genellikle AIDL arayüzünde const int veya int destekli 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. Java, hizmete özgü istisnalar için kullanıcı tanımlı hatayla birlikte android.os.ServiceSpecificException değerini kullanır.

Android'deki yerel kodda istisnalar kullanılmaz. PBM 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 sunmadan önce yerel Rust hatalarına (StatusCode, ExceptionCode) dönüştürür. Hizmete özgü hatalar için döndürülen Status veya ScopedAStatus, kullanıcı tanımlı hatayla birlikte EX_SERVICE_SPECIFIC kullanır.

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

Arka uç Tanım
Java android/os/Parcel.java
ABM binder/Status.h
NDK android/binder_status.h
Rust android/binder_status.h

Çeşitli arka uçları kullanma

Bu talimatlar Android platform koduna özeldir. Bu örneklerde, tanımlanmış bir tür olan my.package.IFoo kullanılmaktadır. Rust arka ucunun nasıl kullanılacağıyla ilgili talimatlar için Android Rust Desenleri sayfasındaki Rust AIDL örneğine bakın.

İçe aktarma türleri

Tanımlanan tür bir arayüz, paketlenebilir veya birleştirme ise Java'ya aktarabilirsiniz:

import my.package.IFoo;

Alternatif olarak, PBM arka ucunda:

#include <my/package/IFoo.h>

Alternatif olarak, NDK arka ucunda (ek aidl ad alanına dikkat edin):

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

Rust arka ucunda da:

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

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

Arayüz uygulama

Bir arayüzü uygulamak için yerel stub sınıfından devralmanız gerekir. Bir arayüzün uygulaması, hizmet yöneticisine veya android.app.ActivityManager'e kaydedildiğinde genellikle hizmet, bir hizmetin istemcisi tarafından kaydedildiğinde ise geri çağırma olarak adlandırılır. Bununla birlikte, tam kullanıma bağlı olarak arayüz uygulamalarını tanımlamak için çeşitli adlar kullanılır. Stub sınıfı, bağlayıcı sürücüsünden komutları okur ve uyguladığınız yöntemleri yürütür. Aşağıdaki gibi bir AIDL dosyanız olduğunu varsayalım:

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

Java'da, oluşturulan Stub sınıfından 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 (ek 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(())
        }
    }

Alternatif olarak, eşzamansız Rust ile:

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

Kaydolma ve hizmet alma

Android platformundaki hizmetler genellikle servicemanager işlemine kaydedilir. Aşağıdaki API'lere ek olarak bazı API'ler hizmeti kontrol eder (yani hizmet kullanılamıyorsa hemen yanıt verir). Tam ayrıntılar için ilgili servicemanager arayüzünü kontrol edin. Bu işlemler yalnızca Android platformuna göre derleme yapılırken yapılabilir.

Java'da:

    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"));

CPP arka ucunda:

    #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"));

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

    #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")));

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

Tek iş parçacıklı bir çalışma zamanında, eş zamansız Rust arka ucunda:

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
}

Diğer seçeneklerden önemli bir farkı, eşzamansız Rust ve tek iş parçacıklı bir çalışma zamanı kullanırken join_thread_pool'yi çağırmamamızdır. Bunun nedeni, Tokio'ya oluşturulan görevleri yürütebileceği bir iş parçacığı vermeniz gerektiğidir. Bu örnekte ana iş parçacığı bu amaca hizmet eder. tokio::spawn kullanılarak oluşturulan tüm görevler ana iş parçacığında yürütülür.

Çok iş parçacıklı bir çalışma zamanında, eş zamansız Rust arka ucunda:

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

Çok iş parçacıklı Tokio çalışma zamanında, oluşturulan görevler ana iş parçacığında yürütülmez. Bu nedenle, ana iş parçacığının boşta kalmaması için ana iş parçacığında join_thread_pool çağrılması daha mantıklıdır. Asynkron bağlamdan çıkmak için çağrıyı block_in_place içine sarmanız gerekir.

Bir bağlayıcıyı barındıran hizmetin kullanımdan kaldırılması durumunda bildirim alma isteğinde bulunabilirsiniz. Bu, geri çağırma proxy'lerinin sızmasını önlemeye yardımcı olabilir veya hata kurtarmaya yardımcı olabilir. Bu çağrıları bağlayıcı proxy nesnelerinde yapın.

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

Arayan bilgileri

Bir çekirdek bağlayıcı çağrısı alındığında, arayanın bilgileri çeşitli API'lerde kullanılabilir. PID (veya işlem kimliği), işlem gönderen sürecin Linux işlem kimliğini ifade eder. UID (veya kullanıcı kimliği), Linux kullanıcı kimliğini ifade eder. Tek yönlü arama alındığında arayan PID'si 0 olur. Bu işlevler, bir bağlayıcı işlem bağlamının dışındayken geçerli işlemin PID ve UID değerini döndürür.

Java arka ucunda:

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

CPP arka ucunda:

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

NDK arka ucunda:

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

Rust arka ucunda, arayüzü uygularken varsayılan olarak ayarlanmasına izin vermek yerine aşağıdakileri belirtin:

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

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

Hata raporları çalıştırıldığında (örneğin, adb bugreport ile), çeşitli sorunlarda hata ayıklama işlemine yardımcı olmak için sistem genelinden bilgi toplanır. AIDL hizmetleri için hata raporları, bilgilerini hata raporuna aktarmak amacıyla hizmet yöneticisine kayıtlı tüm hizmetlerde dumpsys ikilisini kullanır. dumpsys SERVICE [ARGS] içeren bir hizmetten bilgi almak için komut satırında dumpsys'ü de kullanabilirsiniz. C++ ve Java arka uçlarında, addService için ek bağımsız değişkenler kullanarak hizmetlerin döküldüğü sırayı kontrol edebilirsiniz. Hata ayıklama sırasında bir hizmetin PID'sini almak için dumpsys --pid SERVICE'ü de kullanabilirsiniz.

Hizmetinize özel çıkış eklemek için sunucu nesnenizdeki dump yöntemini, bir AIDL dosyasında tanımlanan diğer IPC yöntemlerini uygular gibi geçersiz kılabilirsiniz. Bunu yaparken, döküm işlemini uygulama izni android.permission.DUMP ile veya belirli UID'lerle kısıtlamanız gerekir.

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, arayüzü uygularken varsayılan olarak ayarlanmasına izin vermek yerine aşağıdakileri belirtin:

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

Zayıf işaretçiler kullanma

Bir bağlayıcı nesnesine zayıf referans tutabilirsiniz.

Java, WeakReference'ü desteklese de yerel katmanda zayıf bağlayıcı referanslarını desteklemez.

CPP arka ucunda zayıf tür wp<IFoo>'tür.

NDK arka ucunda ScopedAIBinder_Weak kullanın:

#include <android/binder_auto_utils.h>

AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));

Rust arka ucunda WpIBinder veya Weak<IFoo> kullanırsınız:

let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();

Arayüz tanımlayıcısı dinamik olarak alma

Arayüz tanımlayıcısı, arayüzün türünü tanımlar. Bu, hata ayıklama sırasında 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 bir kodla 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 özelliği desteklemez.

Arayüz tanımlayıcısını statik olarak alma

Bazen (@VintfStability hizmetlerini kaydederken gibi), arayüz tanımlayıcının ne olduğunu statik olarak 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 (ek 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()

Enum aralığı

Yerleşik arka uçlarda, bir enum'un alabileceği olası değerler üzerinde iterasyon yapabilirsiniz. Kod boyutu nedeniyle bu özellik Java'da desteklenmez.

AIDL'de tanımlanan bir enum 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_values()

Mesaj dizisi yönetimi

Bir işlemdeki her libbinder örneği bir iş parçacığı havuzu bulundurur. Çoğu kullanım alanı için bu, tüm arka uçlarda paylaşılan tam olarak bir iş parçacığı havuzu olmalıdır. Bunun tek istisnası, tedarikçi kodu /dev/vndbinder ile konuşmak için libbinder'ın başka bir kopyasını yüklediğinde ortaya çıkar. Bu ayrı bir bağlayıcı düğümünde olduğundan iş parçacığı havuzu paylaşılmaz.

Java arka ucu için iş parçacığı havuzu yalnızca boyut olarak büyüyebilir (zaten başlatılmış olduğundan):

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

Asenkron Rust arka ucunda iki iş parçacığı havuzuna ihtiyacınız vardır: binder ve Tokio. Bu, özellikle join_thread_pool kullanımı söz konusu olduğunda, asenkron Rust kullanan uygulamaların özel dikkat gerektirdiği anlamına gelir. Bu konuda daha fazla bilgi için hizmetleri kaydettirmeyle ilgili bölüme bakın.

Ayrılmış adlar

C++, Java ve Rust bazı adları anahtar kelime olarak veya dile özgü kullanım için ayırır. AIDL, dil kurallarına dayalı kısıtlamalar uygulamasa da ayrılmış bir adla eşleşen alan veya tür adları kullanmak C++ veya Java için derleme hatasına neden olabilir. Rust için alan veya tür, r# ön ekiyle erişilebilen "ham tanımlayıcı" söz dizimi kullanılarak yeniden adlandırılır.

Ergonomik olmayan bağlamaları veya derleme hatalarını önlemek için mümkün olduğunda AIDL tanımlarınızda ayrılmış adlar kullanmaktan kaçının.

AIDL tanımlarınızdaki ayrılmış adlar varsa protokol uyumluluğunu korurken alanları güvenle yeniden adlandırabilirsiniz. Derlemeye devam etmek için kodunuzu güncellemeniz gerekebilir ancak derlenmiş programlar birlikte çalışmaya devam eder.

Kullanılmaması gereken adlar: