Bluetooth cihazları bağlama

İki cihaz arasında bağlantı oluşturmak için hem sunucu tarafı hem de istemci tarafı mekanizmalarını uygulamanız gerekir. Çünkü bir cihazın sunucu yuvasını açması ve diğerinin sunucu cihazının MAC adresini kullanarak bağlantıyı başlatması gerekir. Sunucu cihazı ve istemci cihaz, gerekli BluetoothSocket'i farklı şekillerde edinir. Gelen bağlantı kabul edildiğinde sunucu yuva bilgilerini alır. İstemci, sunucuya bir RFCOMM kanalı açtığında yuva bilgilerini sağlar.

Sunucu ve istemcinin her biri aynı RFCOMM kanalında bağlı bir BluetoothSocket'e sahip olduğunda birbirine bağlı olarak kabul edilir. Bu noktada, her cihaz giriş ve çıkış akışlarını alabilir ve veri aktarımı başlayabilir. Bu konu, Bluetooth verilerini aktarma konulu bölümde ele alınmaktadır. Bu bölümde, iki cihaz arasında bağlantının nasıl başlatılacağı açıklanmaktadır.

Bluetooth cihazları bulmaya çalışmadan önce uygun Bluetooth izinlerine sahip olduğunuzdan ve uygulamanızı Bluetooth için ayarladığınızdan emin olun.

Bağlantı teknikleri

Uygulama tekniklerinden biri, her cihazı otomatik olarak sunucu şeklinde hazırlamaktır. Böylece her cihazda açık bir sunucu yuvası olur ve bağlantıları dinleyebilirsiniz. Bu durumda, cihazlardan biri diğeriyle bağlantı başlatabilir ve istemci haline gelebilir. Alternatif olarak, bir cihaz bağlantıyı açıkça barındırıp istek üzerine bir sunucu yuvası açabilir ve diğer cihaz bağlantıyı başlatır.


Şekil 1. Bluetooth eşleme iletişim kutusu.

Sunucu olarak bağlan

İki cihazı birbirine bağlamak istediğinizde, cihazlardan biri açık BluetoothServerSocket tutarak sunucu görevini yapmalıdır. Sunucu yuvasının amacı, gelen bağlantı isteklerini dinlemek ve istek kabul edildikten sonra bağlı bir BluetoothSocket sağlamaktır. BluetoothSocket, BluetoothServerSocket kaynağından edinildiğinde, cihazın daha fazla bağlantı kabul etmesini istemiyorsanız BluetoothServerSocket silinebilir ve silinmelidir.

Bir sunucu yuvası kurmak ve bağlantıyı kabul etmek için aşağıdaki adımları uygulayın:

  1. listenUsingRfcommWithServiceRecord(String, UUID) numaralı telefonu arayarak BluetoothServerSocket edinin.

    Dize, hizmetinizin tanımlayıcı bir adıdır. Bu ad, sistem tarafından otomatik olarak cihazdaki yeni bir Hizmet Keşif Protokolü (SDP) veritabanı girişine yazılır. Bu ad rastgeledir ve uygulamanızın adı olabilir. Evrensel Benzersiz Tanımlayıcı (UUID) da SDP girişine dahil edilir ve istemci cihazla bağlantı sözleşmesinin temelini oluşturur. Yani istemci bu cihaza bağlanmaya çalıştığında, bağlanmak istediği hizmeti benzersiz şekilde tanımlayan bir UUID taşır. Bağlantının kabul edilmesi için bu UUID'ler eşleşmelidir.

    UUID, bilgileri benzersiz bir şekilde tanımlamak için kullanılan bir dize kimliği için standartlaştırılmış 128 bitlik bir biçimdir. UUID'nin tekrarlanma olasılığı neredeyse sıfır olduğu için bir sistem veya ağ içinde benzersiz olması gereken bilgileri tanımlamak için kullanılır. Merkezi bir otoriteye ihtiyaç duyulmadan, bağımsız olarak oluşturulur. Bu durumda, uygulamanızın Bluetooth hizmetini benzersiz bir şekilde tanımlamak için kullanılır. Uygulamanızla kullanmak üzere bir UUID almak için web'deki çok sayıda rastgele UUID oluşturucudan birini kullanabilir, ardından fromString(String) ile bir UUID başlatabilirsiniz.

  2. accept() numaralı telefonu arayarak bağlantı isteklerini dinlemeye başlayın.

    Bu engelleme amaçlı bir arama. Bir bağlantı kabul edildiğinde veya bir istisna oluştuğunda döndürülür. Bağlantı, yalnızca uzak cihaz bu dinleme sunucusu soketine kayıtlı olan UUID ile eşleşen bir bağlantı isteği gönderdiğinde kabul edilir. Başarılı olduğunda accept(), bağlı bir BluetoothSocket döndürür.

  3. Ek bağlantıları kabul etmek istemiyorsanız close() numaralı telefonu arayın.

    Bu yöntem çağrısı, sunucu yuvasını ve tüm kaynaklarını serbest bırakır ancak accept() tarafından döndürülen bağlı BluetoothSocket öğesini kapatmaz. TCP/IP'nin aksine RFCOMM, aynı anda kanal başına yalnızca bir bağlı istemciye izin verir. Bu nedenle çoğu durumda, bağlı bir yuvayı kabul ettikten hemen sonra BluetoothServerSocket üzerinde close() çağrısı yapmak mantıklıdır.

accept() çağrısı bir engelleme çağrısı olduğundan, bunu ana etkinlik kullanıcı arayüzü iş parçacığında yürütmeyin. Kodu başka bir iş parçacığında yürütmek, uygulamanızın diğer kullanıcı etkileşimlerine hâlâ yanıt verebilmesini sağlar. BluetoothServerSocket veya BluetoothSocket içeren tüm işleri uygulamanız tarafından yönetilen yeni bir iş parçacığında yapmak genellikle mantıklıdır. accept() gibi engellenmiş bir çağrıyı iptal etmek için başka bir iş parçacığından BluetoothServerSocket veya BluetoothSocket üzerinde close() çağrısı yapın. BluetoothServerSocket veya BluetoothSocket üzerindeki tüm yöntemlerin iş parçacığı güvenli olduğunu unutmayın.

Örnek

Aşağıda, gelen bağlantıları kabul eden sunucu bileşeni için basitleştirilmiş bir iş parçacığı verilmiştir:

Kotlin

private inner class AcceptThread : Thread() {

   private val mmServerSocket: BluetoothServerSocket? by lazy(LazyThreadSafetyMode.NONE) {
       bluetoothAdapter?.listenUsingInsecureRfcommWithServiceRecord(NAME, MY_UUID)
   }

   override fun run() {
       // Keep listening until exception occurs or a socket is returned.
       var shouldLoop = true
       while (shouldLoop) {
           val socket: BluetoothSocket? = try {
               mmServerSocket?.accept()
           } catch (e: IOException) {
               Log.e(TAG, "Socket's accept() method failed", e)
               shouldLoop = false
               null
           }
           socket?.also {
               manageMyConnectedSocket(it)
               mmServerSocket?.close()
               shouldLoop = false
           }
       }
   }

   // Closes the connect socket and causes the thread to finish.
   fun cancel() {
       try {
           mmServerSocket?.close()
       } catch (e: IOException) {
           Log.e(TAG, "Could not close the connect socket", e)
       }
   }
}

Java

private class AcceptThread extends Thread {
   private final BluetoothServerSocket mmServerSocket;

   public AcceptThread() {
       // Use a temporary object that is later assigned to mmServerSocket
       // because mmServerSocket is final.
       BluetoothServerSocket tmp = null;
       try {
           // MY_UUID is the app's UUID string, also used by the client code.
           tmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
       } catch (IOException e) {
           Log.e(TAG, "Socket's listen() method failed", e);
       }
       mmServerSocket = tmp;
   }

   public void run() {
       BluetoothSocket socket = null;
       // Keep listening until exception occurs or a socket is returned.
       while (true) {
           try {
               socket = mmServerSocket.accept();
           } catch (IOException e) {
               Log.e(TAG, "Socket's accept() method failed", e);
               break;
           }

           if (socket != null) {
               // A connection was accepted. Perform work associated with
               // the connection in a separate thread.
               manageMyConnectedSocket(socket);
               mmServerSocket.close();
               break;
           }
       }
   }

   // Closes the connect socket and causes the thread to finish.
   public void cancel() {
       try {
           mmServerSocket.close();
       } catch (IOException e) {
           Log.e(TAG, "Could not close the connect socket", e);
       }
   }
}

Bu örnekte, yalnızca bir gelen bağlantı istenir. Bu nedenle bir bağlantı kabul edilip BluetoothSocket edinildiğinde, uygulama alınan BluetoothSocket öğesini ayrı bir iş parçacığına iletir, BluetoothServerSocket öğesini kapatır ve döngüden çıkar.

accept(), BluetoothSocket değerini döndürdüğünde yuvanın zaten bağlı olduğunu unutmayın. Bu nedenle, istemci tarafında yaptığınız gibi connect() çağrısı yapmamalısınız.

Uygulamaya özel manageMyConnectedSocket() yöntemi, veri aktarımına yönelik ileti dizisini başlatmak için tasarlanmıştır. Bu yöntem, Bluetooth verilerini aktarma konulu başlıkta ele alınmaktadır.

Genellikle gelen bağlantıları dinlemeyi bitirir bitirmez BluetoothServerSocket cihazınızı kapatmanız gerekir. Bu örnekte, close() BluetoothSocket edinilir edinmez çağrılır. Sunucu soketinde dinlemeyi bırakmanız gerekirse iş parçacığınızda özel BluetoothSocket öğesini kapatabilecek bir herkese açık yöntem de sağlayabilirsiniz.

İstemci olarak bağlanın

Açık sunucu yuvasında bağlantıları kabul eden uzak bir cihazla bağlantı başlatmak için önce uzak cihazı temsil eden bir BluetoothDevice nesnesi edinmeniz gerekir. Nasıl BluetoothDevice oluşturacağınızı öğrenmek için Bluetooth cihazları bulma bölümüne bakın. Ardından bir BluetoothSocket edinmek ve bağlantıyı başlatmak için BluetoothDevice kullanmanız gerekir.

Temel prosedür aşağıdaki gibidir:

  1. BluetoothDevice ile createRfcommSocketToServiceRecord(UUID) numaralı telefonu arayarak BluetoothSocket kazanın.

    Bu yöntem, istemcinin bir BluetoothDevice öğesine bağlanmasına olanak tanıyan bir BluetoothSocket nesnesini başlatır. Buraya iletilen UUID, BluetoothServerSocket açmak için listenUsingRfcommWithServiceRecord(String, UUID) çağrılırken sunucu cihazı tarafından kullanılan UUID ile eşleşmelidir. Eşleşen bir UUID kullanmak için UUID dizesini uygulamanıza sabit bir şekilde kodlayın ve ardından hem sunucu hem de istemci kodundan bu dizeye referans verin.

  2. connect() yöntemini çağırarak bağlantıyı başlatın. Bu yöntemin engelleme çağrısı olduğunu unutmayın.

    Bir istemci bu yöntemi çağırdıktan sonra sistem, eşleşen UUID'ye sahip uzak cihazı bulmak için bir SDP araması gerçekleştirir. Arama başarılı olursa ve uzak cihaz bağlantıyı kabul ederse bağlantı sırasında kullanmak üzere RFCOMM kanalını paylaşır ve connect() yöntemi geri döner. Bağlantı başarısız olursa veya connect() yöntemi zaman aşımına uğrarsa (yaklaşık 12 saniye sonra) yöntem bir IOException gönderir.

connect() engelleme çağrısı olduğundan bu bağlantı prosedürünü her zaman ana etkinlik (kullanıcı arayüzü) iş parçacığından ayrı bir iş parçacığında gerçekleştirmeniz gerekir.

Örnek

Aşağıda, Bluetooth bağlantısı başlatan bir istemci iş parçacığına temel bir örnek verilmiştir:

Kotlin

private inner class ConnectThread(device: BluetoothDevice) : Thread() {

   private val mmSocket: BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE) {
       device.createRfcommSocketToServiceRecord(MY_UUID)
   }

   public override fun run() {
       // Cancel discovery because it otherwise slows down the connection.
       bluetoothAdapter?.cancelDiscovery()

       mmSocket?.let { socket ->
           // Connect to the remote device through the socket. This call blocks
           // until it succeeds or throws an exception.
           socket.connect()

           // The connection attempt succeeded. Perform work associated with
           // the connection in a separate thread.
           manageMyConnectedSocket(socket)
       }
   }

   // Closes the client socket and causes the thread to finish.
   fun cancel() {
       try {
           mmSocket?.close()
       } catch (e: IOException) {
           Log.e(TAG, "Could not close the client socket", e)
       }
   }
}

Java

private class ConnectThread extends Thread {
   private final BluetoothSocket mmSocket;
   private final BluetoothDevice mmDevice;

   public ConnectThread(BluetoothDevice device) {
       // Use a temporary object that is later assigned to mmSocket
       // because mmSocket is final.
       BluetoothSocket tmp = null;
       mmDevice = device;

       try {
           // Get a BluetoothSocket to connect with the given BluetoothDevice.
           // MY_UUID is the app's UUID string, also used in the server code.
           tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
       } catch (IOException e) {
           Log.e(TAG, "Socket's create() method failed", e);
       }
       mmSocket = tmp;
   }

   public void run() {
       // Cancel discovery because it otherwise slows down the connection.
       bluetoothAdapter.cancelDiscovery();

       try {
           // Connect to the remote device through the socket. This call blocks
           // until it succeeds or throws an exception.
           mmSocket.connect();
       } catch (IOException connectException) {
           // Unable to connect; close the socket and return.
           try {
               mmSocket.close();
           } catch (IOException closeException) {
               Log.e(TAG, "Could not close the client socket", closeException);
           }
           return;
       }

       // The connection attempt succeeded. Perform work associated with
       // the connection in a separate thread.
       manageMyConnectedSocket(mmSocket);
   }

   // Closes the client socket and causes the thread to finish.
   public void cancel() {
       try {
           mmSocket.close();
       } catch (IOException e) {
           Log.e(TAG, "Could not close the client socket", e);
       }
   }
}

Bu snippet'te cancelDiscovery(), bağlantı girişimi gerçekleşmeden önce çağrılır. Özellikle cihaz keşfinin o anda devam edip etmediğinden bağımsız olarak cancelDiscovery() başarılı olduğundan connect() öncesinde her zaman cancelDiscovery() çağırmanız gerekir. Uygulamanızın, cihaz keşfinin devam edip etmediğini belirlemesi gerekiyorsa isDiscovering() kullanarak kontrol edebilirsiniz.

Uygulamaya özel manageMyConnectedSocket() yöntemi, Bluetooth verilerinin aktarılması ile ilgili bölümde ele alınan veri aktarımı iş parçacığını başlatmak için tasarlanmıştır.

BluetoothSocket ile işiniz bittiğinde her zaman close() numaralı telefonu arayın. Bu işlem, bağlı yuvayı hemen kapatır ve ilgili tüm dahili kaynakları serbest bırakır.