+2

Android Bluetooth Classic - Part 3

Chào các bạn, hôm nay mình tiếp tục chuỗi bài liên quan đến Bluetooth Classic trong Android. Bài hôm nay mình tập trung vào phần Connect giữa các thiết bị Bluetooth.

Để tạo kết nối Bluetooth giữa 2 thiết bị chúng ta cần thiết lập lập kết nối giống như việc kết nối giữa máy chủ (Server) và máy khách (Client). Chúng giao tiếp với nhau thông qua RFCOMM sockets, cụ thể thông qua đối tượng BluetoothSocket Socket trong Android. Cơ chế như nhau, máy khách (Client) cung cấp Socket information khi nó mở một RFCOMM Channel đến máy chủ (Server), máy chủ (Server) nhận Socket information khi kết nối đến được Accept. Server và Client được coi là đã kết nối với nhau khi chúng có một BluetoothSocket được kết nối trên cùng một kênh RFCOMM.

Có 2 kết nối mà bạn cần quan tâm khi tạo Connect trong Bluetooth Classic

  • Connect như là một Client
  • Connect như là một Server

Với từng bài toán, từng requirement cụ thể thì ta sẽ sử dụng chúng theo cách phù hợp.

1. Connect như là một Client

Cách connect đầu tiên này thường dùng cho bài toán kết nối với một thiết bị Bluetooth có sẵn. Từ đó kết nối, điều khiển, transfer dữ liệu với thiết bị đó.

Đầu tiên bạn phải có được một đối tượng BluetoothDevice đại diện cho thiết bị Bluetooth. Đây là công đoạn bạn Scan (Discovery) và lấy thông thiên của thiết bị. Sau đó, bạn phải sử dụng BluetoothDevice để có được BluetoothSocket và bắt đầu kết nối.

Các bước cụ thể như sau:

  1. Tạo một BluetoothSocket (RFCOMM Sockets) từ BluetoothDevice bằng cách gọi hàm createRfcommSocketToServiceRecord(UUID). Các bạn có hiểu UUID là một mã string, kiểu như một Port Number. Mã này của Client truyền vào hàm này, phải khớp với mã UUID mà máy chủ cấp. Thường với bài toán kết nối với thiết bị Bluetooth, thì thiết bị Bluetooth có cung cấp một chuỗi UUID chuẩn để ứng dụng có thể fix cứng dưới code và gửi lên khi tạo connect.
  2. Gọi BluetoothSocket.connect(). Sau khi gọi hàm, hệ thống sẽ thực hiện tra cứu SDP để tìm thiết bị từ xa có UUID phù hợp. Nếu tìm kiếm và khớp thành công và thiết bị từ xa chấp nhận kết nối, nó sẽ chia sẻ kênh RFCOMM để sử dụng trong quá trình kết nối và trả về phương thức connect(). Nếu kết nối không thành công hoặc nếu phương thức connect() timeout (sau khoảng 12 giây), thì phương thức này sẽ đưa ra một IOException.

Bạn có thể tham khảo mã code sau

        class BluetoothClassicConnection(device: BluetoothDevice): Thread() {

        private val bluetoothSocket: BluetoothSocket? by lazy {
            device.createRfcommSocketToServiceRecord(UUID)
        }

        override fun run() {
            // Cancel discovery because it otherwise slows down the connection.
            if (BluetoothAdapter.getDefaultAdapter()?.isDiscovering == true) {
                BluetoothAdapter.getDefaultAdapter()?.cancelDiscovery()
            }

            try {
                bluetoothSocket?.let { socket ->
                    socket.connect()

                    // Handle Transfer Data 
                }
            } catch (e: IOException) {
                try {
                    bluetoothSocket?.close()
                } catch (e: IOException) {
                    Log.e(TAG, "Could not close the client socket", e)
                }
                Log.e(TAG, "Could not connect", e)
            }
        }

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

        companion object {
            private const val UUID = "UUID code provide from Server Site"
         }
        }

Note:

  • Việc duy trì kết nối Bluetooth Classic khá tốn tài nguyên của máy, đặc biệt như là PIN. Các bạn cần lưu ý đóng socket khi không cần dùng nữa, bằng cách gọi close() của đối tượng BluetoothSocket. Sau khi gọi Socket sẽ ngay lực tức được đóng lại và giải phóng tất cả các tài nguyên nội bộ có liên quan.
  • Chú ý đến việc huỷ Discovery() trước khi kết nối để đảm bảo việc connect được diễn ra thành công.

2. Connect như một Server

Với loại connect này được thực hiện nếu bạn muốn connect 2 thiết bị devices với nhau. Một thiết bị trong 2 thiết bị đó phải hoạt động như một Server bằng cách nắm giữ BluetoothServerSocket. Cụ thể BluetoothServerSocket lắng nghe các kết nối, sử dụng để thu nhận kết nối bluetoothSocket (RFCOMM sockets). Khi kết nối được thiết lập, máy chủ socket không còn cần thiết nữa và có thể đóng lại thông qua hàm close().

Các bước cụ thể như sau:

  1. Lấy thông tin BluetoothServerSocket bằng cách gọi hàm listenUsingRfcommWithServiceRecord(String, UUID).
  2. Bắt đầu lắng nghe các yêu cầu kết nối bằng cách gọi accept(). Khi thành công, accept() trả về BluetoothSocket được kết nối và sau đó chúng ta sẽ sử dụng nó để trao đổi dữ liệu với thiết bị kết nối.
        class BluetoothClassicServerConnection: Thread() {
        private val serverSocket: BluetoothServerSocket? by lazy {
            BluetoothAdapter.getDefaultAdapter()?.listenUsingInsecureRfcommWithServiceRecord(NAME, UUID)
        }

        private var bluetoothSocket: BluetoothSocket? = null

        override fun run() {
            // Keep listening until exception occurs or a socket is returned.
            var shouldLoop = true

            while (shouldLoop) {
                val socket: BluetoothSocket? = try {
                    bluetoothSocket = serverSocket?.accept()
                } catch (e: IOException) {
                    Log.e(TAG, "Socket's accept() method failed", e)
                    shouldLoop = false
                    null
                }
                socket?.also {
                    
                    // Handle Transfer Data  

                    serverSocket?.close()
                    shouldLoop = false
                }
            }
        }

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

        companion object {
            private const val NAME = "The name we provide the service with when it is added to the SDP (Service Discovery Protocol)"
            private const val UUID = "UUID code provide from Server Site"
          }
        }

Note:

  • Khi call accept() trả về BluetoothSocket, Socket đã được kết nối. Vì vậy, không nên gọi connect(), giống như mình làm ở phía Client.

Vậy là mình cũng vừa trình bày xong phần liên quan đến Connect Bluetooth Classic trong Android. Các loại và các trường hợp sử dụng nó. Bài tiếp theo mình sẽ trình bày về việc Transfer Data (Read and Write) của Bluetooth Classic.

Hẹn gặp lại các bạn trong bài viết sắp tới.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí