IPC Yöntemleri 1 - AIDL

IPC (Inter Process Communication), process'ler arası haberleşme anlamına gelen genel bir kavramdır.


Android özelinde ise,

  • Uygulamalar arası haberleşme

  • Multi-process bir uygulamadaki process'lerin haberleşmesi (Activity, Service vb. bileşenleri farklı process'lerde çalıştırılmış uygulama)

anlamlarına gelebilir. Bu yazıda uygulamalar arası haberleşme üzerine örnekler oluşturacağız.


Android'de IPC için kullanılan 3 temel yöntem var:

  1. AIDL

  2. Messenger

  3. Broadcast

IPC için Linux sistemlerde kullanılan geleneksel yöntemleri (socket haberleşmesi, paylaşımlı dosyalar gibi) kullanmak da mümkün fakat güvenlik sebebiyle iletişim kurmak isteyen uygulamaya kimlik doğrulaması yapabiliyor olmamız gibi artılarından dolayı yukarıda saydığımız Android'e has yöntemleri tercih etmemiz öneriliyor.


Bu yazıda 3 yöntemin de nasıl kullanılacağına bakacağız.



AIDL


Bu yöntemi, multithreaded bir IPC yapısına ihtiyaç duyuyorsanız kullanmalısınız. Eğer concurrent (eş zamanlı) işlemler yapmayacaksanız AIDL yerine Messenger'ı tercih edebilirsiniz.


Bu yöntemde, bir uygulama aynı cihazdaki bir başka uygulamadaki metodu çağırır, yani RPC (Remote Procedure Call) yapar. Bu metotlar birçok farklı process/thread'den eş zamanlı olarak çağrılabileceği için, çağrıları ele alacağımız mekanizmanın thread-safe olmasına özen göstermemiz gerekir.


Peki AIDL nedir? IPC kavramına aşina iseniz AIDL dosyasını Android-IDL (Interface Definition Language) olarak düşünebilirsiniz. Kurulacak iletişim için tarafların el sıkıştığı arayüz AIDL dilinde oluşturulduğu için bu yöntem AIDL olarak bilinir. AIDL, Java syntax'ına benzer bir dildir.


Temel kullanıma bir örnek vermek için IPCServer ve IPCClient isimli iki uygulama oluşturup bunları nasıl haberleştireceğimizi ele alalım. Yazının devamında, sadelik olması açısından bu uygulamalardan sunucu ve istemci diye bahsedeceğim. Uygulamaların kaynak kodlarına yazının sonundaki linkten ulaşabilirsiniz.


İki fonksiyonalite içeren bir senaryoyu kodlayalım;

  • İstemci, sunucunun PID'sini ve sunucuya kaç bağlantı isteği geldiğini sorsun.

  • İstemci, sunucu uygulamasına kendi PID'si ve paket adı gibi bilgilerini göndersin. Bunlar da sunucu uygulamasının grafik arayüzünde son bağlanan istemcinin bilgileri olarak gösterilsin.

Örnek bittiğinde bu şekilde görünecek,


Bu yöntemde iletişim Binder mimarisine dayanır. Bir servis, startService çağırımı ile başlatılabildiği gibi bindService çağırımı ile de başlatılabilir. Servise, hem uygulama içinden hem de bir remote process'ten bind olunabilir. Bind olan bileşene (activity, service) bir binder objesi döndürülür.


Örneğimizde istemci, sunucu uygulamasının servisine bind olacak ve bu sayede sunucu uygulamasında tanımlanan metotlara erişebilecek. Bu yazının konusu olmadığı için Bound Service kavramının detaylarına daha fazla değinmeyeceğiz.



Sunucu Uygulamasının Geliştirilmesi


Öncelikle sunucu uygulaması için yeni bir proje oluşturalım.


İstemciden çağırılacak metotları içeren AIDL uzantılı dosyamızı soldaki project explorer kısmında sağ tıklayarak, New->AIDL->AIDL File yolunu izleyerek oluşturuyoruz. Bu şekilde oluşturduğumuzda dosyamız, otomatik olarak java ve res ile aynı dizinde, aidl klasörü içinde oluşturulmuş oluyor. Örneğin bu projede, src/main/aidl/com/pmirkelam/ipcserver/ dizininde.


Şimdi, AIDL dilindeki bu dosyayı modifiye edelim. Örnek olarak gelen basicTypes metodu yerine kendi metotlarımızı ekliyoruz.


getPid: Sunucu uygulamasının process ID'si

getConnectionCount: Sunucu uygulaması ayakta olduğu süre boyunca kaç kere istemci bağlantısı kurduğu bilgisini tutar.

setDisplayedValue: İstemciye ait bazı bilgileri sunucuya iletelim: istemci paket adı, istemci PID, keyfi bir metin. Sunucu, bu bilgileri grafik arayüzünde gösterecek.

AIDL, metotlarının aldığı parametreler ve dönüş tipleri için bazı kısıtlamalar mevcut. Genel geçer tipler olan primitive tipler, String ve bunlardan oluşan List veya Map gibi veri yapılarını kullanabiliyoruz. Kendi tanımladığımız bir sınıfı kullanmak istersek de sınıfın, işletim sisteminin anlayacağı bir düzeye ayrıştırılabilmesi için parcelable yapmak gerekiyor. Parametre ve dönüş değeri olarak kullanılabilen tüm tipleri aşağıda bulabilirsiniz:

  • Java'daki tüm primitive tipler (int, long, char, boolean, ...)

  • Primitive tiplerden oluşturulan Array'ler

  • String, CharSequence

  • Bu listedeki veri tiplerinden, başka bir AIDL'in ürettiği interface'lerden veya parcelable'lardan oluşturulan List'ler. İşlev, List ile tanımlanmış olmasına rağmen, implement eden taraf bunu ArrayList olarak karşılayacaktır.

  • Bu listedeki veri tiplerinden, başka bir AIDL'in ürettiği interface'lerden veya parcelable'lardan oluşturulan Map'ler. İşlev, Map ile tanımlanmış olmasına rağmen, implement eden taraf bunu HashMap olarak karşılayacaktır. Map'e alternatif olarak Bundle kullanmak da tercih edilebilir.


Primitive tipte olmayan tüm parametreler için in, out ve inout anahtar kelimeleri kullanılarak, parametrenin input için mi output için mi kullanıldığının bildirilmesi gerekir.


Mesela byte array kopyalayan bir metodumuz olduğunu düşünelim. Metodun parametrelerinden hangisinin input, hangisinin output ile ilgili olduğunu belirtmemiz gerekiyor. Aşağıdaki örneği inceleyelim. İstemci, copyArray metodunu çağırırken, dest objesinde veri olmadığı, ancak sunucunun dest objesinde değişiklik yapması gerektiği, böylece istemcinin güncellenmiş dest nesnesini alacağı anlamına gelir.

void copyArray(in byte[] source, out byte[] dest);

inout ise hem input hem output olarak kullanılan bir parametre olduğu anlamına geliyor. Bir örnek daha inceleyelim. İstemci, charsToUpper metodunu çağırırken chars objesinde input değerinde veri var, metot süresince chars objesi güncellenecek, istemci güncellenmiş chars objesini output olarak alacaktır.

void charsToUpper(inout char[] chars);

Peki bu bildirimleri neden yapıyoruz? Bir AIDL metodu çağrıldığında, tüm parametrelerine marshalling denen masraflı işler yapılıyor. Serialized -> Transmitted -> Received -> Deserialized

in veya out ön eklerini kullanarak gereksiz adımlar atlanmış oluyor ve bu sayede performans artışı sağlanıyor.


AIDL dosyamızı oluşturduktan sonra Build -> Rebuild Project adımları ile build aldığımızda aynı isimde .java uzantılı bir interface üretildiğini görebiliriz. Bu interface, tanımladığımız metotları implement eden Stub adında bir abstract alt sınıfa sahiptir. Sunucu servisinde, istemciye iletmek üzere bu sınıfı implement ederek kullanacağız.


İstemci bilgilerinden oluşan Client veri tipimizi ve son bağlanan istemci bilgisini tutacağımız singleton RecentClient sınıfımızı oluşturalım. Burada bağlı istemcilerin listesi de tutulabilirdi ama bu örnekte sadece son bağlantı kuran istemcinin bilgilerini göstereceğiz.


  • Servisi oluşturalım.

  • Stub sınıfındaki metotları implement ederek binder objemizi oluşturalım. Burada metotlara görevlerini atıyoruz.

  • Bind olan istemciye binder objemizi döndürelim.

  • RecentClient objemizi, istemci bind ve unbind olduğu zaman güncelleyelim ki UI güncellensin.

Not: Concurrent (eş zamanlı) işlemler yapacaksanız binder objesinin metotlarında Coroutines kullanılabilir.


Manifest'te servisimize intent filter eklemeyi unutmayalım. Böylece servis, varsayılan olarak exported şeklinde işaretlenmiş olur ve android:exported="true" ibaresini eklememize gerek kalmaz. Bu ifade, diğer process'lerden servise erişilebileceği anlamına gelir.


Burada verdiğimiz action ismini istemci uygulamasında kullanacağız.


Arayüz için XML dosyasını oluşturalım:


String resource dosyamız:


Kod sadeliği için ViewBinding kütüphanesini kullanmayı tercih edebiliriz. Önce gradle'da kullanacağımızı bildirelim.


Artık activity'yi istemci bilgileriyle güncelleyebiliriz.

Son istemcinin bilgileri görüntülenecek, hiç bağlı istemci yok ise detaylar gizlenecek.


Sunucu uygulamasını tamamladık. Bağlantı kurulduğunda bilgiler, bu şekilde görüntülenecek:



İstemci Uygulamasının Geliştirilmesi


İstemci uygulaması için yeni proje oluşturalım.


Yazının devamında, diğer IPC yöntemleri (Broadcast ve Messenger) için aynı uygulamayı kullanacağız. Bu sebeple projeyi oluştururken activity galerisinden Bottom Navigation Activity'yi seçelim. AIDL, Messenger ve Broadcast seçenekleri olsun.



Projeyi açtıktan sonra, sunucu uygulamasında oluşturduğumuz AIDL dosyasının bire bir aynısını burada da tanımlamamız ve yine build almamız gerekiyor. Dosyayı yine, New->AIDL->AIDL File yolunu izleyerek oluşturabiliriz.


Burada dikkat edilmesi gereken husus, dosyanın bulunduğu dizinin iki uygulamada da aynı olması. Dosyayı IDE yardımıyla oluşturduğumuzda, otomatik olarak src/main/aidl/com/pmirkelam/ipcclient/ dizini altında oluşacak. Bunu src/main/aidl/com/pmirkelam/ipcserver/ olarak güncellememiz gerekiyor.


AIDL dosyasını, paket ismine kadar sunucu uygulamasındakinin aynısı olacak şekilde kopyalıyoruz:


/res/menu/ dizinindeki menu dosyasını düzenleyelim:


/res/navigation/ dizinindeki navigasyon dosyasını düzenleyelim:


String resource dosyasını düzenleyelim:



Sunucu uygulamasından farklı görünmesi için temadaki renkleri değiştirelim:



Activity XML dosyasında değişiklik yapmıyoruz.



Activity'de navigation ve binding işlemlerini yapalım.



  • Fragment XML dosyasını oluşturalım:

  • Bağlantı kur-bağlantıyı kes butonunu oluşturalım.

  • Bağlantı yoksa gizli, bağlantı kurulduğunda görünecek olan bir layout ekleyelim.

  • Bu layoutta sunucu uygulamasından aldığımız bilgiler görünsün.



  • Fragment'ta binding işlemlerini yapalım.

  • Connect butonuna basıldığı zaman sunucu servisine bind olalım.

  • Bind olurken servis henüz oluşturulmamış ise otomatik oluşturulması için BIND_AUTO_CREATE flagini ekliyoruz.

  • Bağlantı kurma-kopma durumlarını ServiceConnection arayüzünü implement ederek dinleyelim.

  • Bağlantı kurulduğu zaman ileteceğimiz mesajı, paket adımızı ve PID'mizi sunucu uygulamasına iletelim, sunucudan aldığımız bilgileri de grafik arayüzde çizdirelim.

  • Bağlantı koptuğu zaman, servise unBind olarak arkamızı temiz bırakıyoruz. İşletim sistemi, memory ihtiyacı olduğunda, bir süredir kullanıcı etkileşimi olmayan uygulamalardaki background servisleri öldürebilir. Bound Service'lerde ise bind olan en az bir istemci olduğu sürece, çok uç durumlar hariç, background servisler öldürülmez. Kaynak israfına sebebiyet vermemek için kullanmadığımız halde servise bind olmuş şekilde kalmayalım.



İstemci uygulamamızı tamamladık. Sunucudaki metotları çağırırken grafik arayüzündeki akış bu şekilde olacak:



  • Tamamladığımız bu örnekte, yalnızca istemci sunucuya mesaj gönderebiliyor. Peki, sunucunun istemciye mesaj göndermesi gerektiğinde ne yapabiliriz? Örneğin istemci, sunucunun uzun süren-bloklayan bir metodunu çağırdı ve işlem bittiğinde sunucunun istemciye callback dönmesi gerekiyor.

  • Bu mekanizmayı, aşağıdaki örnekteki gibi, AIDL metoduna parametre olarak bir başka AIDL'i vererek gerçekleştirmek mümkün.

  • AIDL, varsayılan olarak senkron yapıda çalışır. oneway anahtar kelimesi, aşağıdaki gibi asenkron kullanımlarda eklenmesi gereken ön ektir.


Ancak, böyle asenkron bir yapıya ihtiyacınız varsa ve concurrent (eş zamanlı) işlem yapmayacaksanız, AIDL yerine Messenger yöntemini kullanmak daha anlamlı bir çözüm olur. Messenger'da bu denli bir efor sarf etmeksizin çift taraflı iletişimi sağlayabilir, istemciye callback dönebilirsiniz.


Messenger ve Broadcast yöntemlerinin anlatıldığı yazının devamına buradan erişebilirsiniz.


GitHub: github.com/perihanmirkelam/IPC-Examples


Referanslar:


#android #ipc #interprocesscommunication #aidl


Komünite

Platform

Mobiler.dev Anasayfa
  • Twitter
  • Instagram
  • development_düzenlendi_düzenlendi
  • Youtube
  • slack-icon-black_edited_edited_edited
  • Gri LinkedIn Simge
imageedit_2_9667998092.png
JetBrains Hakkında Detaylı Bilgi Alın

© 2020 by mobiler.dev

Kurumsal Yazar Hesapları

adesso.png
mobilerdevLogo.jpg
Yazarlık Başvurusu Hakkında Bilgi Alın, Başvuru Yapın.
Topluluk Yazarlarını Tanıyın