Android笔记—蓝牙串口(BlueTooth+EventBus)


资料来源如下

  • 第一行代码(第二版)

编程环境

  • Android Studio 2.2.3

导语

  • 毕设中的蓝牙部分,记录以供复习

概述

最终效果

  • Android应用 与 Hc-05 蓝牙模块连接,单片机与 Android 端 可以通过串口正常收发数据.

  • 预留足够灵活的接口, Android 应用中可以在任意位置获取到蓝牙数据

  • Activity切换时,蓝牙连接不断开.

用到的开源库/知识点资料 简介/来源

  • 蓝牙:
    Android API 指南

    https://developer.android.com/guide/topics/connectivity/bluetooth.html

  • EventBus:
    EventBus-GitHub 是一个Android端优化的publish/subscribe消息总线,用来替代 Intent 、 Handler 、 Broadcast 等在 Actvity 、 Fragment 、 Service 等组件之间传递信息 . EventBus 可以传递一个完整的对象,简单高效, 注意 EventBus 只能在多线程之间传递消息,无法在不同进程之间传递消息 ,EventBus 3.0 以后进一步简化了传递方式,真的是很值得学习的一个开源库!

    参考资料如下:

    EventBus 3.0初探: 入门使用及其使用 完全解析

    EventBus3(3.0.0)源码解析


基础部分

  • 蓝牙 与 EventBus 基础部分

BlueTooth


  • 备注 : 这里使用的是 传统蓝牙 即 蓝牙4.0以前版本, 而不是 蓝牙4.0 ( ble低功耗蓝牙)及以后版本

  • Android 对 Bluetooth 做了很好的封装,我们可以比较轻松的在 Android Bluetooth API 上进行开发.
  • Android中所有蓝牙API均来自 android.bluetooth 包

BlueTooth基础

  • BluetoothAdapter 本地蓝牙适配器
    BluetoothAdapter 是所有蓝牙交互的入口点, 在初始化蓝牙及蓝牙配对阶段使用
    1.发现其他蓝牙设备
    2.查询绑定(配对)设备的列表
    3.使用已知的 MAC 地址实例化 BluetoothDevice
    4.创建 BluetoothServerSocket 侦听来自其他设备的通信。

  • BluetoothDevice 远程蓝牙设备
    含有该设备的信息,例如设备的名称、地址、类和绑定状态等。

  • BluetoothSocket 蓝牙套接字接口(与 TCP Socket 相似)
    与 BluetoothDevice 配合建立远程连接,允许应用通过 InputStream 和 OutputStream 与其他蓝牙设备交换数据.

初始化蓝牙

  • 声明蓝牙权限
    Android 应用使用蓝牙前都需要声明蓝牙权限
    一般只需要 BLUETOOTH 权限即可,但是考虑到之后有更多的需求,需要更改系统蓝牙设置,由此需要 BLUETOOTH_ADMIN 权限_也一起声明

    1
    2
    3
    4
    5
    <manifest ... >
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    ...
    </manifest>
  • 蓝牙设备可用性 获取 BluetoothAdapter
    获取 BluetoothAdapter,需要静态 getDefaultAdapter() 方法。getDefaultAdapter() 会返回一个表示设备自身的蓝牙适配器的 BluetoothAdapter 设备不支持蓝牙 则返回 null

    1
    2
    3
    4
    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null) {
    // 蓝牙不可用操作
    }
  • 开启蓝牙
    BluetoothAdapter 的 .isEnabled() 可以判断系统蓝牙是否开启.
    没有开启时 startActivityForResult() 发送包含 ACTION_REQUEST_ENABLE 的 Intent 请求系统开启蓝牙,并在 onActivityResult() 返回的数据中 RESULT_CANCELED 表示开启失败 RESULT_OK 表示开启成功,这里我们只检测开启失败情况,并提示用户

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    if (!mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
    }

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_ENABLE_BT && resultCode == RESULT_CANCELED) {
    Toast.makeText(MyApplication.getContext(),R.string.Bluetooth_openfail, Toast.LENGTH_SHORT).show();
    }
    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null) {
    //设备不支持蓝牙时处理
    }
    }
  • 查询已配对的设备
    调用 getBondedDevices(),返回已配对设备的一组 BluetoothDevice.之后使用for循环遍历

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
    if (pairedDevices.size() > 0) {
    //重新加载bluetoothDeviceList
    bluetoothDeviceList.clear();
    //BluetoothDevice列表循环
    for (BluetoothDevice device : pairedDevices) {
    bluetoothDeviceList.add(device);
    }
    }
  • 扫描设备
    查找设备是非常耗费系统资源的事项,需要安排在子线程中执行,这里没有用到,由需要请参考Google官方蓝牙教程

  • 连接设备
    蓝牙分主从机,链接为服务器/客户端。这里使用的是连接为客户端。

    • 首先获取远程设备的 BluetoothDevice 对象,即在选择设备阶段的BluetoothDevice
    • 调用 createRfcommSocketToServiceRecord(UUID) 获取 BluetoothSocket,UUID通用唯一识别码 在蓝牙中具体是什么没有很好的解释。这里的值取的是
      "00001101-0000-1000-8000-00805F9B34FB"
    • 调用 connect() 发起连接,阻塞调用,需要在子线程中执行。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      //蓝牙连接子线程
      private class ConnectThread extends Thread {
      private final BluetoothSocket mmSocket;

      //解析函数
      ConnectThread(BluetoothDevice bluetoothDevice) {
      // 使用一个中间变量 tmp
      // mmSocket 类型是 final

      BluetoothSocket tmp = null;
      // 获取 BluetoothSocket
      try {
      // MY_UUID is the app's UUID string, also used by the server code
      tmp = bluetoothDevice.createRfcommSocketToServiceRecord(UUID.fromString(MyApplication.MY_UUID));
      } catch (IOException ignored) {
      }
      //赋值给 mmSocket
      mmSocket = tmp;
      }

      public void run() {
      // 关闭蓝牙扫描
      MyApplication.getBluetoothAdapter().cancelDiscovery();
      try {
      // 通过 socket 连接到设备. connect() 会一直执行直到成功连接或者抛出异常
      mmSocket.connect();
      } catch (IOException connectException) {
      //无法连接到蓝牙,关闭连接并退出
      try {
      mmSocket.close();
      }
      //没有正常关闭
      catch (IOException ignored) {
      }
      return;
      }
      // Do work to manage the connection (in a separate thread)
      mConnectedThread = new ConnectedThread(mmSocket);
      mConnectedThread.start();
      }

      /**
      * 关闭蓝牙连接
      */
      void cancel() {
      try {
      mmSocket.close();
      } catch (IOException ignored) {
      }
      }
      }
  • 管理连接
    获取BluetoothSocket
    获取 InputStream 和 OutputStream, getInputStream() 和 getOutputStream() 来处理数据传输。read(byte[]) 和 write(byte[]) 读取数据并写入到流式传输。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    private class ConnectedThread extends Thread {
    //BluetoothSocket
    private final BluetoothSocket mmSocket;
    //输入流
    private final InputStream mmInStream;
    //输出流
    private final OutputStream mmOutStream;

    ConnectedThread(BluetoothSocket socket) {
    //传入BluetoothSocket,实例化mmSocket
    mmSocket = socket;
    //输入/输出流 中间变量
    InputStream tmpIn = null;
    OutputStream tmpOut = null;

    // 输入输出流实例化
    try {
    tmpIn = socket.getInputStream();
    tmpOut = socket.getOutputStream();
    } catch (IOException e) {
    e.printStackTrace();
    }
    mmInStream = tmpIn;
    mmOutStream = tmpOut;
    }

    public void run() {
    byte[] buffer = new byte[1024];
    int bytes;
    // 连接成功时
    while (true) {
    try {
    // 在InputStream读数据
    bytes = mmInStream.read();
    //发送数据
    Events.bluetooth_Recycle bluetooth_recycle = new Events.bluetooth_Recycle();
    bluetooth_recycle.s = bytes;
    bluetooth_recycle.bytes = String.valueOf((char) bytes);
    EventBus.getDefault().post(bluetooth_recycle);
    } catch (IOException e) {
    break;
    }
    }
    }

    //写方法
    void write(byte[] bytes) {
    try {
    mmOutStream.write(bytes);
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    //关闭流连接
    void cancel() {
    try {
    mmSocket.close();
    } catch (IOException e) {
    }
    }
    }

    EventBus

  • 直接放链接了

    http://www.jianshu.com/p/a040955194fc
    http://www.jianshu.com/p/acfe78296bb5
    http://www.ff50.net/view/40565212977623506063.html

正文

  • 蓝牙连接子线程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
     private class ConnectThread extends Thread {
    private final BluetoothSocket mmSocket;

    //解析函数
    ConnectThread(BluetoothDevice bluetoothDevice) {
    // 使用一个中间变量 tmp
    // mmSocket 类型是 final

    BluetoothSocket tmp = null;
    // 获取 BluetoothSocket
    try {
    // MY_UUID is the app's UUID string, also used by the server code
    tmp = bluetoothDevice.createRfcommSocketToServiceRecord(UUID.fromString(MyApplication.MY_UUID));
    } catch (IOException ignored) {
    }
    //赋值给 mmSocket
    mmSocket = tmp;
    }

    public void run() {
    // 关闭蓝牙扫描
    MyApplication.getBluetoothAdapter().cancelDiscovery();
    try {
    // 通过 socket 连接到设备. connect() 会一直执行直到成功连接或者抛出异常
    mmSocket.connect();
    } catch (IOException connectException) {
    //无法连接到蓝牙,关闭连接并退出
    try {
    mmSocket.close();
    }
    //没有正常关闭
    catch (IOException ignored) {
    }
    return;
    }
    // Do work to manage the connection (in a separate thread)
    mConnectedThread = new ConnectedThread(mmSocket);
    mConnectedThread.start();
    }

    /**
    * 关闭蓝牙连接
    */
    void cancel() {
    try {
    mmSocket.close();
    } catch (IOException ignored) {
    }
    }
    }
  • 管理连接子线程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
      //管理连接子线程
    private class ConnectedThread extends Thread {
    //BluetoothSocket
    private final BluetoothSocket mmSocket;
    //输入流
    private final InputStream mmInStream;
    //输出流
    private final OutputStream mmOutStream;

    ConnectedThread(BluetoothSocket socket) {
    //传入BluetoothSocket,实例化mmSocket
    mmSocket = socket;
    //输入/输出流 中间变量
    InputStream tmpIn = null;
    OutputStream tmpOut = null;

    // 输入输出流实例化
    try {
    tmpIn = socket.getInputStream();
    tmpOut = socket.getOutputStream();
    } catch (IOException e) {
    e.printStackTrace();
    }
    mmInStream = tmpIn;
    mmOutStream = tmpOut;
    }

    public void run() {
    byte[] buffer = new byte[1024];
    int bytes;
    // 连接成功时
    while (true) {
    try {
    // 在InputStream读数据
    bytes = mmInStream.read();
    //发送数据
    Events.bluetooth_Recycle bluetooth_recycle = new Events.bluetooth_Recycle();
    bluetooth_recycle.s = bytes;
    bluetooth_recycle.bytes = String.valueOf((char) bytes);
    EventBus.getDefault().post(bluetooth_recycle);
    } catch (IOException e) {
    break;
    }
    }
    }

    //写方法
    void write(byte[] bytes) {
    try {
    mmOutStream.write(bytes);
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    //关闭流连接
    void cancel() {
    try {
    mmSocket.close();
    } catch (IOException e) {
    }
    }
    }
  • 连接蓝牙

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //连接线程
    private ConnectThread mConnectThread;
    //管理连接进程
    private ConnectedThread mConnectedThread;

    //创建连接子线程
    mConnectThread = new ConnectThread(bluetoothDevice);
    //启动连接子线程
    mConnectThread.start();
  • 向蓝牙写入数据

    1
    2
    3
    4
    5
    6
    7
     //发送数据String,        主线程            优先级4       非粘性事件
    @Subscribe(threadMode = ThreadMode.MAIN, priority = 4, sticky = false)
    public void onEvent(Events.bluetooth_Send bluetoothSend) {
    String bytes = bluetoothSend.bytes;
    mConnectedThread.write(bytes.getBytes());
    LogUtil.d(Tag, "onEvent:Send" + bluetoothSend.bytes);
    }
  • 接收数据处理

    1
    2
    3
    4
    5
    6
      //数据转换                后台进程            优先级3       非粘性事件
    @Subscribe(threadMode = ThreadMode.BACKGROUND, priority = 3, sticky = false)
    public void onEvent(Events.bluetooth_Transformers bluetoothTransformers) {
    int bytes = bluetoothTransformers.bytes;
    //处理数据
    }