HAL'ler için AIDL

Android 11, Android'de HAL'ler için AIDL kullanma özelliğini sunar. Bu da Android'in bazı parçalarını HIDL olmadan uygulayabilmenizi sağlar. Mümkün olduğunda yalnızca AIDL kullanılacak geçiş HAL'leri (yukarı akış HAL'leri HIDL kullandığında, HIDL kullanılmalıdır).

system.img içindekiler gibi çerçeve bileşenleri ile vendor.img gibi donanım bileşenleri arasında iletişim kurmak için AIDL kullanan HAL'ler, Kararlı AIDL kullanmalıdır. Bununla birlikte, bir bölümde (örneğin, bir HAL'den diğerine) iletişim kurmak için kullanılacak IPC mekanizmasına dair bir kısıtlama yoktur.

Motivasyon

AIDL, HIDL'den daha uzun süredir kullanımdadır ve Android çerçeve bileşenleri veya uygulamalar gibi farklı yerlerde kullanılmaktadır. Artık AIDL kararlılık desteğine sahip olduğundan, tek bir IPC çalışma zamanıyla tüm yığını uygulamak mümkündür. AIDL ayrıca HIDL'den daha iyi bir sürüm oluşturma sistemine sahip.

  • Tek bir IPC dili kullanmak; öğrenmek, hata ayıklamak, optimize etmek ve güvence altına almak için tek bir şeye sahip olmak anlamına gelir.
  • AIDL, arayüz sahipleri için yerinde sürüm oluşturmayı destekler:
    • Sahipler, arayüzlerin sonuna yöntemler veya parseller için alanlar ekleyebilir. Bu da, kodun yıllar içinde daha kolay bir şekilde oluşturulmasını ve aynı zamanda yıllık maliyetin daha düşük olması anlamına gelir. (Türler, yerinde değiştirilebilir ve her bir arayüz sürümü için ek kitaplıklara gerek yoktur).
    • Uzantı arayüzleri, tür sistemi yerine çalışma zamanında eklenebilir. Bu nedenle, aşağı akış uzantılarının yeni arayüz sürümlerine yeniden yerleştirilmesine gerek yoktur.
  • Mevcut bir AIDL arayüzü, sahibi tarafından sabitlemeyi seçtiğinde doğrudan kullanılabilir. Daha önce, arayüzün tamamının HIDL'de oluşturulması gerekiyordu.

AIDL çalışma zamanına göre geliştirme yapın

AIDL'nin üç farklı arka ucu vardır: Java, NDK ve CPP. Kararlı AIDL'yi kullanmak için her zaman system/lib*/libbinder.so adresindeki libbinder'ın sistem kopyasını kullanmanız ve /dev/binder üzerinde konuşmanız gerekir. Tedarikçi firma görüntüsündeki kod için bu, libbinder (VNDK'dan) kullanılamaz: Bu kitaplıkta kararsız bir C++ API'si ve kararsız dahili öğeleri vardır. Bunun yerine, yerel tedarikçi firma kodu AIDL'nin NDK arka ucunu kullanmalı, libbinder_ndk (libbinder.so sistemi tarafından desteklenir) ile bağlantı kurmalı ve aidl_interface girişleri tarafından oluşturulan NDK kitaplıklarına bağlanmalıdır. Tam modül adları için modül adlandırma kurallarına bakın.

AIDL HAL arayüzü yazma

AIDL arayüzünün sistem ve satıcı arasında kullanılması için arayüzde iki değişiklik yapılması gerekir:

  • Her tür tanımı @VintfStability ile açıklanmalıdır.
  • aidl_interface beyanında stability: "vintf", bulunmalıdır.

Bu değişiklikleri yalnızca arayüzün sahibi yapabilir.

Bu değişiklikleri yaptığınızda arayüzün çalışması için VINTF manifest'inde olması gerekir. vts_treble_vintf_vendor_test VTS testini kullanarak bunu (ve serbest bırakılan arayüzlerin dondurulduğunu doğrulama gibi ilgili şartları) test edin. Bu gereksinimler olmadan bir @VintfStability arayüzünü, başka bir işleme gönderilmeden önce NDK arka ucunda AIBinder_forceDowngradeToLocalStability, C++ arka ucunda android::Stability::forceDowngradeToLocalStability ya da Java arka ucunda android.os.Binder#forceDowngradeToSystemStability çağrısı yaparak kullanabilirsiniz. Tüm uygulamalar bir sistem bağlamında çalıştığından, bir hizmetin tedarikçi firma kararlılığına düşürülmesi Java'da desteklenmez.

Ayrıca, maksimum kod taşınabilirliği sağlamak ve gereksiz ek kitaplıklar gibi olası sorunlardan kaçınmak için PPP arka ucunu devre dışı bırakın.

Üç arka uç (Java, NDK ve CPP) olduğundan aşağıdaki kod örneğinde backends kullanımının doğru olduğunu unutmayın. Aşağıdaki kodda, devre dışı bırakmak için PBM arka ucunun nasıl seçileceği açıklanmaktadır.

    aidl_interface: {
        ...
        backends: {
            cpp: {
                enabled: false,
            },
        },
    }

AIDL HAL arayüzlerini bulma

HAL'ler için AOSP Kararlı AIDL arayüzleri, aidl klasörlerde HIDL arayüzleriyle aynı temel dizinlerde bulunur.

  • donanım/arayüzler
  • çerçeveler/donanım/arayüzler
  • sistem/donanım/arayüzler

Uzantı arayüzlerini vendor veya hardware içindeki diğer hardware/interfaces alt dizinlerine yerleştirmeniz gerekir.

Uzantı arayüzleri

Android'in her sürümde bir dizi resmi AOSP arayüzü vardır. Android iş ortakları bu arayüzlere işlev eklemek istediklerinde, bunları doğrudan değiştirmemelidirler. Bu, Android çalışma zamanlarının AOSP Android çalışma zamanıyla uyumlu olmadığı anlamına gelir. GMS cihazlarda, bu arayüzleri değiştirmekten kaçınmak da GSI görüntüsünün çalışmaya devam etmesini sağlar.

Uzantılar iki farklı şekilde kaydedilebilir:

Bununla birlikte, tedarikçi firmaya özgü (yukarı akış AOSP'nin bir parçası olmayan) bileşenler arayüzü kullandığında uzantı kaydedilir, birleştirme çakışması olasılığı olmaz. Ancak yukarı akış AOSP bileşenlerinde aşağı akış değişiklikleri yapıldığında birleştirme çakışmaları ortaya çıkabilir ve aşağıdaki stratejiler önerilir:

  • arayüz eklemelerinin bir sonraki sürümde AOSP'ye yayınlanması
  • birleştirme çakışmaları olmadan daha fazla esneklik sağlayan arayüz eklemeleri bir sonraki sürümde yayınlanabilecek

Uzantı paketleri: ParcelableHolder

ParcelableHolder, başka bir Parcelable içerebilen bir Parcelable. ParcelableHolder öğesinin ana kullanım alanı, Parcelable uzantısını genişletilebilir hale getirmektir. Örneğin, cihaz uygulayıcılarının AOSP tarafından tanımlanan Parcelable, AospDefinedParcelable öğesini, katma değerli özelliklerini içerecek şekilde genişletmesini beklediği görüntü.

Daha önce ParcelableHolder olmadan, cihaz uygulayıcıları daha fazla alan eklemek hata olacağından AOSP tarafından tanımlanan kararlı bir AIDL arayüzünü değiştiremiyordu:

parcelable AospDefinedParcelable {
  int a;
  String b;
  String x; // ERROR: added by a device implementer
  int[] y; // added by a device implementer
}

Önceki kodda görüldüğü gibi, cihaz uygulayıcısı tarafından eklenen alanlar, Parcelable'ın Android'in sonraki sürümlerinde revize edilmesi sırasında bir çakışma yaşayabileceği için bu uygulama ihlal edilmiştir.

Ayrıştırılabilir öğenin sahibi, ParcelableHolder kullanarak Parcelable içinde bir uzantı noktası tanımlayabilir.

parcelable AospDefinedParcelable {
  int a;
  String b;
  ParcelableHolder extension;
}

Daha sonra, cihaz uygulayıcıları uzantıları için kendi Parcelable değerlerini tanımlayabilir.

parcelable OemDefinedParcelable {
  String x;
  int[] y;
}

Son olarak, yeni Parcelable, orijinal Parcelable öğesine ParcelableHolder alanıyla eklenebilir.


// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;

ap.extension.setParcelable(op);

...

OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);

// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();

ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);

...

std::shared_ptr<OemDefinedParcelable> op_ptr;

ap.extension.getParcelable(&op_ptr);

// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);

...

std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);

// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });

ap.extension.set_parcelable(Rc::clone(&op));

...

let op = ap.extension.get_parcelable::<OemDefinedParcelable>();

AIDL HAL sunucusu örneği adları

Geleneksel olarak, AIDL HAL hizmetleri $package.$type/$instance biçiminde bir örnek adına sahiptir. Örneğin, vibratör HAL'nin bir örneği android.hardware.vibrator.IVibrator/default olarak kaydedilir.

AIDL HAL sunucusu yazma

@VintfStability AIDL sunucuları VINTF manifest'inde tanımlanmalıdır. Örneğin:

    <hal format="aidl">
        <name>android.hardware.vibrator</name>
        <version>1</version>
        <fqname>IVibrator/default</fqname>
    </hal>

Aksi takdirde, AIDL hizmetini normal şekilde kaydetmeleri gerekir. VTS testleri çalıştırırken beyan edilen tüm AIDL HAL'lerin kullanılabilir olması beklenir.

AIDL istemcisi yaz

AIDL istemcileri, uyumluluk matrisinde kendilerini beyan etmelidir. Örneğin:

    <hal format="aidl" optional="true">
        <name>android.hardware.vibrator</name>
        <version>1-2</version>
        <interface>
            <name>IVibrator</name>
            <instance>default</instance>
        </interface>
    </hal>

Mevcut bir HAL'yi HIDL'den AIDL'ye dönüştürme

HIDL arayüzünü AIDL'ye dönüştürmek için hidl2aidl aracını kullanın.

hidl2aidl özellikleri:

  • Belirtilen paket için .hal dosyalarına dayalı .aidl dosyaları oluşturun
  • Yeni oluşturulan AIDL paketi için tüm arka uçlar etkin şekilde derleme kuralları oluşturun
  • HIDL türlerinden AIDL türlerine çeviri için Java, CPP ve NDK arka uçlarında çeviri yöntemleri oluşturma
  • Gerekli bağımlılıklara sahip çeviri kitaplıkları için derleme kuralları oluşturma
  • HIDL ve AIDL numaralandırıcılarının PPP ve NDK arka uçlarında aynı değerlere sahip olmasını sağlamak için statik onaylamalar oluşturun

Bir .hal dosyası paketini .aidl dosyalarına dönüştürmek için aşağıdaki adımları uygulayın:

  1. system/tools/hidl/hidl2aidl adresindeki aracı oluşturun.

    Bu aracı en güncel kaynaktan oluşturmak en eksiksiz deneyimi sunar. Önceki sürümlerdeki eski şubelerdeki arayüzleri dönüştürmek için en yeni sürümü kullanabilirsiniz.

    m hidl2aidl
    
  2. Aracı, dönüştürülecek paketin ardından bir çıkış diziniyle yürütün.

    İsteğe bağlı olarak, yeni lisans dosyasının içeriğini oluşturulan tüm dosyaların en üstüne eklemek için -l bağımsız değişkenini kullanın. Doğru lisansı ve tarihi kullandığınızdan emin olun.

    hidl2aidl -o <output directory> -l <file with license> <package>
    

    Örnek:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
    
  3. Oluşturulan dosyaları okuyun ve dönüşümle ilgili sorunları giderin.

    • conversion.log, öncelikle düzeltilmesi gereken sorunları içeriyor.
    • Oluşturulan .aidl dosyalarında işlem gerekebilecek uyarı ve öneriler bulunabilir. Bu yorumlar // ile başlıyor.
    • Paketi temizleme ve pakette iyileştirmeler yapma fırsatından yararlanabilirsiniz.
    • toString veya equals gibi gerekli olabilecek özellikler için @JavaDerive notunu kontrol edin.
  4. Yalnızca ihtiyacınız olan hedefleri oluşturun.

    • Kullanılmayan arka uçları devre dışı bırakın. CPP arka ucu yerine NDK arka ucunu tercih edin. Çalışma zamanı seçme bölümüne bakın.
    • Çeviri kitaplıklarını veya kullanılmayacak olan kodları kaldırın.
  5. Önemli AIDL/HIDL farkları konusuna bakın.

    • AIDL'nin yerleşik Status ve istisnalarının kullanılması genellikle arayüzü iyileştirir ve arayüze özgü başka bir durum türüne olan ihtiyacı ortadan kaldırır.
    • Yöntemlerdeki AIDL arayüzü bağımsız değişkenleri, HIDL'deki gibi varsayılan olarak @nullable değildir.

AIDL HAL'lere ilişkin SEPolitikası

Tedarikçi firma kodu tarafından görülebilen bir AIDL hizmet türünün hal_service_type özelliği olmalıdır. Aksi takdirde, sepolicy yapılandırması diğer AIDL hizmetleriyle aynıdır (ancak HAL'ler için özel özellikler vardır). Aşağıda, HAL hizmeti bağlamının örnek bir tanımı verilmiştir:

    type hal_foo_service, service_manager_type, hal_service_type;

Platform tarafından tanımlanan çoğu hizmet için doğru türde bir hizmet bağlamı zaten eklenmiştir (örneğin, android.hardware.foo.IFoo/default zaten hal_foo_service olarak işaretlenmiştir). Ancak bir çerçeve istemcisi birden fazla örnek adını destekliyorsa cihaza özel service_contexts dosyalarına ek örnek adları eklenmelidir.

    android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0

Yeni bir HAL türü oluşturduğumuzda HAL özellikleri eklenmelidir. Belirli bir HAL özelliği, birden çok hizmet türüyle ilişkilendirilebilir (her birinin, az önce konuştuğumuz gibi birden fazla örneği olabilir). HAL, foo için hal_attribute(foo). Bu makro hal_foo_client ve hal_foo_server özelliklerini tanımlar. Belirli bir alan adında hal_client_domain ve hal_server_domain makroları, alanı belirli bir HAL özelliğiyle ilişkilendirir. Örneğin, sistem sunucusunun bu HAL'nin istemcisi olması, hal_client_domain(system_server, hal_foo) politikasına karşılık gelir. Benzer şekilde bir HAL sunucusunda hal_server_domain(my_hal_domain, hal_foo) bulunur. Genellikle, belirli bir HAL özelliği için referans veya örnek HAL'ler olarak hal_foo_default gibi bir alan adı da oluştururuz. Ancak bazı cihazlar bu alan adlarını kendi sunucuları için kullanır. Birden fazla sunucunun alanlarını ayırt etmek, yalnızca aynı arayüzü sunan birden fazla sunucumuz olduğunda ve bunların uygulamalarında farklı bir izne ihtiyaç duyulduğunda önemlidir. Bu makroların tümünde hal_foo, aslında bir sepolicy nesnesi değildir. Bunun yerine, bu jeton bu makrolar tarafından, bir istemci sunucu çiftiyle ilişkilendirilmiş özellik grubuna başvurmak için kullanılır.

Ancak şu ana kadar hal_foo_service ve hal_foo öğelerini (hal_attribute(foo) alanındaki özellik çifti) ilişkilendirmedik. HAL özelliği, hal_attribute_service makrosu kullanılarak AIDL HAL hizmetleriyle ilişkilendirilir (HIDL HAL'leri hal_attribute_hwservice makrosu kullanır). Örneğin, hal_attribute_service(hal_foo, hal_foo_service). Bu, hal_foo_client işlemlerinin HAL'yi ele alabileceği ve hal_foo_server işlemlerinin HAL'yi kaydedebileceği anlamına gelir. Bu kayıt kurallarının uygulanması bağlam yöneticisi (servicemanager) tarafından yapılır. Hizmet adlarının her zaman HAL özelliklerine karşılık gelmeyebileceğini unutmayın. Örneğin, şunu görebiliriz: hal_attribute_service(hal_foo, hal_foo2_service). Ancak genellikle bu durum, hizmetlerin her zaman birlikte kullanıldığını ima ettiğinden hal_foo2_service etiketini kaldırıp tüm hizmet bağlamlarımız için hal_foo_service etiketini kullanabiliriz. Birden fazla hal_attribute_service ayarlayan HAL'lerin çoğu, orijinal HAL özellik adının yeterince genel olmaması ve değiştirilememesinden kaynaklanır.

Hepsini bir araya getirdiğimizde, bir HAL örneği şu şekilde olacaktır:

    public/attributes:
    // define hal_foo, hal_foo_client, hal_foo_server
    hal_attribute(foo)

    public/service.te
    // define hal_foo_service
    type hal_foo_service, hal_service_type, protected_service, service_manager_type

    public/hal_foo.te:
    // allow binder connection from client to server
    binder_call(hal_foo_client, hal_foo_server)
    // allow client to find the service, allow server to register the service
    hal_attribute_service(hal_foo, hal_foo_service)
    // allow binder communication from server to service_manager
    binder_use(hal_foo_server)

    private/service_contexts:
    // bind an AIDL service name to the selinux type
    android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0

    private/<some_domain>.te:
    // let this domain use the hal service
    binder_use(some_domain)
    hal_client_domain(some_domain, hal_foo)

    vendor/<some_hal_server_domain>.te
    // let this domain serve the hal service
    hal_server_domain(some_hal_server_domain, hal_foo)

Ekli uzantı arayüzleri

Bir uzantı, doğrudan hizmet yöneticisine kayıtlı üst düzey bir arayüz veya bir alt arayüz olması fark etmeksizin tüm bağlayıcı arayüzlere eklenebilir. Uzantı alırken, uzantı türünün beklendiği gibi olduğunu onaylamanız gerekir. Uzantılar, yalnızca bağlayıcı sunan işlemden ayarlanabilir.

Ekli uzantılar, bir uzantı mevcut bir HAL'nin işlevini değiştirdiğinde kullanılmalıdır. Tamamen yeni bir işlev gerektiğinde bu mekanizmanın kullanılması gerekmez ve bir uzantı arayüzü doğrudan hizmet yöneticisine kaydedilebilir. Derin veya çok örnekli olabileceğinden, ekli uzantı arayüzleri, alt arayüzlere bağlı olduklarında daha anlamlı olur. Başka bir hizmetin bağlayıcı arayüzü hiyerarşisini yansıtmak için global uzantı kullanmak, doğrudan eklenen uzantılara eşdeğer işlevler sağlamak için kapsamlı muhasebe işlemleri gerektirir.

Bağlayıcıda bir uzantı ayarlamak için aşağıdaki API'leri kullanın:

  • NDK arka ucunda: AIBinder_setExtension
  • Java arka ucunda: android.os.Binder.setExtension
  • PBM arka ucunda: android::Binder::setExtension
  • Rust arka ucunda: binder::Binder::set_extension

Bir bağlayıcıda uzantı almak için aşağıdaki API'leri kullanın:

  • NDK arka ucunda: AIBinder_getExtension
  • Java arka ucunda: android.os.IBinder.getExtension
  • PBM arka ucunda: android::IBinder::getExtension
  • Rust arka ucunda: binder::Binder::get_extension

İlgili arka uçtaki getExtension işlevinin dokümanlarında bu API'ler hakkında daha fazla bilgi bulabilirsiniz. Uzantıların nasıl kullanılacağına ilişkin bir örneği donanım/arayüzler/testler/uzantı/titreşim bölümünde bulabilirsiniz.

Önemli AIDL ve HIDL farklılıkları

AIDL HAL veya AIDL HAL arayüzleri kullanırken, HIDL HAL yazmayla karşılaştırıldığında farklılıklara dikkat edin.

  • AIDL dilinin söz dizimi Java'ya daha yakındır. HIDL söz dizimi C++ ile benzer.
  • Tüm AIDL arayüzlerinde yerleşik hata durumları bulunur. Özel durum türleri oluşturmak yerine arayüz dosyalarında sabit durum int'leri oluşturun ve CPP/NDK arka uçlarında EX_SERVICE_SPECIFIC ve Java arka ucunda ServiceSpecificException kullanın. Hata İşleme bölümüne bakın.
  • AIDL, bağlayıcı nesneleri gönderildiğinde iş parçacığı havuzlarını otomatik olarak başlatmaz. Bunların manuel olarak başlatılması gerekir (ileti dizisi yönetimine bakın).
  • AIDL, kontrol edilmemiş aktarım hatalarında iptal etmez (HIDL Return, kontrol edilmemiş hatalarda iptal eder).
  • AIDL dosya başına yalnızca bir tür tanımlayabilir.
  • AIDL bağımsız değişkenleri, çıkış parametresine ek olarak giriş/çıkış/inout olarak belirtilebilir ("eşzamanlı geri çağırma" yoktur).
  • AIDL, temel tür olarak herkese açık kullanıcı adı yerine fd kullanır.
  • HIDL, uyumsuz değişiklikler için ana sürümleri, uyumlu değişiklikler için ise alt sürümleri kullanır. AIDL'de geriye dönük uyumlu değişiklikler uygulanır. AIDL'de ana sürümler konusunda açık bir kavram yoktur. Bunun yerine bu kavram paket adlarına dahil edilmiştir. Örneğin, AIDL paket adını bluetooth2 kullanabilir.
  • AIDL varsayılan olarak gerçek zamanlı önceliği devralmaz. setInheritRt işlevi, gerçek zamanlı öncelik devralmayı etkinleştirmek için bağlayıcı başına kullanılmalıdır.