Menghubungkan perangkat Bluetooth

Untuk membuat koneksi antara dua perangkat, Anda harus menerapkan mekanisme sisi server dan sisi klien karena satu perangkat harus membuka soket server, dan perangkat lainnya harus memulai koneksi menggunakan alamat MAC perangkat server. Perangkat server dan perangkat klien masing-masing mendapatkan BluetoothSocket yang diperlukan dengan cara yang berbeda. Server menerima informasi soket saat koneksi masuk diterima. Klien memberikan informasi soket ketika membuka saluran RFCOMM ke server.

Server dan klien dianggap terhubung satu sama lain jika masing-masing memiliki BluetoothSocket yang terhubung pada saluran RFCOMM yang sama. Pada tahap ini, setiap perangkat dapat memperoleh aliran input dan output, serta transfer data dapat dimulai, yang dibahas di bagian tentang mentransfer data Bluetooth. Bagian ini menjelaskan cara memulai koneksi antara dua perangkat.

Pastikan Anda memiliki izin Bluetooth yang sesuai dan menyiapkan aplikasi untuk Bluetooth sebelum mencoba menemukan perangkat Bluetooth.

Teknik koneksi

Salah satu teknik implementasi adalah dengan otomatis menyiapkan setiap perangkat sebagai server sehingga setiap perangkat memiliki soket server yang terbuka dan memproses koneksi. Dalam hal ini, salah satu perangkat dapat memulai koneksi dengan perangkat lainnya dan menjadi klien. Atau, satu perangkat dapat secara eksplisit menghosting koneksi dan membuka soket server sesuai permintaan, dan perangkat lainnya akan memulai koneksi.


Gambar 1. Dialog penyambungan Bluetooth.

Menghubungkan sebagai server

Jika Anda ingin menghubungkan dua perangkat, salah satunya harus bertindak sebagai server dengan menahan BluetoothServerSocket terbuka. Tujuan dari soket server adalah untuk memproses permintaan koneksi yang masuk dan menyediakan BluetoothSocket yang terhubung setelah permintaan diterima. Saat BluetoothSocket diperoleh dari BluetoothServerSocket, BluetoothServerSocket dapat—dan harus—dihapus, kecuali jika Anda ingin perangkat menerima lebih banyak koneksi.

Untuk menyiapkan soket server dan menerima koneksi, selesaikan langkah-langkah berikut:

  1. Dapatkan BluetoothServerSocket dengan memanggil listenUsingRfcommWithServiceRecord(String, UUID).

    String ini adalah nama yang dapat diidentifikasi layanan Anda, yang akan otomatis ditulis oleh sistem ke entri database Service Discovery Protocol (SDP) baru di perangkat. Nama ini tidak tentu dan bisa berupa nama aplikasi Anda. Universally Unique Identifier (UUID) juga disertakan dalam entri SDP dan membentuk dasar untuk perjanjian koneksi dengan perangkat klien. Artinya, saat klien mencoba untuk terhubung dengan perangkat ini, klien tersebut akan membawa UUID yang secara unik mengidentifikasi layanan yang ingin dihubungkan. UUID ini harus cocok agar koneksi dapat diterima.

    UUID adalah format 128-bit terstandardisasi untuk ID string yang digunakan untuk mengidentifikasi informasi secara unik. UUID digunakan untuk mengidentifikasi informasi yang harus unik dalam sistem atau jaringan karena probabilitas UUID yang diulangi pada dasarnya adalah nol. Kode ini dihasilkan secara independen, tanpa menggunakan otoritas terpusat. Dalam hal ini, digunakan untuk mengidentifikasi layanan Bluetooth aplikasi Anda secara unik. Agar UUID dapat digunakan dengan aplikasi, Anda dapat menggunakan salah satu dari banyak generator UUID acak di web, lalu melakukan inisialisasi UUID dengan fromString(String).

  2. Mulai proses permintaan koneksi dengan memanggil accept().

    Ini adalah panggilan pemblokir. Metode akan ditampilkan saat koneksi telah diterima atau terjadi pengecualian. Koneksi hanya diterima jika perangkat jarak jauh telah mengirimkan permintaan koneksi berisi UUID yang cocok dengan yang terdaftar pada soket server pemroses. Jika berhasil, accept() akan menampilkan BluetoothSocket yang terhubung.

  3. Kecuali Anda ingin menyetujui koneksi tambahan, panggil close().

    Panggilan metode ini melepaskan soket server dan semua resource-nya, tetapi tidak menutup BluetoothSocket terhubung yang telah ditampilkan oleh accept(). Tidak seperti TCP/IP, RFCOMM hanya mengizinkan satu klien terhubung per saluran pada satu waktu. Jadi, dalam banyak kasus, sebaiknya segera panggil close() pada BluetoothServerSocket setelah menerima soket yang terhubung.

Karena panggilan accept() adalah panggilan pemblokir, jangan jalankan di UI thread aktivitas utama. Mengeksekusinya di thread lain akan memastikan aplikasi Anda masih dapat merespons interaksi pengguna lainnya. Biasanya masuk akal untuk melakukan semua tugas yang melibatkan BluetoothServerSocket atau BluetoothSocket di thread baru yang dikelola oleh aplikasi Anda. Untuk membatalkan panggilan yang diblokir seperti accept(), panggil close() di BluetoothServerSocket atau BluetoothSocket dari thread lain. Perlu diperhatikan bahwa semua metode pada BluetoothServerSocket atau BluetoothSocket aman untuk thread.

Contoh

Berikut adalah thread yang disederhanakan untuk komponen server yang menerima koneksi masuk:

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);
       }
   }
}

Dalam contoh ini, hanya satu koneksi masuk yang diinginkan, jadi segera setelah koneksi diterima dan BluetoothSocket diperoleh, aplikasi akan meneruskan BluetoothSocket yang diperoleh ke thread terpisah, menutup BluetoothServerSocket, dan keluar dari loop.

Perhatikan bahwa saat accept() menampilkan BluetoothSocket, soket sudah terhubung. Oleh karena itu, Anda tidak boleh memanggil connect(), seperti yang Anda lakukan dari sisi klien.

Metode manageMyConnectedSocket() khusus aplikasi dirancang guna memulai thread untuk mentransfer data, yang akan dibahas dalam topik mentransfer data Bluetooth.

Biasanya, Anda harus segera menutup BluetoothServerSocket setelah selesai memproses koneksi yang masuk. Dalam contoh ini, close() dipanggil segera setelah BluetoothSocket diperoleh. Anda mungkin juga perlu menyediakan metode publik di thread yang dapat menutup BluetoothSocket pribadi jika Anda perlu berhenti memproses soket server tersebut.

Menghubungkan sebagai klien

Untuk memulai koneksi dengan perangkat jarak jauh yang menerima koneksi pada soket server terbuka, Anda harus terlebih dahulu mendapatkan objek BluetoothDevice yang mewakili perangkat jarak jauh. Untuk mempelajari cara membuat BluetoothDevice, lihat Menemukan perangkat Bluetooth. Kemudian, Anda harus menggunakan BluetoothDevice untuk memperoleh BluetoothSocket dan memulai koneksi.

Prosedur dasarnya sebagai berikut:

  1. Dengan menggunakan BluetoothDevice, dapatkan BluetoothSocket dengan memanggil createRfcommSocketToServiceRecord(UUID).

    Metode ini menginisialisasi objek BluetoothSocket yang memungkinkan klien terhubung ke BluetoothDevice. UUID yang diteruskan di sini harus cocok dengan UUID yang digunakan oleh perangkat server saat memanggil listenUsingRfcommWithServiceRecord(String, UUID) untuk membuka BluetoothServerSocket. Untuk menggunakan UUID yang cocok, lakukan hard code string UUID ke dalam aplikasi, lalu referensikan dari server dan kode klien.

  2. Mulai koneksi dengan memanggil connect(). Perhatikan, metode ini adalah panggilan pemblokir.

    Setelah klien memanggil metode ini, sistem akan melakukan pencarian SDP untuk menemukan perangkat jarak jauh dengan UUID yang cocok. Jika pencarian berhasil dan perangkat jarak jauh menerima koneksi, perangkat tersebut akan membagikan saluran RFCOMM untuk digunakan selama koneksi, dan metode connect() akan ditampilkan. Jika koneksi gagal, atau jika waktu metode connect() habis (setelah sekitar 12 detik), metode tersebut akan menampilkan IOException.

Karena connect() adalah panggilan pemblokiran, Anda harus selalu melakukan prosedur koneksi ini dalam thread yang terpisah dari thread aktivitas (UI) utama.

Contoh

Berikut adalah contoh dasar thread klien yang memulai koneksi Bluetooth:

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);
       }
   }
}

Perhatikan bahwa dalam cuplikan ini, cancelDiscovery() dipanggil sebelum upaya koneksi dilakukan. Anda harus selalu memanggil cancelDiscovery() sebelum connect(), terutama karena cancelDiscovery() berhasil, terlepas dari apakah penemuan perangkat sedang berlangsung atau tidak. Jika aplikasi perlu menentukan apakah penemuan perangkat sedang berlangsung, Anda dapat memeriksanya menggunakan isDiscovering().

Metode manageMyConnectedSocket() khusus aplikasi dirancang guna memulai thread untuk mentransfer data, yang dibahas di bagian mentransfer data Bluetooth.

Setelah selesai menggunakan BluetoothSocket, selalu panggil close(). Tindakan ini akan segera menutup soket yang terhubung dan melepaskan semua resource internal terkait.