低功耗蓝牙
谷歌BLE引文原文:Bluetooth Low Energy
获取BluetoothAdapter
//Android 4.3引入BluetoothManager概念
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
检查是否支持蓝牙开启了蓝牙
//确保蓝牙在设备上启用,如果不是这样,显示一个对话框要求用户开启蓝牙
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
检查是否支持外围设备
(5.0才开始支持,不保证所有大于5.0的设备都支持)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
BluetoothLeAdvertiser mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
if (mBluetoothLeAdvertiser != null) {
Toast.makeText(this, "support peripheral", Toast.LENGTH_SHORT).show();
break;
}
}
Toast.makeText(this, "the device not support peripheral", Toast.LENGTH_SHORT).show();
扫描周围低功耗设备
private static boolean mScanning;
private static Handler mHandler = new Handler();
// 10秒后停止扫描
private static final long SCAN_PERIOD = 10000;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static void scanLeDevice(final boolean enable) {
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
mBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback);
}
}, SCAN_PERIOD);
mScanning = true;
ScanSettings.Builder scanSettings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_BALANCED);
mBluetoothAdapter.getBluetoothLeScanner().startScan(null, scanSettings.build(), mScanCallback);
} else {
mScanning = false;
mBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback);
}
}
// 设备扫描回调
private static List<BluetoothDevice> devices = new ArrayList<>();
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private static ScanCallback mScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
BluetoothDevice device = result.getDevice();
if (!devices.contains(device)) { //判断是否已经添加
devices.add(device);
LogUtil.d(result.getDevice().toString() + " rssi = " + result.getRssi());
}
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
LogUtil.d("onBatchScanResults = " + results.size());
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
LogUtil.d("onScanFailed = " + errorCode);
}
};
注意:只能扫描BLE设备或者扫描传统蓝牙设备,不能同时扫描BLE和传统蓝牙设备。
与设备连接
更确切的讲是与设备上的GATT服务连接
“`
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static void connectBle(String address) {
BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(address);
remoteDevice.connectGatt(mContext, false, bluetoothGattCallback);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private static BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
LogUtil.d(" onConnectionStateChange() called with: gatt = [" + gatt + "], status = [" + status + "], newState = [" + newState + "]");
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices();//连接成功,开始搜索服务,一定要调用此方法,否则获取不到服务
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
LogUtil.d(" onServicesDiscovered() called with: gatt = [" + gatt + "], status = [" + status + "]");
List<BluetoothGattService> services = gatt.getServices();
for (BluetoothGattService service : services) {
LogUtil.d("service = " + service.getUuid());
List<BluetoothGattService> includedServices = service.getIncludedServices();
for (BluetoothGattService includedService : includedServices) {
LogUtil.d(" includedService = " + includedService.getUuid());
}
List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
for (BluetoothGattCharacteristic characteristic : characteristics) {
LogUtil.d(" -characteristic = " + characteristic.getUuid() + " Value()" + characteristic.getValue() + " writhType = " + characteristic.getWriteType());
}
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
LogUtil.d(" onCharacteristicRead");
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
LogUtil.d(" onCharacteristicWrite");
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
LogUtil.d("onCharacteristicChanged");
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorRead(gatt, descriptor, status);
LogUtil.d("onDescriptorRead");
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
LogUtil.d("onDescriptorWrite");
}
@Override
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
super.onReliableWriteCompleted(gatt, status);
LogUtil.d("onReliableWriteCompleted");
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
super.onReadRemoteRssi(gatt, rssi, status);
LogUtil.d("onReadRemoteRssi");
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
super.onMtuChanged(gatt, mtu, status);
LogUtil.d("onMtuChanged");
}
};
“`
//字符串转byte
public static byte[] hexStringToBytes(String hexString) {
if (hexString == null || hexString.equals("")) {
return null;
}
hexString = hexString.toUpperCase();
int length = hexString.length() / 2;
char[] hexChars = hexString.toCharArray();
byte[] d = new byte[length];
for (int i = 0; i < length; i++) {
int pos = i * 2;
d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
}
return d;
}
// 转化字符串为十六进制编码
public static String toHexString(String s) {
String str = "";
for (int i = 0; i < s.length(); i++) {
int ch = (int) s.charAt(i);
String s4 = Integer.toHexString(ch);
str = str + s4;
}
return str;
}
// 转化十六进制编码为字符串
public static String hexToString(String s) {
byte[] baKeyword = new byte[s.length() / 2];
for (int i = 0; i < baKeyword.length; i++) {
try {
baKeyword[i] = (byte) (0xff & Integer.parseInt(s.substring(
i * 2, i * 2 + 2), 16));
} catch (Exception e) {
e.printStackTrace();
}
}
try {
s = new String(baKeyword, "utf-8");// UTF-16le:Not
} catch (Exception e1) {
e1.printStackTrace();
}
return s;
}
低功耗蓝牙Android 4.3版本以上支持 Api》18
- Service : 服务
- Characteristic : 特征
1 Profile,可以理解为一种规范,一种通信协议,profile存在于从机中。SIG规定了一些profile,如心率计,防丢器,HID OVER GATT等等。每个Profile中都包含多个Service。每个Service代表从机的一个能力。
2、Service可以理解为一个服务,在BLE从机中,可以有多个服务,譬如系统电量信息服务,系统信息服务,每个Service又包含多个Characteristic。每个具体的Characteristic值才是BLE通信的主体。比如当前电量是80%,会通过电量的Characteristic特征值存在从机的Profile里面,这样,主机就可以通过这个Characteristic值获得从机的80%电量值。
3、Characteristic BLE主从机通信均通过Characteristic实现。 可以理解为一个标签,通过这个标签可以获取或写入想要的内容。
4、UUID 唯一识别码。上述Service 和 Characteristic 均需要通过一个UUID来识别。UUID为128,但是在BLE中,UUID通常用16位,也就是两个字节来替代。16位UUID和128位UUID可以相互转换,具体如何实现需参考SIG文档。
综上,每个从机均由一个或若干个profile构成,不管是simpleprofile还是防丢器Profile等,而每个profile又由一些列Service组成,每个Service包含若干个Characteristic。 主机和从机之间的通信均是由 Characteristic实现。
蓝牙低能耗技术采用可变连接时间间隔,这个间隔根据具体应用可以设置为几毫秒到几秒不等。另外,因为BLE技术采用非常快速的连接方式,因此平时可以处于“非连接”状态(节省能源),此时链路两端相互间只是知晓对方,只有在必要时才开启链路,然后在尽可能短的时间内关闭链路。
每个蓝牙4.0的设备都是通过服务和特征来展示自己的,一个设备必然包含一个或多个服务,每个服务下面又包含若干个特征。特征是与外界交互的最小单位。比如说,一台蓝牙4.0设备,用特征A来描述自己的出厂信息,用特征B来与收发数据等。
服务和特征都是用UUID来唯一标识的,UUID的概念如果不清楚请自行google,国际蓝牙组织为一些很典型的设备(比如测量心跳和血压的设备)规定了标准的service UUID(特征的UUID比较多,这里就不列举了),如下:
蓝牙4.0分为标准蓝牙和低功耗蓝牙(BLE),标准蓝牙就是手机上用的那种,低功能耗蓝牙由于其具有最大化的待机时间、快速连接和低峰值的发送和接收特性,被广泛用于智能手表、智能手环等可穿戴设备上。在安卓4.3之前,安卓平台上的BLE开发相当难搞,好在谷歌在4.3之后发布了官方的API。在安卓5.0之后又引入了新的API,原来的API已经被废弃。在新的系统里采用旧API开发的APP仍可使用,但采用新API开发的APP只能在LOLLIPOP即安卓5.0及其以后的版本使用。
坑
- Android从4.3(API 18)才支持BLE,只支持作为中心设备 (Central) 模式,这就意味着 Android 设备只能主动扫描和链接其他外围设备 (Peripheral)(两个同是中心设备或者两个同是外围设备是无法连接的)。从Android 5.0(API Level 21)开始两种模式都支持(绝大部分)
- 官方的Demo在6.0设备上会有动态权限问题 java.lang.SecurityException: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission to get scan results
动态申请或者把target降到23以内