AIDL API yönergeleri

Burada özetlenen en iyi uygulamalar, özellikle AIDL'nin kararlı ve geriye dönük uyumlu bir API tanımlamak için kullanıldığı durumlarda, AIDL arayüzlerini etkili bir şekilde ve arayüzün esnekliğine dikkat ederek geliştirme konusunda rehber niteliğindedir.

AIDL, uygulamaların arka plan işleminde birbirleriyle veya sistemle arayüz oluşturması gerektiğinde bir API tanımlamak için kullanılabilir.

HAL arayüzleri için @VintfStability ile kararlı AIDL kullanılır ve istemcilerin ile sunucuların bağımsız olarak güncellenmesine olanak tanır. Bu özellik için geriye dönük uyumluluk ve yapılandırılmış veriler gerekir.

AIDL ile uygulamalarda programlama arayüzleri geliştirme hakkında daha fazla bilgi için Android Arayüz Tanımlama Dili (AIDL) başlıklı makaleyi inceleyin. AIDL'nin uygulamadaki örnekleri için HAL'ler için AIDL ve Kararlı AIDL başlıklı makaleleri inceleyin.

Sürüm oluşturma

AIDL API'sinin geriye dönük uyumlu her anlık görüntüsü bir sürüme karşılık gelir. Anlık görüntü almak için m <module-name>-freeze-api komutunu çalıştırın. API'nin bir istemcisi veya sunucusu her yayınlandığında (ör. Mainline treninde) anlık görüntü almanız ve yeni bir sürüm oluşturmanız gerekir. Sistemden tedarikçiye API'leri için bu işlem, yıllık platform revizyonuyla birlikte yapılmalıdır.

Bir arayüz dondurulduğunda (sürüm oluşturulmuş aidl_api dizinine kaydedildiğinde) hiçbir zaman değiştirilmemelidir. Yalnızca current dizinini düzenleyebilirsiniz. Bir arayüzün sonuna güvenli bir şekilde yöntemler, bir paketlenebilir öğenin sonuna alanlar, bir enum'ın sonuna numaralandırıcılar ve bir birliğin sonuna üyeler ekleyebilirsiniz.

Eski sunucularda yeni yöntemleri çağıran istemciler, istemci tarafından düzgün bir şekilde işlenmesi gereken bir UNKNOWN_TRANSACTION hatası alır.

İzin verilen değişiklik türleri hakkında daha fazla bilgi ve ayrıntı için Arayüzleri sürümleme başlıklı makaleyi inceleyin.

Derleme bağımlılıkları

Android modülleri, aidl_interface'dan oluşturulan kitaplıkların birden fazla farklı sürümüne bağlı olamaz. Kitaplıkların farklı sürümleri, aynı ad alanlarında aynı türleri tanımlar. Android'in aidl derleme sistemi bu sorunu tanımlar ve kitaplıkların eşleşmeyen sürümleriyle biten bağımlılık grafiklerinin her biriyle ilgili bir hata verir.

Bir modül, kendi bağımlılıklarına sahip birçok bağımlılık içerdiğinde ortak bir arayüzün bir sürümünü güncellemek zor olabilir.

Geliştiriciler, aidl_interface_defaults kullanarak paylaşılan bir arayüzün diğer arayüzlere olan bağımlılıklarını bildirebilir. Böylece tüm arayüzlerin bağımsız olarak güncellenmesi gerekmez.

Oluşturulan kitaplıklardaki bağımlılıkları düzenlemek için *_defaults modüllerini (ör. rust_defaults, cc_defaults, java_defaults) kullanmanızı öneririz. Arayüzlerin latest sürümü için varsayılan değerlerin yanı sıra, hâlâ kullanılıyorsa önceki sürümler için de varsayılan değerler olması yaygındır.

Geliştiriciler, aidl_interface_defaults kullanarak paylaşılan bir arayüzün diğer arayüzlere olan bağımlılıklarını bildirebilir. Böylece tüm arayüzlerin bağımsız olarak güncellenmesi gerekmez.

API tasarım kuralları

Genel

1. Her şeyi belgelendirin

  • Her yöntemi semantiği, bağımsız değişkenleri, yerleşik istisnaların kullanımı, hizmete özel istisnalar ve dönüş değeri açısından belgeleyin.
  • Her arayüzü semantiği için belgeleyin.
  • Numaralandırılmış türlerin ve sabitlerin semantik anlamını belgeleme.
  • Uygulayıcı için net olmayan her şeyi belgeleyin.
  • Alakalı yerlerde örnekler verin.

2. Dış Lastik

Türler için büyük harf içeren camel case, yöntemler, alanlar ve bağımsız değişkenler için ise küçük harf içeren camel case kullanın. Örneğin, paketlenebilir bir tür için MyParcelable, bağımsız değişken için anArgument. Kısaltmalar için kısaltmayı bir kelime olarak değerlendirin (NFC -> Nfc).

[-Wconst-name] Numaralandırma değerleri ve sabitler ENUM_VALUE ve CONSTANT_NAME olmalıdır

3. Genel bilgi gerektirmemeye dikkat edin

API'ler, geliştiricilerin kod tabanının tamamı hakkında genel bilgiye veya belirli bir alan uzmanlığına sahip olduğunu varsaymamalıdır. Alana özgü tanımlayıcılarla (ör. cihaz adları, kimlikler veya kullanıcı adları) çalışırken:

  • Bu tanımlayıcıların nereden geldiğini ve biçimlerini açıkça belirtin ve belgeleyin. Arayüzün her iki tarafının da bunları bilmesi önemliyse bu bilgileri paylaşın.
  • Alternatif olarak, arayüze özgü tanımlayıcılar (ör. bağlayıcı nesneler veya özel jetonlar) kullanın ve bir tarafın, eşlemeyi temel değerlerle yönetmesini sağlayın. Bu sayede çakışmalar azaltılır ve kullanıcıların kendi alanları dışındaki uygulama ayrıntılarını anlamaları gerekmez.

4. Tüm veriler yapılandırılmış ve geriye dönük olarak uyumludur.

string, byte[] ve paylaşılan anı gibi yapılandırılmamış verilerin içeriklerinin kararlı bir biçimde olması veya arayüzün bir tarafı için opak olması gerekir.

Örneğin, bir sonuç için hata mesajı olarak kullanılan bir dize bağımsız değişkeni, hata ayıklama için alınabilir ve günlüğe kaydedilebilir ancak biçim ve içerik geriye dönük olarak uyumlu olmayabileceğinden ayrıştırılmamalı ve yorumlanmamalıdır. Arayüzün diğer tarafının çalışma zamanında hatanın ne olduğunu bilmesi gerekiyorsa enum, sabit veya ServiceSpecificException kullanın.

Benzer şekilde, kararlı ve geriye dönük uyumlu olmadıkları sürece nesneleri byte[] veya paylaşılan belleğe serileştirmeyin. Bazı durumlarda, paylaşılan bellekte ve Fast Message Queues'da parcelable'ları ve birleşimleri paylaşmak için @FixedSize ek açıklamasını kullanabilirsiniz.

Arayüzler

1. Adlandırma

[-Winterface-name] Arayüz adı I ile başlamalıdır (ör. IFoo).

2. Kimliğe dayalı "nesneler" içeren büyük arayüzlerden kaçının

Belirli bir API ile ilgili çok sayıda çağrı olduğunda alt arayüzleri tercih edin. Bu durum aşağıdaki avantajları sağlar:

  • İstemci veya sunucu kodunun anlaşılmasını kolaylaştırır.
  • Nesnelerin yaşam döngüsünü basitleştirir.
  • Bağlayıcıların sahtesinin yapılamamasından yararlanır.

Önerilmez: Kimliğe dayalı nesneler içeren tek ve büyük bir arayüz

interface IManager {
   int getFooId();
   void beginFoo(int id); // clients in other processes can guess an ID
   void opFoo(int id);
   void recycleFoo(int id); // ownership not handled by type
}

Önerilen: Ayrı arayüzler

interface IManager {
    IFoo getFoo();
}

interface IFoo {
    void begin(); // clients in other processes can't guess a binder
    void op();
}

3. Tek yönlü yöntemlerle çift yönlü yöntemleri karıştırmayın

[-Wmixed-oneway] Tek yönlü yöntemleri tek yönlü olmayan yöntemlerle karıştırmayın. Bu durum, istemciler ve sunucular için iş parçacığı modelini anlamayı zorlaştırır. Özellikle belirli bir arayüzün istemci kodunu okurken her yöntemin engellenip engellenmeyeceğini kontrol etmeniz gerekir.

4. Durum kodları döndürmekten kaçının

Tüm AIDL yöntemlerinde örtülü bir durum döndürme kodu olduğundan, yöntemler döndürülen değerler olarak durum kodlarından kaçınmalıdır. ServiceSpecificException veya EX_SERVICE_SPECIFIC sayfasına bakın. Bu değerler, bir AIDL arayüzünde sabitler olarak tanımlanır. Bir hatanın yanında özel gecikme veya benzersiz hata verileri gerekiyorsa özel yanıt nesnesi yalnızca bu durumda hatayı temsil etmelidir. Daha ayrıntılı bilgi için Hata işleme bölümüne bakın.

5. Çıkış parametresi olarak diziler zararlı kabul edilir

[-Wout-array] void foo(out String[] ret) gibi dizi çıkış parametrelerine sahip yöntemler genellikle kötüdür. Çünkü çıkış dizisinin boyutu Java'da istemci tarafından bildirilip ayrılmalıdır. Bu nedenle, dizi çıkışının boyutu sunucu tarafından seçilemez. Bu istenmeyen davranış, Java'da dizilerin çalışma şeklinden (yeniden tahsis edilemezler) kaynaklanır. Bunun yerine String[] foo() gibi API'leri tercih edin.

6. Giriş/çıkış parametrelerinden kaçının

[-Winout-parameter] Bu durum, in parametreleri bile out parametrelerine benzediği için müşterilerin kafasını karıştırabilir.

7. Out ve inout @nullable olmayan dizi dışı parametrelerden kaçının

[-Wout-nullable] Java arka ucu @nullable ek açıklamasını diğer arka uçlar gibi işlemediğinden out/inout @nullable T, arka uçlar arasında tutarsız davranışlara yol açabilir. Örneğin, Java olmayan arka uçlar bir out @nullable parametresini null olarak ayarlayabilir (C++'ta std::nullopt olarak ayarlanır) ancak Java istemcisi bunu null olarak okuyamaz.

8. Benzersiz istekler ve yanıtlar kullanma

Gerekli tüm parametreleri tek bir girişte parcelable gruplandırın. Temel öğeleri iletmek yerine her arayüz yöntemi için özel istek ve yanıt paketlenebilirleri oluşturun (örneğin, ayrı değişkenler iletmek yerine ComputeResponse compute(in ComputeRequest request) kullanın). Bu sayede, işlev imzası değiştirilmeden daha sonra yeni bağımsız değişkenler eklenebilir. Gelecekte daha fazla parametre eklenmesi bekleniyorsa veya bir yöntemde zaten dört parametreden fazla varsa bu kalıbın kullanılması önemle tavsiye edilir.

Ek giriş veya çıkış gerektirmeyen yöntemler bu öneriden yararlanmaz. Her durumu açıkça düşünmek ve gelecekteki değişikliklere karşı esnek kalmak, kullanımdan kaldırılan yöntemlerin sayısını azaltabilir ve geriye dönük uyumlu kodun karmaşıklığını düşürebilir.

Bir yöntem bu kalıp kullanılarak oluşturulmadıysa istek ve yanıt paketlenebilir öğeleri içeren yeni bir yöntem oluşturup eski yöntemin desteğini sonlandırarak bu kalıba geçebilirsiniz. Örneğin:

void foo(int a, int b, int c); // original version, but deprecated in favor of the next version
void fooV2(in MyArg arg); // new version having int a, b, c, and d.

Yapılandırılmış Parcelable'lar

1. Ne zaman kullanılır?

Gönderilecek birden fazla veri türü olduğunda yapılandırılmış parcelable'lar kullanın.

Veya tek bir veri türünüz olduğunda ancak gelecekte bunu genişletmeniz gerekeceğini düşündüğünüzde. Örneğin, String username kullanmayın. Aşağıdaki gibi genişletilebilir bir paketlenebilir nesne kullanın:

parcelable User {
    String username;
}

Böylece gelecekte aşağıdaki şekilde uzatabilirsiniz:

parcelable User {
    String username;
    int id;
}

2. Varsayılanları açıkça sağlama

[-Wexplicit-default, -Wenum-explicit-default] Alanlar için açık varsayılanlar sağlayın. Bir paketlenebilir öğeye yeni alanlar eklendiğinde eski istemciler ve sunucular bunları bırakır ancak yeni istemciler ve sunucular için varsayılan değerler otomatik olarak doldurulur.

3. Tedarikçi uzantıları için ParcelableHolder'ı kullanma

Cihaz uygulayıcılarının genişletmesi gereken bir AOSP parcelable tanımlarsanız nesnenize bir ParcelableHolder örneği yerleştirin. Bu, birleştirme çakışmaları oluşturmadan uzantı noktası görevi görür. Bu, eklenmiş arayüz uzantılarına benzer ancak uygulayıcıların kendi arayüzlerini ve türlerini oluşturmadan mevcut parcelable ile birlikte kendi tescilli parcelable'lerini eklemelerine olanak tanır.

4. Veri yapıları

  • AIDL, tüm yerel arka uçlarda güvenli bir şekilde çevrilen Map türlerini (ör. FeatureToScoreEntry[]) yerel olarak desteklemediğinden haritaları temsil etmek için diziler veya List türünde paketlenebilirler kullanın.
  • Gelecekte paralel dizilere ihtiyaç duyulmaması için tekrarlanan alanlarda temel öğe dizileri yerine parcelable nesne dizilerini kullanın.
  • IPC üzerinden serileştirilmiş dizeler veya JSON yerine kesin olarak türü belirlenmiş parcelable nesneleri kullanın.
  • Gelecekteki genişlemeye olanak tanımak için durumlar için boole yerine enum kullanın. Bit maskeleri için bazı arka uçlarda hantal yayınlamayı önlemek amacıyla enum türleri yerine const int türlerini kullanın.

Yapılandırılmamış Parcelable'lar

1. Ne zaman kullanılır?

Yapılandırılmamış Parcelable'lar, Java'da @JavaOnlyStableParcelable ile, NDK arka ucunda ise @NdkOnlyStableParcelable ile kullanılabilir. Bunlar genellikle yapılandırılamayan eski ve mevcut paketlenebilirlerdir.

Sabitler ve sıralamalar

1. Bit alanları sabit alanlar kullanmalıdır

Bit alanları sabit alanlar kullanmalıdır (örneğin, bir arayüzde const int FOO = 3;).

2. Numaralandırmalar kapalı kümeler olmalıdır.

Numaralandırmalar kapalı kümeler olmalıdır. Not: Yalnızca arayüz sahibi enum öğeleri ekleyebilir. Tedarikçilerin veya OEM'lerin bu alanları genişletmesi gerekiyorsa alternatif bir mekanizma gerekir. Mümkün olduğunda, tedarikçi işlevselliğinin upstreaming'i tercih edilmelidir. Ancak bazı durumlarda, özel satıcı değerlerine izin verilebilir (Bununla birlikte, satıcılar bunu sürümlemek için bir mekanizmaya sahip olmalıdır, belki de AIDL'nin kendisi, birbirleriyle çakışmamalıdır ve bu değerler üçüncü taraf uygulamalarına gösterilmemelidir).

3. "NUM_ELEMENTS" gibi değerlerden kaçının.

Numaralandırılmış türler sürümlendirildiğinden kaç değerin mevcut olduğunu belirten değerlerden kaçınılmalıdır. C++'ta bu durum enum_range<> ile çözülebilir. Rust için enum_values() kullanın. Java'da henüz bir çözüm bulunmamaktadır.

Önerilmez: Numaralandırılmış değerler kullanmayın.

@Backing(type="int")
enum FruitType {
    APPLE = 0,
    BANANA = 1,
    MANGO = 2,
    NUM_TYPES, // BAD
}

4. Gereksiz önek ve soneklerden kaçının

[-Wredundant-name] Sabitlerde ve numaralandırıcılarda gereksiz veya tekrarlayan önekler ve sonekler kullanmayın.

Önerilmez: Gereksiz bir önek kullanma

enum MyStatus {
    STATUS_GOOD,
    STATUS_BAD // BAD
}

Önerilen: Enum'u doğrudan adlandırma

enum MyStatus {
    GOOD,
    BAD
}

FileDescriptor

[-Wfile-descriptor] FileDescriptor öğesinin bir bağımsız değişken veya AIDL arayüzü yönteminin dönüş değeri olarak kullanılması kesinlikle önerilmez. Özellikle AIDL, Java'da uygulandığında dikkatli bir şekilde ele alınmadığı takdirde dosya tanımlayıcı sızıntısına neden olabilir. Temel olarak, bir FileDescriptor kabul ederseniz artık kullanılmadığında manuel olarak kapatmanız gerekir.

Yerel arka uçlarda, FileDescriptor, otomatik olarak kapatılabilen unique_fd ile eşlendiği için güvendesiniz. Ancak kullanacağınız arka uç dilinden bağımsız olarak, gelecekte arka uç dilini değiştirme özgürlüğünüzü sınırlayacağından FileDescriptor kullanmamanız önerilir.

Bunun yerine, otomatik olarak kapatılabilen ParcelFileDescriptor özelliğini kullanın.

Değişken birimler

Değişken birimlerin, dokümanlara başvurmaya gerek kalmadan iyi tanımlanıp anlaşılabilmesi için ada dahil edildiğinden emin olun.

Örnekler

long duration; // Bad
long durationNsec; // Good
long durationNanos; // Also good

double energy; // Bad
double energyMilliJoules; // Good

int frequency; // Bad
int frequencyHz; // Good

Zaman damgaları referanslarını belirtmelidir.

Zaman damgaları (aslında tüm birimler!) birimlerini ve referans noktalarını net bir şekilde belirtmelidir.

Örnekler

/**
 * Time since device boot in milliseconds
 */
long timestampMs;

/**
 * UTC time received from the NTP server in units of milliseconds
 * since January 1, 1970
 */
long utcTimeMs;

Eşzamanlılık ve eşzamansız işlemler

Engellemeyi önlemek için uzun süren işlemleri eşzamansız (oneway) bir arayüzle yönetin.

Bir hizmet, istemcilerine güvenmiyorsa istemcilerden aldığı tüm geri aramalar oneway arayüzleri olmalıdır. Bu sayede istemcilerin hizmeti süresiz olarak engellemesi önlenir.

İleriye dönük bir çağrı, giriş bağımsız değişkenleri ve sonuçları almak için bir geri çağırma arayüzünden oluşan asenkron API'ler oluşturun. Argümanlarla ilgili öneriler için Benzersiz istekler ve yanıtlar kullanma başlıklı makaleyi inceleyin.