Burada açıklanan en iyi uygulamalar, AIDL arayüzlerini etkili bir şekilde ve arayüzün esnekliğine dikkat ederek geliştirme konusunda rehberlik sağlar. Bu, özellikle AIDL'nin kararlı ve geriye dönük uyumlu bir API tanımlamak için kullanıldığı durumlarda geçerlidir.
AIDL, uygulamaların arka plan işleminde birbirleriyle veya sistemle arayüz oluşturması gerektiğinde API tanımlamak için kullanılabilir.
@VintfStability ile kararlı AIDL, HAL arayüzleri için kullanılır ve istemciler ile sunucuların bağımsız olarak güncellenmesine olanak tanır. Bu, geriye dönük uyumluluk ve yapılandırılmış veri gerektirir.
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'nin 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 (örneğin, bir Mainline treninde) anlık görüntü almanız ve yeni bir sürüm oluşturmanız gerekir. Sistemden tedarikçiye API'ler için bu, yıllık platform revizyonuyla gerçekleşmelidir.
Dondurulan (sürüm bilgisi içeren aidl_api dizinine kaydedilen) arayüzler hiçbir zaman değiştirilmemelidir. Yalnızca current dizinini düzenleyebilirsiniz. Bir arayüzün sonuna güvenli bir şekilde yöntem, parcelable'ın sonuna alan, enum'a numaralandırıcı ve birliğe üye ekleyebilirsiniz.
Eski sunucularda yeni yöntemleri çağıran istemciler UNKNOWN_TRANSACTION hatası alır. Bu hata, istemci tarafından düzgün bir şekilde işlenmelidir.
İzin verilen değişiklik türleri hakkında daha fazla bilgi için Sürüm oluşturma arayüzleri bölümüne bakın.
Derleme bağımlılıkları
Android modülleri, bir aidl_interface tarafından 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 tespit eder ve kitaplıkların eşleşmeyen sürümleriyle biten her bir bağımlılık grafiğiyle ilgili hata verir.
Bir modül, kendi bağımlılıkları olan birçok bağımlılık içeriyorsa ortak bir arayüzün bir sürümünü güncellemek zor olabilir.
Geliştiriciler, paylaşılan bir arayüzün diğer arayüzlere olan bağımlılıklarını bildirmek için aidl_interface_defaults kullanabilir. 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 (rust_defaults, cc_defaults, java_defaults gibi) kullanmanızı öneririz. Arayüzlerin latest sürümü için varsayılan bir değerin yanı sıra, hâlâ kullanılıyorlarsa önceki sürümler için de varsayılan değerlerin olması yaygındır.
Geliştiriciler, paylaşılan bir arayüzün diğer arayüzlere olan bağımlılıklarını bildirmek için aidl_interface_defaults kullanabilir. Böylece tüm arayüzlerin bağımsız olarak güncellenmesi gerekmez.
API tasarım yönergeleri
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öndürülen değer açısından belgeleyin.
- Her arayüzün semantiğini belgeleyin.
- Sıralamaların ve sabitlerin semantik anlamını belgeleyin.
- Uygulayıcı için net olmayabilecek her şeyi belgeleyin.
- Uygun durumlarda örnekler verin.
2. Çerçeve
Türler için upper camel case, yöntemler, alanlar ve bağımsız değişkenler için lower camel case kullanın. Örneğin, parcelable türü için MyParcelable ve bağımsız değişken için anArgument. Kısaltmalar için kısaltmayı bir kelime olarak düşünün (NFC -> Nfc).
[-Wconst-name] Enum değerleri ve sabitler ENUM_VALUE ve CONSTANT_NAME olmalıdır.
3. Genel bilgi gerektirmeyin
API'ler, geliştiricilerin tüm kod tabanı hakkında genel bilgiye veya belirli bir alan uzmanlığına sahip olduğunu varsaymamalıdır. Alana özgü tanımlayıcılarla (cihaz adları, kimlikler veya kullanıcı adları gibi) çalışırken:
- Bu tanımlayıcıların nereden geldiğini ve biçimlerini açıkça belirtip belgeleyin. Bu, arayüzün her iki tarafının da bunları bilmesi açısından önemlidir.
- Alternatif olarak, arayüze özel tanımlayıcılar (ör. bağlayıcı nesneler veya özel jetonlar) kullanın ve bir tarafın temel değerlerle eşlemeyi yönetmesini sağlayın. Bu sayede çakışmalar azalı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 uyumludur.
string, byte[] ve paylaşılan bellek gibi yapılandırılmamış verilerin içerikleri için kararlı bir biçim olmalı veya arayüzün bir tarafı için opak olmalıdır.
Örneğin, bir sonucun hata mesajı olarak kullanılan 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 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, nesneler kararlı ve geriye dönük uyumlu değilse byte[] veya paylaşılan belleğe serileştirmeyin. Bazı durumlarda, paylaşılan bellekte ve hızlı mesaj kuyruklarında parcelable ve union'ları paylaşmak için @FixedSize ek açıklamasını kullanabilirsiniz.
Arayüzler
1. Adlandırma
[-Winterface-name] Arayüz adı, IFoo gibi I ile başlamalıdır.
2. Kimlik tabanlı "nesneler" içeren büyük arayüzlerden kaçınma
Belirli bir API ile ilgili çok sayıda çağrı olduğunda alt arayüzleri tercih edin. Bu, aşağıdaki avantajları sağlar:
- İstemci veya sunucu kodunun daha kolay anlaşılmasını sağlar.
- Nesnelerin yaşam döngüsünü basitleştirir.
- Bağlayıcıların taklit edilemez olmasından yararlanır.
Önerilmez: Kimlik tabanlı nesneler içeren tek bir büyük 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: Bağımsız 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ü ve ç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, 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 engelleyici olup olmadığını kontrol etmeniz gerekir.
4. Durum kodları döndürmekten kaçının
Tüm AIDL yöntemlerinin örtük bir durum döndürme kodu olduğundan, yöntemler döndürülen değer olarak durum kodlarından kaçınmalıdır. ServiceSpecificException veya EX_SERVICE_SPECIFIC sayfasına göz atın. Bu değerler, kural gereği bir AIDL arayüzünde sabit olarak tanımlanır. Bir hatayla birlikte özel bir gecikme veya benzersiz hata verileri gerekiyorsa özel yanıt nesnesi yalnızca bu durumda bir 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ış parametreleri olan yöntemler genellikle iyi değildir. Bunun nedeni, çıkış dizisi boyutunun Java'da istemci tarafından bildirilmesi ve ayrılması gerektiğinden dizi çıkışının boyutunun sunucu tarafından seçilememesidir. Bu istenmeyen davranış, Java'da dizilerin çalışma şeklinden kaynaklanır (yeniden tahsis edilemezler). Bunun yerine String[] foo() gibi API'leri tercih edin.
6. "inout" parametrelerinden kaçının.
[-Winout-parameter] in parametreleri bile out parametreleri gibi göründüğünden bu durum istemcilerin kafasını karıştırabilir.
7. Dizi olmayan out ve inout @nullable parametrelerinden kaçının
[-Wout-nullable] Java arka ucu, diğer arka uçların aksine @nullable ek açıklamasını 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 çıkış parametresini @nullable
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 Parcelable'ları 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 dörtten fazla parametre varsa bu kalıbın kullanılması önemle tavsiye edilir.
Ek giriş veya çıkış gerektirmeyen yöntemler bu öneriden yararlanamaz. Her bir durumu açıkça düşünmek ve gelecekteki değişikliklere karşı esnek olmak, kullanımdan kaldırılan yöntemlerin sayısını azaltabilir ve geriye dönük uyumlu kodun karmaşıklığını azaltabilir.
Bu kalıp kullanılarak oluşturulmamış bir yöntem varsa istek ve yanıt Parcelable'ı içeren yeni bir yöntem oluşturup eski yöntemi kullanımdan kaldı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ünüz olduğunda yapılandırılmış parcelable'lar kullanın.
Tek bir veri türünüz olduğunda ancak gelecekte bunu genişletmeniz gerekeceğini tahmin ettiğinizde de bu yöntemi kullanabilirsiniz. Örneğin, String username kullanmayın. Aşağıdaki gibi genişletilebilir bir parcelable kullanın:
parcelable User {
String username;
}
Böylece gelecekte bu kodu aşağıdaki gibi genişletebilirsiniz:
parcelable User {
String username;
int id;
}
2. Varsayılanları açıkça belirtme
[-Wexplicit-default, -Wenum-explicit-default] Alanlar için açık varsayılan değerler sağlayın. Parcelable'a 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 genişletmesi gereken bir AOSP parcelable tanımlarsanız nesnenize bir ParcelableHolder örneği yerleştirin. Bu, birleştirme çakışmaları oluşturmadan bir uzantı noktası görevi görür. Bu, ekli 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 öğelerini eklemelerine olanak tanır.
4. Veri yapıları
- AIDL, tüm yerel arka uçlarda güvenli bir şekilde çevrilen
Maptürlerini (ör.FeatureToScoreEntry[]) yerel olarak desteklemediğinden haritaları temsil etmek için diziler veya parcelableListkullanın. - Gelecekte paralel dizilere ihtiyaç duyulmasını önlemek için yinelenen alanlarda temel öğe dizileri yerine
parcelablenesne dizileri kullanın. - IPC üzerinden serileştirilmiş dizeler veya JSON yerine kesin tür belirtimli
parcelablenesneleri kullanın. - Gelecekte genişletmeye olanak tanımak için durumlar için boole değerleri yerine enum'lar kullanın. Bit maskeleri için bazı arka uçlarda zahmetli tür dönüştürme işlemlerinden kaçınmak amacıyla
enumtürleri yerineconst intkullanın.
Yapılandırılmamış Parcelable nesneleri
1. Ne zaman kullanılır?
Yapılandırılmamış parcelable'lar, Java'da @JavaOnlyStableParcelable ile, NDK arka ucunda ise @NdkOnlyStableParcelable ile kullanılabilir. Genellikle bunlar, yapılandırılamayan
eski ve mevcut parcelable'lardır.
Sabitler ve enum veri türleri
1. Bit alanları, sabit alanlar kullanmalıdır
Bit alanları, sabit alanlar kullanmalıdır (ör. 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 firma işlevlerinin yukarı akışa aktarılması tercih edilmelidir. Ancak bazı durumlarda özel tedarikçi firma değerlerine izin verilebilir (tedarikçi firmaların bunu sürümlendirmek için bir mekanizması olmalıdır, belki de AIDL'nin kendisi, birbirleriyle çakışmamalı ve bu değerler üçüncü taraf uygulamalara gösterilmemelidir).
3. "NUM_ELEMENTS" gibi değerlerden kaçının.
Numaralandırmalar sürümlendirildiğinden, kaç değer olduğunu gösteren değerlerden kaçınılmalıdır. C++'ta bu sorun enum_range<> ile çözülebilir. Rust için enum_values() kullanın. Java'da henüz bir çözüm yoktur.
Önerilmez: Numaralandırılmış değerler kullanma
@Backing(type="int")
enum FruitType {
APPLE = 0,
BANANA = 1,
MANGO = 2,
NUM_TYPES, // BAD
}
4. Gereksiz ön ek ve son eklerden kaçının
[-Wredundant-name] Sabitlerde ve numaralandırıcılarda gereksiz veya tekrarlayan önek ve soneklerden kaçını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 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ınmazsa 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çlar için FileDescriptor, otomatik olarak kapatılabilen unique_fd ile eşlendiğinden güvendesiniz. Ancak kullanacağınız arka uç dilinden bağımsız olarak, FileDescriptor kullanmamanız akıllıca olacaktır. Çünkü bu, gelecekte arka uç dilini değiştirme özgürlüğünüzü kısıtlar.
Bunun yerine, otomatik olarak kapatılabilen ParcelFileDescriptor kullanın.
Değişken birimler
Değişken birimlerinin, dokümanlara başvurmaya gerek kalmadan iyi tanımlanması ve anlaşılması 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üreli işlemleri eşzamansız (oneway) bir arayüzle gerçekleştirin.
Bir hizmet, istemcilerine güvenmiyorsa istemcilerden aldığı tüm geri çağırmalar oneway arayüzleri olmalıdır. Bu sayede istemcilerin hizmeti süresiz olarak engellemesi önlenir.
İleriye doğru çağrı, giriş bağımsız değişkenleri ve sonuçları almak için geri çağırma arayüzünden oluşan eşzamansız API'leri yapılandırın. Bağımsız değişkenlerle ilgili öneriler için Benzersiz istekler ve yanıtlar kullanma bölümüne bakın.