Android Arayüz Tanımlama Dili (AIDL)

Android Arayüz Tanımlama Dili (AIDL), diğer IDL'lere benzer: Ara işlem iletişimi (IPC) kullanarak birbirleriyle iletişim kurmak için hem istemcinin hem de hizmetin üzerinde anlaştığı programlama arayüzünü tanımlamanızı sağlar.

Android'de, bir işlem normalde başka bir işlemin belleğine erişemez. Konuşmak için nesneleri, işletim sisteminin anlayabileceği ve sizin için söz konusu sınırın ötesindeki nesnelere ayırabileceği temel öğelere ayırmaları gerekir. Bu işlemi yapmak için gereken kodu yazmak yorucu olduğundan Android, AIDL ile bunu sizin için halleder.

Not: AIDL yalnızca farklı uygulamalardan gelen istemcilerin IPC için hizmetinize erişmesine izin verirseniz ve hizmetinizde çoklu iş parçacıklarını işlemek istiyorsanız gereklidir. Farklı uygulamalarda eş zamanlı IPC gerçekleştirmeniz gerekmiyorsa bir Binder uygulayarak arayüzünüzü oluşturun. IPC gerçekleştirmek istiyorsanız ancak çoklu iş parçacıklarını işlemeniz gerekmiyorsa Messenger kullanarak arayüzünüzü uygulayın. Her şeye rağmen, bir 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 iş parçacığı hakkında varsayımda bulunmayın. Ne olacağı, çağrının yerel işlemdeki bir iş parçacığından mı yoksa uzak bir işlemden mi geldiğine bağlı olarak değişir:

  • Yerel işlemden yapılan çağrılar, aramayı yapan 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 yürütülmeye devam eder. Başka bir iş parçacığı söz konusuysa kodunuzu hizmette yürüten iş parçacığıdır. Bu nedenle, hizmete yalnızca yerel iş parçacıkları erişiyorsa hizmette hangi iş parçacıklarının yürütüldüğünü tamamen kontrol edebilirsiniz. Ancak bu durumda hiç AIDL kullanmayın. Bunun yerine, arayüzü bir Binder uygulayarak oluşturun.
  • Uzak bir işlemden gelen çağrılar, platformun kendi sürecinizde tuttuğu bir iş parçacığı havuzundan gönderilir. Birden fazla arama aynı anda gerçekleştiğinden, bilinmeyen ileti dizilerinden gelen aramalara karşı hazırlıklı olun. Başka bir deyişle, AIDL arayüzünün uygulanması tamamen iş parçacığı açısından güvenli olmalıdır. Aynı uzak nesnedeki bir iş parçacığından yapılan çağrılar, alıcı tarafında sırasıyla gelir.
  • oneway anahtar kelimesi, uzaktan aramaların davranışını değiştirir. Bu numara kullanıldığında uzaktan çağrı engellenmez. İşlem verilerini gönderir ve hemen geri döner. Arayüz uygulaması sonuçta bunu Binder iş parçacığı havuzundan normal bir uzak çağrı gibi normal bir çağrı olarak alır. oneway yerel bir çağrıyla kullanılıyorsa bunun herhangi bir etkisi olmaz ve arama yine de eşzamanlı 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 hizmeti barındıran uygulamanın ve hizmete bağlanan diğer tüm uygulamaların kaynak kodundaki src/ dizinine kaydedin.

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

AIDL 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şturma

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

  2. Arayüzü uygulama

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

  3. Arayüzü istemcilere sunma

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

Dikkat: İlk sürümünüzden sonra AIDL arayüzünde yaptığınız değişiklikler, hizmetinizi kullanan diğer uygulamaların bozulmasını önlemek için geriye dönük uyumlu kalmalıdır. Diğer bir deyişle, hizmetinizin arayüzüne erişebilmeleri için .aidl dosyanızın diğer uygulamalara kopyalanması gerektiğinden, orijinal arayüz desteğini sürdürmeniz gerekir.

.aidl dosyasını oluşturma

AIDL, parametre alabilen ve değer döndürebilen bir veya daha fazla yöntem içeren bir arayüz bildirmenize olanak tanıyan basit bir söz dizimi kullanır. Parametreler ve dönüş değerleri, 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şturmalısınız. 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 temel türler (int, long, char, boolean vb.)
  • int[] gibi basit tür dizileri
  • String
  • CharSequence
  • List

    List içindeki tüm öğeler, bu listede desteklenen veri türlerinden biri veya beyan ettiğiniz AIDL tarafından oluşturulan diğer arayüzlerden ya da paketlerden biri olmalıdır. List, isteğe bağlı olarak List<String> gibi parametreleştirilmiş bir tür sınıfı olarak kullanılabilir. Diğer tarafın aldığı gerçek somut sınıf her zaman ArrayList olur, ancak yöntem List arayüzünü kullanmak için oluşturulmuştur.

  • Map

    Map içindeki tüm öğeler, bu listede desteklenen veri türlerinden biri veya beyan ettiğiniz AIDL tarafından oluşturulan diğer arayüzlerden ya da paketlerden biri olmalıdır. Map<String,Integer> biçimindeki haritalar gibi parametreleştirilmiş tür haritalar desteklenmez. Diğer tarafın aldığı gerçek somut sınıf her zaman HashMap olur, ancak yöntem Map arayüzünü kullanacak şekilde oluşturulur. Map yerine bir Bundle kullanabilirsiniz.

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

Hizmet arayüzünüzü tanımlarken aşağıdaki hususlara dikkat edin:

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

    Temel kavramlar, String, IBinder ve AIDL tarafından oluşturulan arayüzler varsayılan olarak in'dir ve farklı olamaz.

    Dikkat: Marşal parametreleri pahalı olduğundan, yönü gerçekten gerekenlerle sınırlandırın.

  • İçe aktarma ve paket ifadelerinden önceki yorumlar hariç, .aidl dosyasındaki tüm kod yorumları, oluşturulan IBinder arayüzüne dahil edilir.
  • Dize ve tam sayı sabitleri, AIDL arayüzünde const int VERSION = 1; gibi tanımlanabilir.
  • Yöntem çağrıları, normalde arayüzdeki bir yöntem dizinine dayalı olan bir transact() kodu ile gönderilir. Bu durum sürüm oluşturmayı zorlaştırdığından işlem kodunu şu yönteme manuel olarak atayabilirsiniz: void method() = 10;.
  • Boş değer atanabilir bağımsız değişkenler ve dönüş türlerine @nullable kullanılarak ek açıklama eklenmelidir.

Örnek .aidl dosyası:

// 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ştururken 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şiyor ancak .java uzantısına sahip. Örneğin IRemoteService.aidl, IRemoteService.java ile sonuçlanır.

Android Studio kullanıyorsanız artımlı derleme, bağlayıcı sınıfını neredeyse anında oluşturur. Android Studio kullanmıyorsanız Gradle aracı, uygulamanızı oluşturduğunuz bir sonraki seferde bağlayıcı sınıfını oluşturur. .aidl dosyasını yazmayı bitirir bitirmez gradle assembleDebug veya gradle assembleRelease ile projenizi oluşturun, böylece kodunuz oluşturulan sınıfla bağlantı kurabilir.

Arayüzü uygulama

Uygulamanızı derlerken Android SDK araçları, .aidl dosyanızın adını taşıyan bir .java arayüz dosyası oluşturur. Oluşturulan arayüz, üst arayüzünün soyut bir uygulaması (ör. YourInterface.Stub) olan Stub adlı bir alt sınıf içerir ve .aidl dosyasındaki tüm yöntemleri açıklar.

Not: Stub birkaç yardımcı yöntem de tanımlar (özellikle asInterface()). Bu yöntem, genellikle istemcinin onServiceConnected() geri çağırma yöntemine iletilen IBinder işlemini alır ve saplama arayüzünün bir örneğini döndürür. Bu yayını oluşturma hakkında daha fazla bilgi için IPC yöntemini ç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 ve anonim bir örnek kullanılarak IRemoteService adlı arayüzün örnek uygulaması 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.
    }
};

binder artık hizmetin IPC arayüzünü tanımlayan Stub sınıfının (Binder) bir örneğidir. Bir sonraki adımda bu örnek, hizmetle etkileşim kurabilmeleri için müşterilere gösterilir.

AIDL arayüzünüzü uygularken birkaç kurala dikkat edin:

  • Gelen çağrıların ana iş parçacığında yürütüleceği garanti edilmez. Bu nedenle, çoklu iş parçacığı işlemeyi en baştan düşünmeniz ve hizmetinizi iş parçacığı açısından güvenli olacak şekilde düzgün bir şekilde oluşturmanız gerekir.
  • IPC çağrıları varsayılan olarak eşzamanlıdır. Hizmetin bir isteği tamamlamasının birkaç milisaniyeden uzun sürdüğünü biliyorsanız çağrıyı etkinliğin ana iş parçacığından yapmayın. Uygulama kilitlenebilir ve Android'de "Uygulama Yanıt Vermiyor" iletişim kutusu görüntülenebilir. Bunu istemcideki ayrı bir ileti 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 sunun

Arayüzü hizmetinize uyguladıktan sonra bağlanabilmeleri için arayüzü istemcilerin kullanımına sunmanız gerekir. Hizmetinizin arayüzünü genişletmek için Service kapsamını genişletin ve önceki bölümde açıklandığı gibi, sınıfınızın oluşturulan Stub kodunu uygulayan bir örneğini döndürmek amacıyla onBind() 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 bir istemci (ör. bir etkinlik), bu hizmete bağlanmak için bindService() yöntemini çağırdığında istemcinin onServiceConnected() geri çağırma işlemi, 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. Bu nedenle, istemci ve hizmet ayrı uygulamalardaysa istemcinin uygulamasının src/ dizininde .aidl dosyasının bir kopyası bulunmalıdır. Bu kopya android.os.Binder arayüzünü oluşturarak istemcinin AIDL yöntemlerine erişmesini sağlar.

İstemci onServiceConnected() geri çağırmasında IBinder değerini aldığında döndürülen parametreyi YourServiceInterface türüne yayınlamak için YourServiceInterface.Stub.asInterface(service) yöntemini ç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'daki RemoteService.java sınıfına bakın.

IPC üzerinden nesne geçirme

Android 10'da (API düzeyi 29 veya sonraki sürümler) 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, yeniden pazarlama kodunu ve özel bir sınıfı manuel olarak yazmak için gereken ek iş yükünü önler. Ancak bu, temel bir yapı oluşturur. Özel erişimciler veya başka işlevler isteniyorsa bunun yerine Parcelable öğesini 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 otomatik olarak left, top, right ve bottom tam sayı alanlarına sahip bir Java sınıfı oluşturur. İlgili tüm sıralama kodu otomatik olarak uygulanır ve nesne, herhangi bir uygulama eklemek zorunda kalmadan doğrudan kullanılabilir.

Ayrıca, IPC arayüzü aracılığıyla bir işlemden diğerine özel sınıf gönderebilirsiniz. Ancak, sınıfınızın kodunun IPC kanalının diğer tarafında kullanılabildiğinden ve sınıfınızın Parcelable arayüzünü desteklemesi gerekir. Parcelable desteği, Android sisteminin nesneleri işlemler genelinde yönetilebilecek temel öğelere ayırmasını sağladığı için önemlidir.

Parcelable özelliğini destekleyen bir özel 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 alan ve bunu bir Parcel öğesine yazan writeToParcel işlemini uygulayın.
  3. Sınıfınıza, Parcelable.Creator arayüzünü uygulayan nesne olan CREATOR adlı statik bir alan ekleyin.
  4. Son olarak, aşağıdaki Rect.aidl dosyasında gösterildiği gibi, ayrıştırılabilir sınıfınızı tanımlayan bir .aidl dosyası oluşturun.

    Özel bir derleme işlemi kullanıyorsanız .aidl dosyasını derlemenize eklemeyin. C dilindeki bir üstbilgi dosyasına benzer şekilde, bu .aidl dosyası derlenmemiştir.

AIDL, nesnelerinizi sıralamak ve ayırmak için oluşturduğu kodda bu yöntemleri ve alanları kullanır.

Örneğin, ayrıştırılabilir bir Rect sınıfı oluşturmak için aşağıdaki Rect.aidl dosyasını kullanabilirsiniz:

package android.graphics;

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

Şimdi de Rect sınıfının Parcelable protokolünü nasıl uyguladığına dair bir örneği görebilirsiniz.

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 öğesine yazabileceğiniz diğer değer türlerini görmek için Parcel hizmetindeki diğer yöntemlere göz atın.

Uyarı: Diğer işlemlerden veri almanın güvenlik açısından etkilerini unutmayın. Bu durumda, Rect, Parcel kapsamındaki dört sayı okur. Ancak bunların, çağrıyı yapanın yapmaya çalıştığı şey için kabul edilebilir değer aralığında olduğundan emin olmak sizin sorumluluğunuzdadır. Uygulamanızı kötü amaçlı yazılımlara karşı nasıl güvende tutacağınızla ilgili daha fazla bilgi için Güvenlik ipuçlarını inceleyin.

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

Bir yöntem ayrıştırılabilir öğeler içermesi beklenen bir Bundle nesnesini kabul ediyorsa Bundle öğesinden okumayı denemeden önce Bundle.setClassLoader(ClassLoader) çağırarak Bundle classloader'ını ayarladığınızdan emin olun. Aksi takdirde, ayrıştırılabilir öğe uygulamanızda doğru bir şekilde tanımlanmış olsa bile ClassNotFoundException ile 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 ClassLoader, Rect okunmadan önce Bundle içinde açıkça ayarlanmıştı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öntemini çağırma

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

  1. .aidl dosyasını projenin src/ dizinine ekleyin.
  2. AIDL'ye göre oluşturulan IBinder arayüzünün bir örneğini beyan edin.
  3. ServiceConnection uygulamasını uygulayın.
  4. ServiceConnection uygulamanızı geçen Context.bindService() numaralı telefonu 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) yöntemini çağırın.
  6. Arayüzünüzde tanımladığınız yöntemleri çağırın. Bağlantı kopduğunda atılan DeadObjectException istisnalarını her zaman yakala. Ayrıca, IPC yöntem çağrısına dahil olan iki işlem çakışan AIDL tanımlarına sahip olduğunda atılan tuzak SecurityException istisnalarıdır.
  7. Bağlantıyı kesmek için arayüzünüzün örneğiyle birlikte Context.unbindService() yöntemini çağırın.

Bir IPC hizmeti çağırırken şu noktaları aklınızda bulundurun:

  • Nesneler işlemler genelinde 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 edinmek için Sınır hizmetlerine genel bakış bölümünü okuyun.

ApiDemos projesindeki Uzak Hizmet örneğinden alınan ve AIDL tarafından oluşturulan bir hizmetin çağrılmasını gösteren örnek kod aşağıda 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);
            }
        }
    }
}