Bootstrap

android 蓝牙ACL通讯详解

本文主要是讲述android中蓝牙ACL链接的详细介绍,这里只讲述ACL,不讨论BLE,SCO,等等其他通讯通道,后面如果我有兴趣研究,我会贴上对应的文章链接

github下载地址
同时也是一个可直接用于项目的gradle项目


什么是ACL

参考链接
蓝牙基带技术支持两种连接类型:同步定向连接(SCO)类型和异步无连接(ACL)类型。前者主要用于同步话音传送,后者主要用于分组数据传送。
ACL链路就是定向发送数据包,它既支持对称连接,也支持不对称连接(既可以一对一,也可以一对多)。主设备负责控制链路带宽,并决定微微网中的每个从设备可以占用多少带宽和连接的对称性。从设备只有被选中时才能传送数据。ACL链路也支持接收主设备发给微微网中所有从设备的广播消息。

实现过程

包含1服务端,max7的客户端,形成一个小型的局域网,并通讯交互消息

权限

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

监听广播

IntentFilter filter = new IntentFilter();
//蓝牙搜索服务已经开启
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
//蓝牙搜索服务已经结束
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
//搜索到新的蓝牙设备
filter.addAction(BluetoothDevice.ACTION_FOUND);
/*设备绑定状态改变
intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1)
BluetoothDevice.BOND_NONE    //无状态
BluetoothDevice.BOND_BONDING //配对中
BluetoothDevice.BOND_BONDED  //已配对
*/
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
//设备已链接(值得注意的是,配对过程中系统会发送这个广播,但这个时候只是处于配对过程并不是已经可以链接并通讯的状态,所以不能以这个为准是否已经可以通讯)
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
//已经与设备断开链接
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
//正在配对
filter.addAction(BluetoothAdapter.ACTION_PAIRING_REQUEST);
//蓝牙状态
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
set 1 启动蓝牙
BluetoothAdapter mBtAdapter = BluetoothAdapter.getDefaultAdapter()
mBtAdapter.enable() / mBtAdapter.disable()
set 2 创建服务
serverSoctet = mBtAdapter.listenUsingRfcommWithServiceRecord(sdpName, java.util.UUID.fromString(BTUUID));
//每获得一个循环,表示有一台新的机器在尝试链接
while (btWaitConnecting) {
    BluetoothSocket soctet = serverSoctet.accept(); soctet);
    onBtServiceListence.onNewConnection(soctet);
}

这里的UUID用的是00001101-0000-1000-8000-00805F9B34FB
如果没有报错,作为服务器的蓝牙服务已经启动完成。
serverSoctet.accept()会阻塞线程,直到有新的客户端接入
spdName随意,主要将这个名字的蓝牙服务注册到系统中的一个标识
值得注意的是,需要开启蓝牙和设置蓝牙的可见性

蓝牙可见性
public void setDiscoverableTimeout(int secound) {
   BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
   try {
       Method setDiscoverableTimeout = BluetoothAdapter.class.getMethod("setDiscoverableTimeout", int.class);
       setDiscoverableTimeout.setAccessible(true);
       Method setScanMode = BluetoothAdapter.class.getMethod("setScanMode", int.class, int.class);
       setScanMode.setAccessible(true);
       setDiscoverableTimeout.invoke(adapter, secound);
       setScanMode.invoke(adapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, secound);
   } catch (Exception e) {
       e.printStackTrace();
   }
}
客户端链接服务端

双方设备如果没有进行过配对,链接之前系统会弹出一个随机6位数的配对密码,只有点击配对确认后才能获得socket进行通讯,这里很多人都希望可以实现自动配对,但是作者在网上搜索了很多资料ClsUtil.java,基本无法实现,唯一一次实现过自动配对也属于靠运气,再来就不行了,不太靠谱。
另外值得注意的是很多人会使用createBond()进行链接,但是据作者按照不同机型进行观察,有部分机型调用这个功能的时候也属于极度不稳定状态,甚至会让整个蓝牙模块停机工作,所以这里采取了其他方式确保一定弹出配对并不至于让蓝牙模块崩溃。

配对
private boolean doBondDevice(BluetoothDevice device) {
 boolean bool = isBondDevice(device);
 if (!bool) {
     try {
         /**
          //采用这个部分机型可能不会发起配对
          device.createBond()
          */
         //能保证能发起配对请求,成功后,取消链接来达到绑定的目的
         Method m = device.getClass().getMethod("createRfcommSocket", new Class[]{int.class});
         BluetoothSocket socket = (BluetoothSocket) m.invoke(device, 1);
         socket.connect();
         if (socket.isConnected())
             socket.close();
         return true;
     } catch (Exception e) {
         bool = isBondDevice(device);
     }
 }
 return bool;
}
链接
private BluetoothSocket doConnectionWithUUID(BluetoothDevice device) throws IOException {
    BluetoothSocket socket = device.createInsecureRfcommSocketToServiceRecord(UUID.fromString(BTUUID));
    socket.connect();
    return socket;
}

以上已经完成了整个蓝牙链接流程,其实也就几个步骤,不复杂,只是其中的坑比较多,另外需要监听各种广播事件来达到业务处理效果。
在监听的广播中,需要获得远程设备信息都可以直接从Intent中获得,方法如下

BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
;