欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android 使用经典蓝牙

程序员文章站 2024-03-24 23:18:34
...

Android 使用经典蓝牙

本文内容基本是按照安卓官方文档来进行经典蓝牙的学习,大体都从官方文档粘贴而来,英文部分做了写粗略的翻译,作为自己学习安卓经典蓝牙的一个记录。官方文档可参考https://developer.android.com/guide/topics/connectivity/bluetooth.html

基础知识

Android Bluetooth基础类、API,包含在android.bluetooth包下

  • BluetoothAdaper 表示本地蓝牙适配器,是所有蓝牙交互的入口点, BluetoothAdapter 是所有蓝牙交互的入口点。 利用它可以发现其他蓝牙设备,查询绑定(配对)设备的列表,使用已知的 MAC 地址实例化 BluetoothDevice,以及创建 BluetoothServerSocket 以侦听来自其他设备的通信。
  • BluetoothDevice 表示远程蓝牙设备。利用它可以通过 BluetoothSocket 请求与某个远程设备建立连接,或查询有关该设备的信息,例如设备的名称、地址、类和绑定状态等。
  • BluetoothSocket 表示蓝牙套接字接口(与 TCP Socket 相似)。这是允许应用通过 InputStream 和 OutputStream 与其他蓝牙设备交换数据的连接点。
  • BluetoothServerSocket 表示用于侦听传入请求的开放服务器套接字(类似于 TCP ServerSocket)。 要连接两台 Android 设备,其中一台设备必须使用此类开放一个服务器套接字。 当一台远程蓝牙设备向此设备发出连接请求时, BluetoothServerSocket 将会在接受连接后返回已连接的 BluetoothSocket
  • BluetoothClass 描述蓝牙设备的一般特征和功能。 这是一组只读属性,用于定义设备的主要和次要设备类及其服务。 不过,它不能可靠地描述设备支持的所有蓝牙配置文件和服务,而是适合作为设备类型提示
  • BluetoothProfile 表示蓝牙配置文件的接口。 蓝牙配置文件是适用于设备间蓝牙通信的无线接口规范。 免提配置文件便是一个示例。 如需了解有关配置文件的详细讨论,请参阅使用配置文件

使用蓝牙

获取蓝牙权限
<uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
设置蓝牙
  • 获取BluetoothAdapter

    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null) {
        // 若为Null,则说明不支持蓝牙,在这里做出相应处理
    }
    
  • 启用蓝牙

    if (!mBluetoothAdapter.isEnabled()) {
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
    }
    

    若蓝牙为启用,则将显示对话框,请求用户允许启用蓝牙

查找蓝牙
  • 查询配对的设备:在执行设备发现之前,有必要查询已配对的设备及,故使用getBondedDevices()方法,其返回一组已配对的BluetoothDevice

    Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
    // 如果有配对的设备
    if (pairedDevices.size() > 0) {
        // 遍历匹配设备组
        for (BluetoothDevice device : pairedDevices) {
            // 将已配对设备的名称及地址存储在一个ArrayAdapter中
            mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
        }
    }
    
  • 发现设备:要开始发现设备,只需调用 startDiscovery()。该进程为异步进程,并且该方法会立即返回一个布尔值,指示是否已成功启动发现操作。 发现进程通常包含约 12 秒钟的查询扫描,之后对每台发现的设备进行页面扫描,以检索其蓝牙名称。您的应用必须针对 ACTION_FOUND Intent 注册一个 BroadcastReceiver,以便接收每台发现的设备的相关信息。 针对每台设备,系统将会广播 ACTION_FOUNDIntent。此 Intent 将携带额外字段 EXTRA_DEVICEEXTRA_CLASS,二者分别包含 BluetoothDeviceBluetoothClass。 例如,下面说明了在发现设备时如何注册以处理广播。

    // 为 ACTION_FOUND 创建一个广播接收器
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            //当发现一个蓝牙设备时
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                //利用EXTRA_DEVICE字段, 从intent中获取该设备实例
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                // 使用getName和getAddress方法获取蓝牙设备名和地址
                mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
            }
        }
    };
    //注册该广播
    IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
    //搜索完成
    filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
    registerReceiver(mReceiver, filter); // 要在onDestry方法中取消注册该广播
    
    //取消注册该广播
    @Override
        protected void onDestroy() {
            super.onDestroy();
            unregisterReceiver(mBtFoundReceiver);
        }
    
  • 启用本地可检测性(可选)

    • 如果您希望将本地设备设为可被其他设备检测到,请使用ACTION_REQUEST_DISCOVERABLE 操作 Intent 调用 startActivityForResult(Intent, int)。 这将通过系统设置发出启用可检测到模式的请求(无需停止您的应用)。 默认情况下,设备将变为可检测到并持续 120 秒钟。 您可以通过添加 EXTRA_DISCOVERABLE_DURATION Intent Extra 来定义不同的持续时间。 应用可以设置的最大持续时间为 3600 秒,值为 0 则表示设备始终可检测到。 任何小于 0 或大于 3600 的值都会自动设为 120 秒。

      //设置持续时间为300秒
      Intent discoverableIntent = new
      Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
      discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
      startActivity(discoverableIntent);
      

      上述操作将显示对话框,请求用户允许设备设为可检测到,若同意,则您的 Activity 将会收到对 onActivityResult()) 回调的调用,其结果代码等于设备可检测到的持续时间。 如果用户响应“No”或出现错误,结果代码将为 RESULT_CANCELED如果设备尚未启用蓝牙,则在启用设备可检测性将会自动启用蓝牙。仅当您希望您的应用托管将用于接受传入连接的服务器套接字时,才有必要启用可检测性,因为远程设备必须能够发现该设备,然后才能发起连接。

连接设备

要在两台设备上的应用之间创建连接,必须同时实现服务器端和客户端机制,因为其中一台设备必须开放服务器套接字,而另一台设备必须发起连接(使用服务器设备的 MAC 地址发起连接)。 当服务器和客户端在同一 RFCOMM 通道上分别拥有已连接的 BluetoothSocket 时,二者将被视为彼此连接。 这种情况下,每台设备都能获得输入和输出流式传输,并且可以开始传输数据,在有关管理连接的部分将会讨论这一主题。 本部分介绍如何在两台设备之间发起连接。

连接为服务器

当您需要连接两台设备时,其中一台设备必须通过保持开放的 BluetoothServerSocket 来充当服务器。 服务器套接字的用途是侦听传入的连接请求,并在接受一个请求后提供已连接的 BluetoothSocket。 从 BluetoothServerSocket 获取 BluetoothSocket 后,可以(并且应该)舍弃 BluetoothServerSocket,除非您需要接受更多连接。

  • 基本流程

    • 通过调用 listenUsingRfcommWithServiceRecord(String, UUID) 获取 BluetoothServerSocket

      • UUID: 通用唯一标识符,是用于唯一标识信息的字符串ID的128为标准化格式
      • UUID将作为与客户端设备连接协议的基础,即当客户端尝试连接此设备时,他会携带一个想要连接的服务的UUID,两个UUID必须匹配
    • 调用accept()开始侦听连接请求,操作成功,accept将返回已连接的BluetoothSocket

    • 调用 close()。这将释放服务器套接字及其所有资源,但不会关闭 accept() 所返回的已连接的 BluetoothSocket

    • 注意:accept()不应再主Activity UI线程中执行,因为它是阻塞调用。应开启一个新线程,使用 BluetoothServerSocketBluetoothSocket 完成所有工作。要终止 accept() 等被阻塞的调用,请通过另一个线程在 BluetoothServerSocket(或 BluetoothSocket)上调用 close(),被阻塞的调用将会立即返回

      //一个用于接受传入连接的服务器组件的简化线程
      private class AcceptThread extends Thread {
          private final BluetoothServerSocket mmServerSocket;
      
          public AcceptThread() {
              // 使用一个临时变量,之后再将它赋值给mmServerSocket,因为mmServerSocket是一个final变量
              BluetoothServerSocket tmp = null;
              try {
                  // MY_UUID是此应用的UUID串,同时也被客户端使用
                  tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
              } catch (IOException e) { }
              //赋值
              mmServerSocket = tmp;
          }
      
          public void run() {
              BluetoothSocket socket = null;
              // 持续侦听直到返回了一个socket或者发生异常
              while (true) {
                  try {
                      socket = mmServerSocket.accept();
                  } catch (IOException e) {
                      break;
                  }
                  // 连接建立成功
                  if (socket != null) {
                      // 在一个单独的线程中管理连接,manageConnectedSocket为虚构的方法,它将启动用于传输数据的线程
                      manageConnectedSocket(socket);
                      mmServerSocket.close();
                      break;
                  }
              }
          }
      
          /** 将取消侦听socket,并使进程关闭 */
          public void cancel() {
              try {
                  mmServerSocket.close();
              } catch (IOException e) { }
          }
      }
      
连接为客户端

要发起与远程设备(保持开放的服务器套接字的设备)的连接,必须首先获取表示该远程设备的 BluetoothDevice 对象。(在前面有关查找设备的部分介绍了如何获取 BluetoothDevice)。 然后必须使用 BluetoothDevice 来获取 BluetoothSocket 并发起连接。

基本过程

  • 使用 BluetoothDevice,通过调用 createRfcommSocketToServiceRecord(UUID) 获取 BluetoothSocket。此处使用的UUID必须与服务器设备在使用 listenUsingRfcommWithServiceRecord(String, UUID) 开放其BluetoothServerSocket 时所用的 UUID 相匹配。 要使用相同的 UUID,只需将该 UUID 字符串以硬编码方式编入应用,然后通过服务器代码和客户端代码引用该字符串。

  • 通过connect()发起连接。执行此调用时,系统将会在远程设备上执行 SDP 查找,以便匹配 UUID。 如果查找成功并且远程设备接受了该连接,它将共享 RFCOMM 通道以便在连接期间使用,并且 connect() 将会返回。 此方法为阻塞调用。 如果由于任何原因连接失败或 connect() 方法超时(大约 12 秒之后),它将会引发异常。

    由于 connect() 为阻塞调用,因此该连接过程应始终在主 Activity 线程以外的线程中执行

    注意:注:在调用 connect() 时,应始终确保设备未在执行设备发现。 如果正在进行发现操作,则会大幅降低连接尝试的速度,并增加连接失败的可能性。

    //发起蓝牙连接的线程的基本示例
    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;
    
            // Get a BluetoothSocket to connect with the given BluetoothDevice
            try {
                // MY_UUID is the app's UUID string, also used by the server code
                tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
            } catch (IOException e) { }
            mmSocket = tmp;
        }
    
        public void run() {
            // Cancel discovery because it will slow down the connection
            mBluetoothAdapter.cancelDiscovery();
    
            try {
                // Connect the device through the socket. This will block
                // until it succeeds or throws an exception
                mmSocket.connect();
            } catch (IOException connectException) {
                // Unable to connect; close the socket and get out
                try {
                    mmSocket.close();
                } catch (IOException closeException) { }
                return;
            }
    
            // Do work to manage the connection (in a separate thread)
            manageConnectedSocket(mmSocket);
        }
    
        /** Will cancel an in-progress connection, and close the socket */
        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) { }
        }
    }
    
管理连接
  • 获取 InputStreamOutputStream,二者分别通过套接字以及 getInputStream()getOutputStream() 来处理数据传输。
  • 使用 read(byte[])write(byte[]) 读取数据并写入到流式传输。
//传输内容示例
private class ConnectedThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;

    public ConnectedThread(BluetoothSocket socket) {
        //获取socket及输入输出流
        mmSocket = socket;
        InputStream tmpIn = null;
        OutputStream tmpOut = null;
        try {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) { }

        mmInStream = tmpIn;
        mmOutStream = tmpOut;
    }

    public void run() {
        byte[] buffer = new byte[1024];  
        int bytes; 获取read()返回的数据

        // 持续侦听输入流,知道发生异常
        while (true) {
            try {
                从输入流中读取数据
                bytes = mmInStream.read(buffer);
                // 将包含的字节流发送到UI activity
                mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
                        .sendToTarget();
            } catch (IOException e) {
                break;
            }
        }
    }

    /* 在主Activity调用此方法,用以向远程设备发送数据 */
    public void write(byte[] bytes) {
        try {
            mmOutStream.write(bytes);
        } catch (IOException e) { }
    }

    /* 在主activity调用此方法用以关闭连接*/
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}
相关标签: Android 蓝牙