概述
定义
蓝牙是一种支持设备短距离通信的无线电技术,具有低功耗、速率快、成本低等特性。
来源
“蓝牙”(Bluetooth)一词是斯堪的纳维亚语中 Blåtand / Blåtann (即古挪威语blátǫnn) 的一个英语化版本,该词蓝牙的名字来源于10世纪丹麦国王Harald Blatand-英译为Harold Bluetooth,他将纷争不断的丹麦部落统一为一个王国,传说中他还引入了基督教。据说,这位哈洛德国王特别喜欢吃蓝莓,有一颗牙齿被染成了永久性的蓝色,因此人们都叫他「哈洛德·蓝牙」(Harald Bluetooth)国王。
在行业协会筹备阶段,需要一个极具有表现力的名字来命名这项高新技术。行业组织人员,在经过一夜关于欧洲历史和未来无限技术发展的讨论后,有些人认为用Blatand国王的名字命名再合适不过了。Blatand国王将现在的挪威,瑞典和丹麦统一起来;就如同这项即将面世的技术,技术将被定义为允许不同工业领域之间的协调工作,例如计算,手机和汽车行业之间的工作。名字于是就这么定下来了。
以此为蓝牙命名的想法最初是Jim Kardach于1997年提出的,Kardach开发了能够允许移动电话与计算机通讯的系统。他的灵感来自于当时他正在阅读的一本由Frans G. Bengtsson 撰写的描写北欧海盗和Harald Bluetooth国王的历史小说The Long Ships,意指蓝牙也将把通讯协议统一为全球标准。
简史
蓝牙的历史实际上要追溯到第二次世界大战。蓝牙的核心是短距离无线电通讯,它的基础来自于跳频扩频(FHSS)技术,由好莱坞女演员 Hedy Lamarr 和钢琴家 George Antheil 在 1942 年 8 月申请的专利上提出。他们从钢琴的按键数量上得到启发,通过使用 88 种不同载波频率的无线电控制鱼雷,由于传输频率是不断跳变的,因此具有一定的保密能力和抗干扰能力。
起初该项技术并没有引起美国军方的重视,直到 20 世纪 80 年代才被军方用于战场上的无线通讯系统,跳频扩频(FHSS)技术后来在解决包括蓝牙、WiFi、3G 移动通讯系统在无线数据收发问题上发挥着关键作用。
蓝牙技术开始于爱立信在 1994 年创制的方案,该方案旨在研究移动电话和其他配件间进行低功耗、低成本无线通信连接的方法。发明者希望为设备间的无线通讯创造一组统一规则(标准化协议),以解决用户间互不兼容的移动电子设备的通信问题,用于替代 RS-232 串口通讯标准。
有不少人认为,蓝牙技术是从1.1版本开始引用到产品上的,其实这并不准确,早在2001年索尼爱立信T39mc成为了第一款内置蓝牙的手机产品,将曾统治手机无线信息交互的“红外传输”技术拉下神坛。而在2002年,蓝牙1.1版才正式推出。
版本
版本 | 时间 | 速度 | 距离(米) |
---|---|---|---|
5.2 | 2020 | 48Mbit/s | 300 |
5.1 | 2019 | 48Mbit/s | 300 |
5.0 | 2016 | 48Mbit/s | 300 |
4.2 | 2014 | 24Mbit/s | 50 |
4.1 | 2013 | 24Mbit/s | 50 |
4.0 | 2010 | 24Mbit/s | 50 |
3.0+HS | 2009 | 24Mbit/s | 10 |
2.1+EDR | 2007 | 3Mbit/s | 10 |
2.0+EDR | 2004 | 2.1Mbit/s | 10 |
1.2 | 2003 | 1Mbit/s | 10 |
1.1 | 2002 | 810Kbit/s | 10 |
1.0 | 1998 | 723.1Kbit/s | 10 |
- EDR是蓝牙增强速率(Enhanced Data Rate)的英文缩写,其特色是大大提高了蓝牙技术的数据传输速率,达到了2.1Mbps ,是蓝牙1.2技术传输速率的三倍。
- HS是highspeed,高速的意思。
与NFC的区别与联系
名称 | 蓝牙 | NFC |
---|---|---|
连接 | 一对一,一对多 | 一对一 |
距离 | 0米<,<300米 | 0<,<20厘米 |
速率 | 高 | 低 |
速度 | 快 | 慢 |
安全 | 高 | 高 |
成本 | 中 | 低 |
配对 | 需 | 无 |
尺寸 | 小 | 小 |
功耗 | 低 | 低 |
用途
蓝牙音箱,汽车电子,移动互联,健康医疗,物联网,智能家居等。
开发
蓝牙分为传统蓝牙(Classic Bluetooth)和低功耗蓝牙(Bluetooth LE),本文使用BLE。
申请权限
<!-- 使用蓝牙的权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<!-- 扫描蓝牙设备或者操作蓝牙设置 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- 精确位置 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- 粗略位置 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
蓝牙有范围,所以使用的时候除了申请蓝牙权限外,还需申请位置权限。
获取蓝牙
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
Android系统已将蓝牙模块封装好,使用BluetoothAdapter.getDefaultAdapter()即可获取当前设备的蓝牙。若
bluetoothAdapter == null;
则说明该设备不支持蓝牙。
打开蓝牙
打开蓝牙之前需要判断当前设备蓝牙是否处于开启状态,通过
bluetoothAdapter.isEnabled();
判断蓝牙是否开启,若返回true,则已开启,否则处于关闭状态,需要打开蓝牙,打开蓝牙有两种方式:
- 直接
bluetoothAdapter.enable();
- 间接
if (!bluetoothAdapter.isEnabled()) {
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult((Activity) context, enableIntent, 1235, null);
}
第一种方式比较粗暴简单,第二种方式有交互过程,体验会更加好一些。
发现设备
扫描蓝牙设备需要使用广播来发现。
public class BluetoothScanReceiver extends BroadcastReceiver {
private static final String TAG = "BluetoothReceiver";
private BluetoothScanCallback callback;
public BluetoothScanReceiver(BluetoothScanCallback callback) {
this.callback = callback;
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.d(TAG, "action: " + action);
BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
switch (action) {
case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
Log.d(TAG, "开始扫描。。。");
callback.onScanStarted();
break;
case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
Log.d(TAG, "结束扫描。。。");
callback.onScanFinished();
break;
case BluetoothDevice.ACTION_FOUND:
Log.d(TAG, "发现设备。。。");
callback.onScanning(bluetoothDevice);
break;
default:
break;
}
}
}
设置了广播接收,接下来需要注册广播
IntentFilter intentFilter1 = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
context.registerReceiver(bluetoothScanReceiver,intentFilter1);
IntentFilter intentFilter2 = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
context.registerReceiver(bluetoothScanReceiver,intentFilter2);
IntentFilter intentFilter3 = new IntentFilter(BluetoothDevice.ACTION_FOUND);
context.registerReceiver(bluetoothScanReceiver,intentFilter3);
- BluetoothAdapter.ACTION_DISCOVERY_STARTED,开始扫描。
- BluetoothAdapter.ACTION_DISCOVERY_FINISHED,结束扫描。
- BluetoothDevice.ACTION_FOUND,发现设备。
在onReceive中就会发现多个设备,通过 BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE),就能够获取被发现的设备。若存在多个蓝牙设备,则该方法会被多次执行。若将数据缓存下来,则可以查看适合范围内的所有蓝牙设备。
若需要该设备被发现,则需要通过BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE让该设备被发现
Intent discoveredIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoveredIntent .putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,60);
startActivityForResult(discoveredIntent);
BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION是设置时间,单位为秒,若大于0或小于3600,则会被设置为默认的120s,不然的话太耗能。扫描也不是一直扫,每一次扫描都会有持续时间,为了确保设备不被遗漏,可以使用Handle或Thread进行多次扫描,注意切换线程。
连接蓝牙
选着需要连接的设备BleDevice进行连接,通常通过bleDevice.getName()获取蓝牙名称进行连接,若知道目标设备的Mac地址,也可通过bleDevice.getMac()进行连接。
- 根据UUID连接
public class BluetoothConnectTask extends AsyncTask<BluetoothDevice,Integer, BluetoothSocket> {
private final String TAG = "BluetoothConnectTask";
private BluetoothDevice device;
private BluetoothConnectCallback callback;
public BluetoothConnectTask(BluetoothConnectCallback callback) {
this.callback = callback;
}
@Override
protected BluetoothSocket doInBackground(BluetoothDevice... bluetoothDevices) {
Log.d(TAG, "doInBackground: ");
device = bluetoothDevices[0];
BluetoothSocket socket = null;
try {
socket = device.createRfcommSocketToServiceRecord(UUID.fromString(""));
if(socket != null && !socket.isConnected()){
socket.connect();
Log.d(TAG, "doInBackground: socket connect");
}
} catch (IOException e) {
e.printStackTrace();
if(socket != null){
try {
socket.close();
Log.d(TAG, "doInBackground: socket close");
} catch (IOException ioException) {
ioException.printStackTrace();
Log.e(TAG, "doInBackground: socket close fail");
}
}
}
return socket;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
Log.d(TAG, "onPreExecute: onConnectStarted");
if(callback != null){
callback.onConnectStarted();
}
}
@Override
protected void onPostExecute(BluetoothSocket socket) {
super.onPostExecute(socket);
if(socket != null && socket.isConnected()){
Log.d(TAG, "onPostExecute: onConnectSuccess");
callback.onConnectSuccess(device,socket);
} else {
Log.d(TAG, "onPostExecute: onConnectFail");
callback.onConnectFail(device);
}
}
}
执行任务
new BluetoothConnectTask(connectCallback).execute(device)
其中 device.createRfcommSocketToServiceRecord(UUID.fromString(""))中需要UUID,蓝牙设备中可有多个UUID,但具体使用哪一个需要根据蓝牙协议来。
2. 根据BluetoothGattCallback连接
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
bluetoothGatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(),
autoConnect, coreGattCallback, TRANSPORT_LE);
} else {
bluetoothGatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(),
autoConnect, coreGattCallback);
}
private BluetoothGattCallback coreGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
}
...
};
通过返回的bluetoothGatt,获取serviceUuid
bluetoothGattServices = gatt.getServices();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
uuid:
for (BluetoothGattService bluetoothGattService : bluetoothGattServices) {
if (bluetoothGattService.getUuid().toString().substring(4, 8).equals("ffff") || bluetoothGattService.getUuid().toString().substring(4, 8).equals("ffe0")) {
serviceUUid = bluetoothGattService.getUuid().toString();
List<BluetoothGattCharacteristic> characteristics = bluetoothGattService.getCharacteristics();
for (BluetoothGattCharacteristic characteristic : characteristics) {
if (characteristic.getUuid().toString().substring(4, 8).equals("ff00") || characteristic.getUuid().toString().substring(4, 8).equals("ffe1")) {
charaUUid = characteristic.getUuid().toString();
readNotifyData(bleDevice, serviceUUid, charaUUid);
break uuid;
}
}
}
}
}
因为一台蓝牙设备可以有多个serviceUUid ,而每一个serviceUUid 可以有多个characteristicUuid,需要根据蓝牙协议获取需要正在使用serviceUUid和characteristicUuid,然后用它们来与蓝牙设备进行信息传递。
发送数据
发送数据前将需要发送的数据转为byte[]才能进行通信。
调用封装好的方法writeData(final BleDevice bleDevice, String uuidService, String uuidRead, byte[] data)。
/**
* 发送数据
*
* @param bleDevice
* @param uuidService
* @param uuidRead
* @param data
*/
public static void writeData(final BleDevice bleDevice, String uuidService, String uuidRead, byte[] data) {
BleManager.getInstance().write(bleDevice, uuidService, uuidRead, data, new BleWriteCallback() {
@Override
public void onWriteSuccess(int current, int total, byte[] justWrite) {
Log.d(TAG, "writeData onWriteSuccess");
}
@Override
public void onWriteFailure(BleException exception) {
Log.e(TAG, "writeData onWriteFailure" + exception.getDescription());
}
});
}
BleManager.getInstance().write方法的上一层封装为:
/**
* write
*
* @param bleDevice
* @param uuid_service
* @param uuid_write
* @param data
* @param split
* @param sendNextWhenLastSuccess
* @param intervalBetweenTwoPackage
* @param callback
*/
public void write(BleDevice bleDevice,
String uuid_service,
String uuid_write,
byte[] data,
boolean split,
boolean sendNextWhenLastSuccess,
long intervalBetweenTwoPackage,
BleWriteCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("BleWriteCallback can not be Null!");
}
if (data == null) {
BleLog.e("data is Null!");
callback.onWriteFailure(new OtherException("data is Null!"));
return;
}
if (data.length > 20 && !split) {
BleLog.w("Be careful: data's length beyond 20! Ensure MTU higher than 23, or use spilt write!");
}
BleBluetooth bleBluetooth = multipleBluetoothController.getBleBluetooth(bleDevice);
if (bleBluetooth == null) {
callback.onWriteFailure(new OtherException("This device not connect!"));
} else {
if (split && data.length > getSplitWriteNum()) {
new SplitWriter().splitWrite(bleBluetooth, uuid_service, uuid_write, data,
sendNextWhenLastSuccess, intervalBetweenTwoPackage, callback);
} else {
bleBluetooth.newBleConnector()
.withUUIDString(uuid_service, uuid_write)
.writeCharacteristic(data, callback, uuid_write);
}
}
}
最终数据的发送是依靠BluetoothGatt的writeCharacteristic(byte[] data, BleWriteCallback bleWriteCallback, String uuid_write)完成的。
接收数据
接收数据同样离不开BluetoothGatt。
/**
* Reads the requested characteristic from the associated remote device.
*
* <p>This is an asynchronous operation. The result of the read operation
* is reported by the {@link BluetoothGattCallback#onCharacteristicRead}
* callback.
*
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*
* @param characteristic Characteristic to read from the remote device
* @return true, if the read operation was initiated successfully
*/
public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) == 0) {
return false;
}
if (VDBG) Log.d(TAG, "readCharacteristic() - uuid: " + characteristic.getUuid());
if (mService == null || mClientIf == 0) return false;
BluetoothGattService service = characteristic.getService();
if (service == null) return false;
BluetoothDevice device = service.getDevice();
if (device == null) return false;
synchronized (mDeviceBusyLock) {
if (mDeviceBusy) return false;
mDeviceBusy = true;
}
try {
mService.readCharacteristic(mClientIf, device.getAddress(),
characteristic.getInstanceId(), AUTHENTICATION_NONE);
} catch (RemoteException e) {
Log.e(TAG, "", e);
mDeviceBusy = false;
return false;
}
return true;
}
同样读取数据需要进一步封装,便于使用。
/**
* read
*/
public void readCharacteristic(BleReadCallback bleReadCallback, String uuid_read) {
if (mCharacteristic != null
&& (mCharacteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
handleCharacteristicReadCallback(bleReadCallback, uuid_read);
if (!mBluetoothGatt.readCharacteristic(mCharacteristic)) {
readMsgInit();
if (bleReadCallback != null)
bleReadCallback.onReadFailure(new OtherException("gatt readCharacteristic fail"));
}
} else {
if (bleReadCallback != null)
bleReadCallback.onReadFailure(new OtherException("this characteristic not support read!"));
}
}
为了便于调用,还可以再封装一下。
/**
* read
*
* @param bleDevice
* @param uuid_service
* @param uuid_read
* @param callback
*/
public void read(BleDevice bleDevice,
String uuid_service,
String uuid_read,
BleReadCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("BleReadCallback can not be Null!");
}
BleBluetooth bleBluetooth = multipleBluetoothController.getBleBluetooth(bleDevice);
if (bleBluetooth == null) {
callback.onReadFailure(new OtherException("This device is not connected!"));
} else {
bleBluetooth.newBleConnector()
.withUUIDString(uuid_service, uuid_read)
.readCharacteristic(callback, uuid_read);
}
}
这样调用的时候就方便了一些。这是直接读取蓝牙数据,通常我们的做法是通过发送消息的方式取读取数据。
蓝牙连接时已经设置了BluetoothGattCallback,在重写的onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) 中获取接受的数据,接收数据后会通过Hander将数据发送。
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
Iterator iterator = bleReadCallbackHashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
Object callback = entry.getValue();
if (callback instanceof BleReadCallback) {
BleReadCallback bleReadCallback = (BleReadCallback) callback;
if (characteristic.getUuid().toString().equalsIgnoreCase(bleReadCallback.getKey())) {
Handler handler = bleReadCallback.getHandler();
if (handler != null) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_CHA_READ_RESULT;
message.obj = bleReadCallback;
Bundle bundle = new Bundle();
bundle.putInt(BleMsg.KEY_READ_BUNDLE_STATUS, status);
bundle.putByteArray(BleMsg.KEY_READ_BUNDLE_VALUE, characteristic.getValue());
message.setData(bundle);
handler.sendMessage(message);
}
}
}
}
}
最后在需要的地方通过Handler获取数据即可。
case BleMsg.MSG_CHA_NOTIFY_DATA_CHANGE: {
BleNotifyCallback notifyCallback = (BleNotifyCallback) msg.obj;
Bundle bundle = msg.getData();
byte[] value = bundle.getByteArray(BleMsg.KEY_NOTIFY_BUNDLE_VALUE);
if (notifyCallback != null) {
notifyCallback.onCharacteristicChanged(value);
}
break;
}
至此蓝牙的简单使用流程完成。