Android Arayüz Tanımlama Dili (AIDL)

Android Arayüz Tanımı Dili (AIDL), diğer IDL'lere benzer: istemci ve hizmetin, işlemler arası iletişim (IPC) kullanarak birbirleriyle iletişim kurmak için üzerinde anlaştığı programlama arayüzünü tanımlamanıza olanak tanır.

Android'de bir işlem normalde başka bir işlemin belleğine erişemez. Birbirleriyle iletişim kurabilmeleri için nesnelerini işletim sisteminin anlayabileceği temel öğelere ayırmaları ve bu sınırda nesneleri sizin için sıralamaları gerekir. Bu marshalling işlemini yapacak kodu yazmak zahmetli olduğundan Android, AIDL ile bu işlemi sizin için yapar.

Not: AIDL yalnızca farklı uygulamalardaki istemcilerin IPC için hizmetinize erişmesine izin verirseniz ve hizmetinizde çoklu iş parçacığı işlemeyi kullanmak istiyorsanız gereklidir. Farklı uygulamalarda eşzamanlı IPC gerçekleştirmeniz gerekmiyorsa Binder uygulayarak arayüzünüzü oluşturun. IPC gerçekleştirmek istiyorsanız ancak çoklu iş parçacığı işlemeniz gerekmiyorsa arayüzünüzü Messenger kullanarak uygulayın. Yine de AIDL uygulamadan önce bağlı hizmetleri anladığınızdan emin olun.

AIDL arayüzünüzü tasarlamaya başlamadan önce, AIDL arayüzüne yapılan çağrıların doğrudan işlev çağrıları olduğunu unutmayın. Çağrının gerçekleştiği ileti dizisi hakkında varsayımlarda bulunmayın. Ne olacağı, aramanın yerel işlemdeki bir ileti dizisinden mi yoksa uzak bir işlemdeki bir ileti dizisinden mi geldiğine bağlı olarak değişir:

  • Yerel işlemden yapılan çağrılar, çağrıyı yapan aynı iş parçacığında yürütülür. Bu, ana kullanıcı arayüzü iş parçacığınızsa bu iş parçacığı AIDL arayüzünde çalışmaya devam eder. Başka bir iş parçacığıysa hizmette kodunuzu yürüten iş parçacığı budur. Bu nedenle, hizmete yalnızca yerel iş parçacıkları erişiyorsa hizmette hangi iş parçacıklarının çalıştığını tamamen kontrol edebilirsiniz. Ancak bu durumda AIDL'yi hiç kullanmayın. Bunun yerine, Binder uygulayarak arayüzü oluşturun.
  • Uzak bir süreçten gelen çağrılar, platformun kendi süreciniz içinde tuttuğu bir iş parçacığı havuzundan dağıtılır. Aynı anda gerçekleşen birden fazla arama olan bilinmeyen mesaj dizilerinden gelen aramalara hazırlıklı olun. Diğer bir deyişle, bir AIDL arayüzünün uygulaması tamamen iş parçacığı güvenli olmalıdır. Aynı uzak nesnedeki bir iş parçacığından yapılan çağrılar, alıcı ucunda sıralı gelir.
  • oneway anahtar kelimesi, uzak çağrıların davranışını değiştirir. Bu özellik kullanıldığında uzaktan arama engellenmez. İşlem verilerini gönderir ve hemen geri döner. Arayüz uygulaması, bunu normal bir uzaktan çağrı olarak Binder iş parçacığı havuzundan normal bir çağrı olarak alır. oneway yerel bir aramayla birlikte kullanılırsa herhangi bir etki olmaz ve arama senkronize olarak devam eder.

AIDL arayüzü tanımlama

AIDL arayüzünüzü Java programlama dili söz dizimini kullanarak bir .aidl dosyasında tanımlayın, ardından hem hizmeti barındıran uygulamanın hem de hizmete bağlanan diğer uygulamaların kaynak kodundaki src/ dizinine kaydedin.

.aidl dosyasını içeren her uygulamayı derlediğinizde, Android SDK araçları .aidl dosyasına dayalı bir IBinder arayüzü oluşturur ve bunu projenin gen/ dizinine kaydeder. Hizmet, IBinder arayüzünü uygun şekilde uygulamalıdır. Ardından istemci uygulamalar, IPC gerçekleştirmek için IBinder üzerinden hizmete bağlanabilir ve yöntemleri çağırabilir.

AIDL'yi kullanarak sınırlı bir hizmet oluşturmak için aşağıdaki bölümlerde açıklanan adımları uygulayın:

  1. .aidl dosyasını oluşturun

    Bu dosya, yöntem imzaları içeren programlama arayüzünü tanımlar.

  2. Arayüzü uygulama

    Android SDK Tools, .aidl dosyanıza göre Java programlama dilinde bir arayüz oluşturur. Bu arayüzde, Binder sınıfını genişleten ve AIDL arayüzünüzdeki yöntemleri uygulayan Stub adlı bir iç soyut sınıf bulunur. Stub sınıfını genişletmeniz ve bu yöntemleri uygulamanız gerekir.

  3. Arayüzü istemcilere gösterme

    Stub sınıfını uygulamanızı döndürmek için bir Service uygulayın ve onBind()'u geçersiz kılın.

Dikkat: İlk sürümünüzden sonra AIDL arayüzünüzde yaptığınız değişiklikler, hizmetinizi kullanan diğer uygulamaların çalışmasını engellememek için geriye dönük uyumlu kalmalıdır. Yani, .aidl dosyanız diğer uygulamaların hizmetinizin arayüzüne erişebilmesi için diğer uygulamalara kopyalanacağından orijinal arayüz için desteği sürdürmeniz gerekir.

.aidl dosyasını oluşturma

AIDL, parametre alabilen ve değer döndürebilen bir veya daha fazla yöntemle bir arayüzü belirtmenizi sağlayan basit bir söz dizimi kullanır. Parametreler ve döndürülen değerler, AIDL tarafından oluşturulan diğer arayüzler de dahil olmak üzere herhangi bir türde olabilir.

.aidl dosyasını Java programlama dilini kullanarak oluşturmanız gerekir. Her .aidl dosyası tek bir arayüz tanımlamalıdır ve yalnızca arayüz beyanı ile yöntem imzalarını gerektirir.

AIDL varsayılan olarak aşağıdaki veri türlerini destekler:

  • Java programlama dilindeki tüm ilkel türler (int, long, char, boolean vb.)
  • int[] veya MyParcelable[] gibi her türden dizi
  • String
  • CharSequence
  • List

    List içindeki tüm öğeler, bu listedeki desteklenen veri türlerinden veya AIDL tarafından oluşturulan diğer arayüzlerden ya da beyan ettiğiniz diğer ürünlerden biri olmalıdır. List isteğe bağlı olarak List<String> gibi parametreli bir tür sınıfı olarak kullanılabilir. Yöntem, List arayüzünü kullanacak şekilde oluşturulsa da diğer tarafın aldığı gerçek somut sınıf her zaman bir ArrayList olur.

  • Map

    Map içindeki tüm öğeler, bu listedeki desteklenen veri türlerinden veya tanımladığınız diğer AIDL tarafından oluşturulan arayüzlerden ya da parcelable'lerden biri olmalıdır. Map<String,Integer> biçimindekiler gibi parametreli tür eşlemeleri desteklenmez. Yöntem Map arayüzünü kullanacak şekilde oluşturulsa da diğer tarafın aldığı gerçek somut sınıf her zaman bir HashMap olur. Map yerine Bundle kullanabilirsiniz.

Daha önce listelenmeyen her ek tür için, arayüzünüzle aynı pakette tanımlanmış olsalar bile bir import ifadesi eklemeniz gerekir.

Hizmet arayüzünüzü tanımlarken aşağıdakileri göz önünde bulundurun:

  • Yöntemler sıfır veya daha fazla parametre alabilir ve bir değer ya da boşluk döndürebilir.
  • Basit olmayan tüm parametreler, verilerin hangi yönde aktığını belirten bir yön etiketi gerektirir: in, out veya inout (aşağıdaki örneğe bakın).

    Temel öğeler, String, IBinder ve AIDL tarafından oluşturulan arayüzler varsayılan olarak in olur ve başka bir şekilde olamaz.

    Dikkat: Yönlendirmeyi, gerçekten ihtiyaç duyulanla sınırlayın. Çünkü parametrelerin düzenlenmesi pahalıdır.

  • İçe aktarma ve paket beyanlarından önceki yorumlar hariç olmak üzere, .aidl dosyasına dahil edilen tüm kod yorumları oluşturulan IBinder arayüzüne dahil edilir.
  • Dize ve int sabitleri, AIDL arayüzünde const int VERSION = 1; gibi tanımlanabilir.
  • Yöntem çağrıları, normalde arayüzdeki bir yöntem dizinini temel alan bir transact() kodu tarafından gönderilir. Bu durum sürüm oluşturmayı zorlaştırdığından, işlem kodunu bir yönteme manuel olarak atayabilirsiniz: void method() = 10;.
  • Boş bırakılabilir bağımsız değişkenler ve döndürülen türlerin @nullable ile eklenmesi gerekir.

Aşağıda bir .aidl dosyası örneği verilmiştir:

// IRemoteService.aidl
package com.example.android;

// Declare any non-default types here with import statements.

/** Example service interface */
interface IRemoteService {
    /** Request the process ID of this service. */
    int getPid();

    /** Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

.aidl dosyanızı projenizin src/ dizinine kaydedin. Uygulamanızı oluşturduğunuzda SDK araçları, projenizin gen/ dizininde IBinder arayüz dosyasını oluşturur. Oluşturulan dosyanın adı, .aidl dosyasının adıyla eşleşir ancak .java uzantısına sahiptir. Örneğin, IRemoteService.aidl IRemoteService.java sonucunu verir.

Android Studio kullanıyorsanız artımlı derleme neredeyse anında bağlayıcı sınıfı oluşturur. Android Studio kullanmıyorsanız Gradle aracı, uygulamanızı bir sonraki sefer oluşturduğunuzda bağlayıcı sınıfını oluşturur. Kodunuzun oluşturulan sınıfa bağlantı oluşturabilmesi için .aidl dosyasını yazmayı bitirir bitirmez projenizi gradle assembleDebug veya gradle assembleRelease ile derleyin.

Arayüzü uygulama

Uygulamanızı oluşturduğunuzda Android SDK araçları, .aidl dosyanızı temel alan bir .java arayüz dosyası oluşturur. Oluşturulan arayüz, YourInterface.Stub gibi üst arayüzünün soyut bir uygulaması olan ve .aidl dosyasındaki tüm yöntemleri açıklayan Stub adlı bir alt sınıf içerir.

Not: Stub, birkaç yardımcı yöntem de tanımlar. Bunlardan en önemlisi, genellikle istemcinin onServiceConnected() geri çağırma yöntemine iletilen bir IBinder alan ve stub arayüzünün bir örneğini döndüren asInterface() yöntemidir. Bu yayını yapma hakkında daha fazla bilgi için IPC yöntemi çağırma bölümüne bakın.

.aidl öğesinden oluşturulan arayüzü uygulamak için oluşturulan Binder arayüzünü (ör. YourInterface.Stub) genişletin ve .aidl dosyasından devralınan yöntemleri uygulayın.

Aşağıda, önceki IRemoteService.aidl örneğinde tanımlanan IRemoteService adlı bir arayüzün anonim bir örnek kullanılarak uygulandığı örnek verilmiştir:

Kotlin

private val binder = object : IRemoteService.Stub() {

    override fun getPid(): Int =
            Process.myPid()

    override fun basicTypes(
            anInt: Int,
            aLong: Long,
            aBoolean: Boolean,
            aFloat: Float,
            aDouble: Double,
            aString: String
    ) {
        // Does nothing.
    }
}

Java

private final IRemoteService.Stub binder = new IRemoteService.Stub() {
    public int getPid(){
        return Process.myPid();
    }
    public void basicTypes(int anInt, long aLong, boolean aBoolean,
        float aFloat, double aDouble, String aString) {
        // Does nothing.
    }
};

Artık binder, hizmetin IPC arayüzünü tanımlayan Stub sınıfının bir örneğidir (Binder). Sonraki adımda, bu örnek istemcilere gösterilir. Böylece istemciler hizmetle etkileşime geçebilir.

AIDL arayüzünüzü uygularken birkaç kuralı göz önünde bulundurun:

  • Gelen aramaların ana iş parçacığında yürütülmesi garanti edilmez. Bu nedenle, baştan itibaren çoklu iş parçacığı kullanmayı düşünmeniz ve hizmetinizi iş parçacığı güvenliğine uygun olacak şekilde doğru şekilde oluşturmanız gerekir.
  • Varsayılan olarak IPC çağrıları eşzamanlıdır. Hizmetin bir isteği tamamlamasının birkaç milisaniyeden uzun sürdüğünü biliyorsanız hizmeti işlemin ana iş parçacığında çağırmayın. Bu durum, uygulamanın kilitlenmesine ve Android'in "Uygulama Yanıt Vermiyor" iletişim kutusunu görüntülemesine neden olabilir. İstemcide ayrı bir mesaj dizisinden çağırın.
  • Yalnızca Parcel.writeException() ile ilgili referans dokümanlarında listelenen istisna türleri, arayana geri gönderilir.

Arayüzü istemcilere gösterme

Hizmetinizin arayüzünü uyguladıktan sonra, istemcilerin bağlanabilmesi için arayüzü istemcilere göstermeniz gerekir. Hizmetinizin arayüzünü göstermek için Service sınıfını genişletin ve önceki bölümde açıklandığı gibi, oluşturulan Stub sınıfını uygulayan sınıfınızın bir örneğini döndürmek için onBind() sınıfını uygulayın. Aşağıda, IRemoteService örnek arayüzünü istemcilere gösteren bir örnek hizmet verilmiştir.

Kotlin

class RemoteService : Service() {

    override fun onCreate() {
        super.onCreate()
    }

    override fun onBind(intent: Intent): IBinder {
        // Return the interface.
        return binder
    }


    private val binder = object : IRemoteService.Stub() {
        override fun getPid(): Int {
            return Process.myPid()
        }

        override fun basicTypes(
                anInt: Int,
                aLong: Long,
                aBoolean: Boolean,
                aFloat: Float,
                aDouble: Double,
                aString: String
        ) {
            // Does nothing.
        }
    }
}

Java

public class RemoteService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface.
        return binder;
    }

    private final IRemoteService.Stub binder = new IRemoteService.Stub() {
        public int getPid(){
            return Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) {
            // Does nothing.
        }
    };
}

Artık etkinlik gibi bir istemci bu hizmete bağlanmak için bindService() yöntemini çağırdığında, istemcinin onServiceConnected() geri çağırması, hizmetin onBind() yöntemi tarafından döndürülen binder örneğini alır.

İstemcinin arayüz sınıfına da erişimi olmalıdır. Dolayısıyla istemci ve hizmet ayrı uygulamalardaysa istemcinin uygulamasında src/ dizininde .aidl dosyasının bir kopyası olmalıdır. Bu kopya, android.os.Binder arayüzünü oluşturur ve istemciye AIDL yöntemlerine erişim sağlar.

Müşteri, onServiceConnected() geri çağırma işlevinde IBinder aldığında, döndürülen parametreyi YourServiceInterface türüne dönüştürmek için YourServiceInterface.Stub.asInterface(service) işlevini çağırmalıdır:

Kotlin

var iRemoteService: IRemoteService? = null

val mConnection = object : ServiceConnection {

    // Called when the connection with the service is established.
    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        // Following the preceding example for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service.
        iRemoteService = IRemoteService.Stub.asInterface(service)
    }

    // Called when the connection with the service disconnects unexpectedly.
    override fun onServiceDisconnected(className: ComponentName) {
        Log.e(TAG, "Service has unexpectedly disconnected")
        iRemoteService = null
    }
}

Java

IRemoteService iRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established.
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Following the preceding example for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service.
        iRemoteService = IRemoteService.Stub.asInterface(service);
    }

    // Called when the connection with the service disconnects unexpectedly.
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        iRemoteService = null;
    }
};

Daha fazla örnek kod için ApiDemos'taki RemoteService.java sınıfına bakın.

IPC üzerinden nesne iletme

Android 10'da (API düzeyi 29 veya üstü) Parcelable nesnelerini doğrudan AIDL'de tanımlayabilirsiniz. AIDL arayüz bağımsız değişkenleri ve diğer ayrıştırılabilir öğeler olarak desteklenen türler de burada desteklenir. Bu sayede, marshalling kodunu ve özel sınıfı manuel olarak yazmak için ek iş gerekmez. Ancak bu işlem, boş bir yapı da oluşturur. Özel erişim sağlayıcılar veya başka bir işlev isteniyorsa bunun yerine Parcelable uygulayın.

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect {
    int left;
    int top;
    int right;
    int bottom;
}

Önceki kod örneği, left, top, right ve bottom tam sayı alanlarına sahip bir Java sınıfını otomatik olarak oluşturur. Alakalı tüm düzenleme kodları otomatik olarak uygulanır ve nesne herhangi bir uygulama eklemek zorunda kalmadan doğrudan kullanılabilir.

Ayrıca, bir IPC arayüzü aracılığıyla özel bir sınıfı bir işlemden diğerine gönderebilirsiniz. Ancak sınıfınızın kodunun IPC kanalının diğer tarafında kullanılabildiğinden emin olun ve sınıfınız Parcelable arayüzünü desteklemelidir. Parcelable desteklemek, Android sisteminin nesneleri işlemler arasında düzenlenebilecek temel öğelere ayırmasına olanak tanıdığı için önemlidir.

Parcelable özelliğini destekleyen özel bir sınıf oluşturmak için aşağıdakileri yapın:

  1. Sınıfınızın Parcelable arayüzünü uygulamasını sağlayın.
  2. Nesnenin mevcut durumunu alıp bir Parcel'a yazan writeToParcel işlevini uygulayın.
  3. Sınıfınıza Parcelable.Creator arayüzünü uygulayan bir nesne olan CREATOR adlı statik bir alan ekleyin.
  4. Son olarak, aşağıdaki Rect.aidl dosyasında gösterildiği gibi, paketlenebilir sınıfınızı açıklayan bir .aidl dosyası oluşturun.

    Özel bir derleme süreci kullanıyorsanız .aidl dosyasını derlemenize eklemeyin. C dilinde bir başlık dosyasına benzer şekilde, bu .aidl dosyası derlenmez.

AIDL, oluşturduğu kodda bu yöntemleri ve alanları kullanarak nesnelerinizi sıralar ve sıfırlar.

Örneğin, aşağıdaki Rect.aidl dosyası, bölünebilir bir Rect sınıfı oluşturmak için kullanılır:

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;

Aşağıda, Rect sınıfının Parcelable protokolünü nasıl uyguladığına dair bir örnek verilmiştir.

Kotlin

import android.os.Parcel
import android.os.Parcelable

class Rect() : Parcelable {
    var left: Int = 0
    var top: Int = 0
    var right: Int = 0
    var bottom: Int = 0

    companion object CREATOR : Parcelable.Creator<Rect> {
        override fun createFromParcel(parcel: Parcel): Rect {
            return Rect(parcel)
        }

        override fun newArray(size: Int): Array<Rect?> {
            return Array(size) { null }
        }
    }

    private constructor(inParcel: Parcel) : this() {
        readFromParcel(inParcel)
    }

    override fun writeToParcel(outParcel: Parcel, flags: Int) {
        outParcel.writeInt(left)
        outParcel.writeInt(top)
        outParcel.writeInt(right)
        outParcel.writeInt(bottom)
    }

    private fun readFromParcel(inParcel: Parcel) {
        left = inParcel.readInt()
        top = inParcel.readInt()
        right = inParcel.readInt()
        bottom = inParcel.readInt()
    }

    override fun describeContents(): Int {
        return 0
    }
}

Java

import android.os.Parcel;
import android.os.Parcelable;

public final class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;

    public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
        public Rect createFromParcel(Parcel in) {
            return new Rect(in);
        }

        public Rect[] newArray(int size) {
            return new Rect[size];
        }
    };

    public Rect() {
    }

    private Rect(Parcel in) {
        readFromParcel(in);
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(left);
        out.writeInt(top);
        out.writeInt(right);
        out.writeInt(bottom);
    }

    public void readFromParcel(Parcel in) {
        left = in.readInt();
        top = in.readInt();
        right = in.readInt();
        bottom = in.readInt();
    }

    public int describeContents() {
        return 0;
    }
}

Rect sınıfında sıralama basittir. Parcel değerine yazabileceğimiz diğer değer türlerini görmek için Parcel'teki diğer yöntemlere göz atın.

Uyarı: Diğer işlemlerden veri almanın güvenlikle ilgili sonuçlarını göz önünde bulundurun. Bu durumda Rect, Parcel'dan dört sayı okur ancak bunların, arayanın yapmaya çalıştığı işlem için kabul edilebilir değer aralığında olduğundan emin olmak size aittir. Uygulamanızı kötü amaçlı yazılımlara karşı nasıl koruyacağınız hakkında daha fazla bilgi için Güvenlik ipuçları bölümüne bakın.

Parcelable içeren Bundle bağımsız değişkenlerine sahip yöntemler

Bir yöntem, ayrıştırılabilir öğe içermesi beklenen bir Bundle nesnesini kabul ediyorsa Bundle öğesinden okumayı denemeden önce Bundle.setClassLoader(ClassLoader) yöntemini çağırarak Bundle sınıf yükleyicisini ayarladığınızdan emin olun. Aksi takdirde, paketlenebilir uygulamanızda doğru tanımlanmış olsa bile ClassNotFoundException hatasıyla karşılaşırsınız.

Örneğin, aşağıdaki örnek .aidl dosyasını ele alalım:

// IRectInsideBundle.aidl
package com.example.android;

/** Example service interface */
interface IRectInsideBundle {
    /** Rect parcelable is stored in the bundle with key "rect". */
    void saveRect(in Bundle bundle);
}
Aşağıdaki uygulamada gösterildiği gibi, Rect okunmadan önce ClassLoader, Bundle içinde açıkça ayarlanır:

Kotlin

private val binder = object : IRectInsideBundle.Stub() {
    override fun saveRect(bundle: Bundle) {
      bundle.classLoader = classLoader
      val rect = bundle.getParcelable<Rect>("rect")
      process(rect) // Do more with the parcelable.
    }
}

Java

private final IRectInsideBundle.Stub binder = new IRectInsideBundle.Stub() {
    public void saveRect(Bundle bundle){
        bundle.setClassLoader(getClass().getClassLoader());
        Rect rect = bundle.getParcelable("rect");
        process(rect); // Do more with the parcelable.
    }
};

IPC yöntemi çağırma

AIDL ile tanımlanmış bir uzak arayüzü çağırmak için görüşme sınıfınızda aşağıdaki adımları uygulayın:

  1. .aidl dosyasını src/ projesinin dizinine ekleyin.
  2. AIDL'ye göre oluşturulan IBinder arayüzünün bir örneğini tanımlayın.
  3. ServiceConnection'ü uygulayın.
  4. ServiceConnection uygulamanızı göndererek Context.bindService()'ü arayın.
  5. onServiceConnected() uygulamanızda, service adlı bir IBinder örneği alırsınız. Döndürülen parametreyi YourInterface türüne yayınlamak için YourInterfaceName.Stub.asInterface((IBinder)service) komutunu çağırın.
  6. Arayüzünüzde tanımladığınız yöntemleri çağırın. Bağlantı kesildiğinde oluşturulan DeadObjectException istisnalarını her zaman yakalayın. Ayrıca, IPC yöntem çağrısında yer alan iki işlemde çakışan AIDL tanımları olduğunda tetiklenen SecurityException istisnalarını da yakalayabilirsiniz.
  7. Bağlantıyı kesmek için arayüzünüzün örneğini kullanarak Context.unbindService() işlevini çağırın.

IPC hizmetini çağırırken aşağıdaki noktaları göz önünde bulundurun:

  • Nesneler, süreçler genelinde referans olarak sayılır.
  • Anonim nesneleri yöntem bağımsız değişkenleri olarak gönderebilirsiniz.

Bir hizmete bağlama hakkında daha fazla bilgi için Bağlı hizmetlere genel bakış başlıklı makaleyi okuyun.

Aşağıda, ApiDemos projesindeki Remote Service örneğinden alınan, AIDL ile oluşturulmuş bir hizmeti çağırma işlemini gösteren örnek bir kod verilmiştir.

Kotlin

private const val BUMP_MSG = 1

class Binding : Activity() {

    /** The primary interface you call on the service.  */
    private var mService: IRemoteService? = null

    /** Another interface you use on the service.  */
    internal var secondaryService: ISecondary? = null

    private lateinit var killButton: Button
    private lateinit var callbackText: TextView
    private lateinit var handler: InternalHandler

    private var isBound: Boolean = false

    /**
     * Class for interacting with the main interface of the service.
     */
    private val mConnection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // This is called when the connection with the service is
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = IRemoteService.Stub.asInterface(service)
            killButton.isEnabled = true
            callbackText.text = "Attached."

            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                mService?.registerCallback(mCallback)
            } catch (e: RemoteException) {
                // In this case, the service crashes before we can
                // do anything with it. We can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }

            // As part of the sample, tell the user what happened.
            Toast.makeText(
                    this@Binding,
                    R.string.remote_service_connected,
                    Toast.LENGTH_SHORT
            ).show()
        }

        override fun onServiceDisconnected(className: ComponentName) {
            // This is called when the connection with the service is
            // unexpectedly disconnected&mdash;that is, its process crashed.
            mService = null
            killButton.isEnabled = false
            callbackText.text = "Disconnected."

            // As part of the sample, tell the user what happened.
            Toast.makeText(
                    this@Binding,
                    R.string.remote_service_disconnected,
                    Toast.LENGTH_SHORT
            ).show()
        }
    }

    /**
     * Class for interacting with the secondary interface of the service.
     */
    private val secondaryConnection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // Connecting to a secondary interface is the same as any
            // other interface.
            secondaryService = ISecondary.Stub.asInterface(service)
            killButton.isEnabled = true
        }

        override fun onServiceDisconnected(className: ComponentName) {
            secondaryService = null
            killButton.isEnabled = false
        }
    }

    private val mBindListener = View.OnClickListener {
        // Establish a couple connections with the service, binding
        // by interface names. This lets other applications be
        // installed that replace the remote service by implementing
        // the same interface.
        val intent = Intent(this@Binding, RemoteService::class.java)
        intent.action = IRemoteService::class.java.name
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
        intent.action = ISecondary::class.java.name
        bindService(intent, secondaryConnection, Context.BIND_AUTO_CREATE)
        isBound = true
        callbackText.text = "Binding."
    }

    private val unbindListener = View.OnClickListener {
        if (isBound) {
            // If we have received the service, and hence registered with
            // it, then now is the time to unregister.
            try {
                mService?.unregisterCallback(mCallback)
            } catch (e: RemoteException) {
                // There is nothing special we need to do if the service
                // crashes.
            }

            // Detach our existing connection.
            unbindService(mConnection)
            unbindService(secondaryConnection)
            killButton.isEnabled = false
            isBound = false
            callbackText.text = "Unbinding."
        }
    }

    private val killListener = View.OnClickListener {
        // To kill the process hosting the service, we need to know its
        // PID.  Conveniently, the service has a call that returns
        // that information.
        try {
            secondaryService?.pid?.also { pid ->
                // Note that, though this API lets us request to
                // kill any process based on its PID, the kernel
                // still imposes standard restrictions on which PIDs you
                // can actually kill. Typically this means only
                // the process running your application and any additional
                // processes created by that app, as shown here. Packages
                // sharing a common UID are also able to kill each
                // other's processes.
                Process.killProcess(pid)
                callbackText.text = "Killed service process."
            }
        } catch (ex: RemoteException) {
            // Recover gracefully from the process hosting the
            // server dying.
            // For purposes of this sample, put up a notification.
            Toast.makeText(this@Binding, R.string.remote_call_failed, Toast.LENGTH_SHORT).show()
        }
    }

    // ----------------------------------------------------------------------
    // Code showing how to deal with callbacks.
    // ----------------------------------------------------------------------

    /**
     * This implementation is used to receive callbacks from the remote
     * service.
     */
    private val mCallback = object : IRemoteServiceCallback.Stub() {
        /**
         * This is called by the remote service regularly to tell us about
         * new values.  Note that IPC calls are dispatched through a thread
         * pool running in each process, so the code executing here is
         * NOT running in our main thread like most other things. So,
         * to update the UI, we need to use a Handler to hop over there.
         */
        override fun valueChanged(value: Int) {
            handler.sendMessage(handler.obtainMessage(BUMP_MSG, value, 0))
        }
    }

    /**
     * Standard initialization of this activity.  Set up the UI, then wait
     * for the user to interact with it before doing anything.
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.remote_service_binding)

        // Watch for button taps.
        var button: Button = findViewById(R.id.bind)
        button.setOnClickListener(mBindListener)
        button = findViewById(R.id.unbind)
        button.setOnClickListener(unbindListener)
        killButton = findViewById(R.id.kill)
        killButton.setOnClickListener(killListener)
        killButton.isEnabled = false

        callbackText = findViewById(R.id.callback)
        callbackText.text = "Not attached."
        handler = InternalHandler(callbackText)
    }

    private class InternalHandler(
            textView: TextView,
            private val weakTextView: WeakReference<TextView> = WeakReference(textView)
    ) : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                BUMP_MSG -> weakTextView.get()?.text = "Received from service: ${msg.arg1}"
                else -> super.handleMessage(msg)
            }
        }
    }
}

Java

public static class Binding extends Activity {
    /** The primary interface we are calling on the service. */
    IRemoteService mService = null;
    /** Another interface we use on the service. */
    ISecondary secondaryService = null;

    Button killButton;
    TextView callbackText;

    private InternalHandler handler;
    private boolean isBound;

    /**
     * Standard initialization of this activity. Set up the UI, then wait
     * for the user to interact with it before doing anything.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.remote_service_binding);

        // Watch for button taps.
        Button button = (Button)findViewById(R.id.bind);
        button.setOnClickListener(mBindListener);
        button = (Button)findViewById(R.id.unbind);
        button.setOnClickListener(unbindListener);
        killButton = (Button)findViewById(R.id.kill);
        killButton.setOnClickListener(killListener);
        killButton.setEnabled(false);

        callbackText = (TextView)findViewById(R.id.callback);
        callbackText.setText("Not attached.");
        handler = new InternalHandler(callbackText);
    }

    /**
     * Class for interacting with the main interface of the service.
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // This is called when the connection with the service is
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = IRemoteService.Stub.asInterface(service);
            killButton.setEnabled(true);
            callbackText.setText("Attached.");

            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                mService.registerCallback(mCallback);
            } catch (RemoteException e) {
                // In this case the service crashes before we can even
                // do anything with it. We can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }

            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_connected,
                    Toast.LENGTH_SHORT).show();
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service is
            // unexpectedly disconnected&mdash;that is, its process crashed.
            mService = null;
            killButton.setEnabled(false);
            callbackText.setText("Disconnected.");

            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_disconnected,
                    Toast.LENGTH_SHORT).show();
        }
    };

    /**
     * Class for interacting with the secondary interface of the service.
     */
    private ServiceConnection secondaryConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // Connecting to a secondary interface is the same as any
            // other interface.
            secondaryService = ISecondary.Stub.asInterface(service);
            killButton.setEnabled(true);
        }

        public void onServiceDisconnected(ComponentName className) {
            secondaryService = null;
            killButton.setEnabled(false);
        }
    };

    private OnClickListener mBindListener = new OnClickListener() {
        public void onClick(View v) {
            // Establish a couple connections with the service, binding
            // by interface names. This lets other applications be
            // installed that replace the remote service by implementing
            // the same interface.
            Intent intent = new Intent(Binding.this, RemoteService.class);
            intent.setAction(IRemoteService.class.getName());
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            intent.setAction(ISecondary.class.getName());
            bindService(intent, secondaryConnection, Context.BIND_AUTO_CREATE);
            isBound = true;
            callbackText.setText("Binding.");
        }
    };

    private OnClickListener unbindListener = new OnClickListener() {
        public void onClick(View v) {
            if (isBound) {
                // If we have received the service, and hence registered with
                // it, then now is the time to unregister.
                if (mService != null) {
                    try {
                        mService.unregisterCallback(mCallback);
                    } catch (RemoteException e) {
                        // There is nothing special we need to do if the service
                        // crashes.
                    }
                }

                // Detach our existing connection.
                unbindService(mConnection);
                unbindService(secondaryConnection);
                killButton.setEnabled(false);
                isBound = false;
                callbackText.setText("Unbinding.");
            }
        }
    };

    private OnClickListener killListener = new OnClickListener() {
        public void onClick(View v) {
            // To kill the process hosting our service, we need to know its
            // PID.  Conveniently, our service has a call that returns
            // that information.
            if (secondaryService != null) {
                try {
                    int pid = secondaryService.getPid();
                    // Note that, though this API lets us request to
                    // kill any process based on its PID, the kernel
                    // still imposes standard restrictions on which PIDs you
                    // can actually kill.  Typically this means only
                    // the process running your application and any additional
                    // processes created by that app as shown here. Packages
                    // sharing a common UID are also able to kill each
                    // other's processes.
                    Process.killProcess(pid);
                    callbackText.setText("Killed service process.");
                } catch (RemoteException ex) {
                    // Recover gracefully from the process hosting the
                    // server dying.
                    // For purposes of this sample, put up a notification.
                    Toast.makeText(Binding.this,
                            R.string.remote_call_failed,
                            Toast.LENGTH_SHORT).show();
                }
            }
        }
    };

    // ----------------------------------------------------------------------
    // Code showing how to deal with callbacks.
    // ----------------------------------------------------------------------

    /**
     * This implementation is used to receive callbacks from the remote
     * service.
     */
    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
        /**
         * This is called by the remote service regularly to tell us about
         * new values.  Note that IPC calls are dispatched through a thread
         * pool running in each process, so the code executing here is
         * NOT running in our main thread like most other things. So,
         * to update the UI, we need to use a Handler to hop over there.
         */
        public void valueChanged(int value) {
            handler.sendMessage(handler.obtainMessage(BUMP_MSG, value, 0));
        }
    };

    private static final int BUMP_MSG = 1;

    private static class InternalHandler extends Handler {
        private final WeakReference<TextView> weakTextView;

        InternalHandler(TextView textView) {
            weakTextView = new WeakReference<>(textView);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case BUMP_MSG:
                    TextView textView = weakTextView.get();
                    if (textView != null) {
                        textView.setText("Received from service: " + msg.arg1);
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
}