最近项目中使用到蓝牙打印机,通过蓝牙连接蓝牙打印机进行打印功能。在此对蓝牙知识进行归纳输出一波,达到巩固之效果。蓝牙开发《基础篇一》
蓝牙开发
Android 的蓝牙开发主要分为两种:
- 经典蓝牙开发
- BLE蓝牙开发
经典蓝牙开发
英文: Classic Bluetooth
用处
用于多个 Android 端流媒体传输和通讯。
使用步骤
通过使用蓝牙 API 开发需要完成以下 4 个主要的任务:
- 设置蓝牙 API
- 查找设备
- 连接设备
- 设备间数据传输
准备工作
配对和绑定过程
配对过程 在发现对应需要传输数据的设备之后,就可以进行设备间的配对。
绑定过程 在配对完成后,交换了「安全码」之后就完成了绑定过程。
在配对和绑定完成之后 Android 设备间才可以进行数据的传输。在数据传输会话结束后,设备之间会释放连接,使其重新变成可发现状态。但是他们没有解除绑定关系,下次只要两台设备在合理的范围内,它们就会自动地进行连接。
权限分配
-
android.permission.BLUETOOTH
请求连接,接受连接,传输数据
-
LOCATON
获取设备定位权限,用于确定设备的距离
-
BLUETOOTH_ADMIN
用于设备发起查找附近蓝牙设备
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
无线接口规范 Profiles
基于蓝牙的设备通讯的无线接口通讯规范。
使用 profiles
- 得到一个 BluetoothAdapter 这个对应于使用蓝牙的设置步骤
- 注册 BluetoothProfile.ServiceListener 事件,监听蓝牙的连接与断开连接
- getProfileProxy() 和具体的代理对象建立连接,例如 BluetoothHeadset,BluetoothGatt等
- onServiceConnected() 接收通过 getProfileProxy 进行的连接的代理对象
- 在获取到代理对象 profiles 之后就可以进行通过它进行监听连接的状态和操作该profile相关的功能。
设置 Bluetool
-
判断设备是否支持蓝牙
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mBluetoothAdapter == null) { // Device does not support Bluetooth }
-
开启蓝牙
通过下面这种方式优雅地开启蓝牙,并且可以在当前应用接收蓝牙开关状态,也就是在 onActivityResult 接受 RESULT_OK result code 即可。
if (!mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }
查找设备
通过 BluetoolAdapter 可以扫描附近的蓝牙设备或者从已配对的蓝牙列表中查找。
-
查找已配对设备
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); if (pairedDevices.size() > 0) { // There are paired devices. Get the name and address of each paired device. for (BluetoothDevice device : pairedDevices) { String deviceName = device.getName(); String deviceHardwareAddress = device.getAddress(); // MAC address } }
-
扫描设备
在扫描到设备之后,系统是通过广播的方式来通知的,因此需要注册一个广播接受者
扫描设备这个过程是比较消耗资源的,在你进行连接之后,就要停止扫描操作。当已经和设备进行建立连接了,就不要进行扫描操作,不然会渐低已连接设备的可用带宽的。
@Override protected void onCreate(Bundle savedInstanceState) { ... // Register for broadcasts when a device is discovered. IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); registerReceiver(mReceiver, filter); //开始扫描 adapter.startDiscovery(); } // Create a BroadcastReceiver for 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)) { // Discovery has found a device. Get the BluetoothDevice // object and its info from the Intent. //获取扫描的设备对象 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); String deviceName = device.getName(); String deviceHardwareAddress = device.getAddress(); // MAC address } } }; @Override protected void onDestroy() { super.onDestroy(); ... // Don't forget to unregister the ACTION_FOUND receiver. unregisterReceiver(mReceiver); //在需要的时候停止扫描 adapter. cancelDiscovery(); }
-
让设备可见
想要让其他蓝牙蓝牙设备扫描到自己,那么就必须让当前设备可见。Android 通过发送意图的方式来设置蓝牙设备可见性。
通过注册 intent = ACTION_SCAN_MODE_CHANGED 的广播可以监听设备的可见状态,该广播intent有2个 extra field 分别是- EXTRA_SCAN_MODE 新的状态
- EXTRA_PREVIOUS_SCAN_MODE 旧的状态
该intent这两个额外的属性的取值分别如下:
- BloothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE 设备处于可见状态
- BloothAdapter.SCAN_MODE_CONNECTABLE 设备不处于可见状态,但是可以接受连接
- BloothAdapter.SCAN_MODE_NONE 设备处于不可见状态,并且不可以连接
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); //设备可见300s。 //如果这个值为0表示设备一直可见不过google不推荐这样做。 discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); startActivity(discoverableIntent); IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_SCAN_MODE_CHANGED); registerReceiver(mReceiver, filter); private final BroadcastReceiver mReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_SCAN_MODE_CHANGED.equals(action)){ //取出action的属性即可 } }
连接设备
当之前几步操作都完毕之后,现在就可以准备进行蓝牙设备的连接了。
连接的标记
当客户端和服务端在同一个 RFCOMM 通道都有一个已连接的 BlueSocket 就表示已建立连接,此时双方设备都可以获取输入输出流对象,就可以进行数据通讯了。
服务端连接
基本操作流程:
- 获取 BluetoolServerSocket 对象。
- accept() 接受客户端连接,该方法是一个阻塞式方法,当接收到连接或者是出现异常才会返回。当成功接受连接之后会返回一个已连接的 BluetoolSocket 对象。注意:此时返回的 BluetoolSocket 不需要调用 connect 方法进行连接。
- 除非你想要接受额外的客户端连接,否则你可以调用 close 方法去释放 BluetoolServerSocket 的相关资源。但是并不关闭通过accept()方法获取到的 BluetoolSocket 资源,和 TCP/IP 不一样的是,RFCOMM 允许在在某个时间存在一个已连接的client。所以说一般情况,当accept 获取到连接就后就可以将 BluetoolServerSocket 进行关闭。
客户端连接
作为客户端而言,当远程设备在一个打开的 ServerSocket 上接受连接时,客户端首先需要获取一个代表远程设备的 BluetoolDevice ,然后根据这个 BluetoolDevice 去获取一个 BluetoolSocket 对象,并且建立连接。
基本的操作流程:
-
获取远程设备的 BluetoolDevice
-
根据BluetoolDevice 获取 BluetoolSocket createRfcommSocketToServiceRecord(UUID)
-
connect() 发起连接,注意该方法是阻塞方法,应该将其放到子线程中去处理。
-
当 connect() 方法被调用之后,系统就会去和远程设备进行连接,当连接成功,那么两个蓝牙设备就共同一个 RFCOMM channel 。如果 connect 方法失败或者超时(about 12s)那么该方法就会抛出 IOException 异常。
-
当使用完之后需要将 socket进行close。
上面我们讲过,在扫描设备之后,我们调用 connect 方法进行设备连接之前一定要记得cancelDiscover() ,因为在已连接的设备如果还在扫描的话,会占用设备的通讯带宽。判断方法:isDiscovering().
管理连接
获取输入输出流,注意这两个方法都是阻塞式方法,需要放在子线程中执行。
总结
-
BluetoolAdapter
表示本地蓝牙适配器,它是所有蓝牙通讯交互的入口点。
扫描设备。
列出已绑定的设备。
根据 mac 地址初始化一个 BluetoolDevice 。
创建 BluetoolServerSocket 监听来自其他设备的连接。 -
BluetoolDevice
代表远程设备
通过 bluetoolDevice 可以获取与远程建立连接的 BluetoolSocket
查询远程设备的信息,name,mac和绑定状态。 -
BluetoolSocket
允许当前设备通过输入输出流与其他设备进行数据交换
-
BluetoolServerSocket
用于服务端,监听所有的客户端的连接,当接受到远程设备的连接之后就返回一个已连接的 BluetoolSocket 。
网友评论