谷歌官方文档:
https://developer.android.google.cn/guide/topics/connectivity/bluetooth-le.html
谷歌官方demo:
https://github.com/googlesamples/android-BluetoothLeGatt/
参考:
http://blog.csdn.net/chenfengdejuanlian/article/details/45787123
http://blog.csdn.net/z957250254/article/details/52411556
第一次接触蓝牙方面的知识,仅此记录,大家多多交流啊
开启蓝牙权限
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="true" />
检查设备是否支持低功耗蓝牙
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, "此设备不支持BLE(低功耗蓝牙)", Toast.LENGTH_LONG).show();
finish();
}
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
并在activity中动态申请权限(本例使用RxPermission请求权限)
/**
* 请求定位权限
*/
private void checkPermission() {
new RxPermissions((Activity) mContext).request(Manifest.permission.ACCESS_COARSE_LOCATION)
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Boolean granted) {
if (granted) {
initBlueTooth();
} else {
// 未获取权限
Toast.makeText(mContext, "未获取定位权限", Toast.LENGTH_LONG).show();
}
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
}
搜索蓝牙设备是需要一个BluetoothAdapter来扫描设备的,这里又要进行设备是否支持蓝牙功能的判断和是否开启蓝牙(若未开启,提示用户或强制开启蓝牙)
private void initBlueTooth() {
//BluetoothAdapter代表设备自己的蓝牙适配器(蓝牙无线电)
BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
//设备是否支持蓝牙
if (mBluetoothAdapter == null) {
Toast.makeText(mContext, "该设备不支持蓝牙功能", Toast.LENGTH_LONG).show();
finish();
}
//蓝牙是否启用,若未启用,请求用户启用
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
return;
}
//若开启蓝牙,直接扫描低功耗蓝牙设备;若未开启,提示用户开启,用户开启后在onActivityResult开启扫描
scanLeDevice(true);
}
扫描或关闭扫描
注意:扫描不能一直进行,本例SCAN_PERIOD=10000毫秒后停止扫描
/**
* 扫描低功耗蓝牙设备
* 您只能扫描蓝牙LE设备或扫描经典蓝牙设备,如蓝牙中所述 您无法同时扫描Bluetooth LE和传统设备。
*
* @param enable
*/
private void scanLeDevice(final boolean enable) {
if (enable) {
//Because scanning is battery-intensive, you should observe the following guidelines:
//As soon as you find the desired device, stop scanning.找到所需设备停止扫描
//Never scan on a loop, and set a time limit on your scan.切勿扫描循环,并在扫描上加上时间限制
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
mTextView.setText("扫描结束");
invalidateOptionsMenu();
}
}, SCAN_PERIOD);
mScanning = true;
//如果只想扫描特定类型的外设,则可以改为调用startLeScan(UUID [],BluetoothAdapter.LeScanCallback)
//提供指定您的应用程序支持的GATT服务的UUID对象数组
mBluetoothAdapter.startLeScan(mLeScanCallback);
mTextView.setText("扫描中...");
} else {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
mTextView.setText("扫描结束");
}
invalidateOptionsMenu();
}
mBluetoothAdapter.startLeScan()的参数mLeScanCallback是扫描结果的回调
mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (device != null) {
//过滤同一设备
if (!mDevices.contains(device)) {
//将设备添加到列表中
mDeviceListAdapter.addData(device);
}
}
}
});
}
};
其中mDeviceListAdapter为列表适配器,mDevices为列表适配器中的数据,若不进行同一设备的过滤,会返回很多相同设备
本demo是基于谷歌的示例代码,就直接将谷歌连接设备的相关类拷过来了,这里直接对其分析
在activity中绑定一个BluetoothLeService服务,此服务是用于对指定蓝牙设备进行连接或数据通信的服务
Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
其中mServiceConnection管理BluetoothLeService服务的生命周期
/**
* 管理蓝牙服务的生命周期
*/
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService();
if (!mBluetoothLeService.initialize()) {
Log.e(TAG, "Unable to initialize Bluetooth");
finish();
}
//成功启动初始化后自动连接到设备
mBluetoothLeService.connect(mDeviceAddress);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
mBluetoothLeService = null;
}
};
在连接设备之前,要判断设备是否支持低功耗蓝牙
public boolean initialize() {
// For API level 18 and above, get a reference to BluetoothAdapter through
// BluetoothManager.
if (mBluetoothManager == null) {
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
Log.e(TAG, "Unable to initialize BluetoothManager.");
return false;
}
}
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
return false;
}
return true;
}
BluetoothLeService绑定成功后,将要连接的设备地址传入,在service中连接设备
/**
* 连接到Bluetooth LE设备上托管的GATT服务器
*
* @param address 设备地址
* @return 返回是否连接是否成功启动(注意:是启动,并不是连接结果)
* 连接结果是在BluetoothGattCallback onConnectionStateChange异步返回的
*/
public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
// 以前连接的设备,尝试重连
if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)
&& mBluetoothGatt != null) {
Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
if (mBluetoothGatt.connect()) {
mConnectionState = STATE_CONNECTING;
return true;
} else {
return false;
}
}
//通过传入的设备地址获取设备
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}
// 我们要直接连接到设备,所以我们正在设置autoConnect参数为false (此处autoConnect为true可能不是立即连接)
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
Log.d(TAG, "Trying to create a new connection.");
mBluetoothDeviceAddress = address;
mConnectionState = STATE_CONNECTING;
return true;
}
其中device.connectGatt()的第三个参数mGattCallback为蓝牙设备与GATT服务端连接和数据通信的监听,此方法的返回值mBluetoothGatt 稍后介绍
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
//回调指示何时GATT客户端连接到远程GATT服务器/从远程GATT服务器断开连接
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
String intentAction;
//连接成功
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
mConnectionState = STATE_CONNECTED;
broadcastUpdate(intentAction);
Log.i(TAG, "Connected to GATT server.");
// 连接成功后尝试发现服务
Log.i(TAG, "Attempting to start service discovery:" +
mBluetoothGatt.discoverServices());
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_GATT_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
Log.i(TAG, "Disconnected from GATT server.");
broadcastUpdate(intentAction);
}
}
//发现新服务时调用回调
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
//回调报告特征性读取操作的结果
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
//由于远程特征通知而触发的回调
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
};
其中,连接成功、断开或发现服务或数据通信的回调均发送了广播,在activity中接收广播进行管理,这里先不关注每一个方法,尽量从一个流程分析
看到onConnectionStateChange回调中,判断状态为连接成功,调用了如下代码发现服务
mBluetoothGatt.discoverServices()
注意:
上文中mBluetoothGatt相当于手机与设备通信的管道
通过BluetoothGatt
demo中发现服务后发送广播到activity中,在activity中接收广播调用service(此service为BluetoothLeService,是自己写的对指定蓝牙设备进行连接或数据通信的服务)的获取服务(此处的服务为**蓝牙设备所拥有的服务**BluetoothGattService)方法getServices,然后在activity中对服务进行遍历,并存储每个服务下的特征值
此处分析下流程:调用mBluetoothGatt.discoverServices方法后,发现服务后会回调给BluetoothGattCallback的onServicesDiscovered,在调用mBluetoothGatt.getServices()返回一个包含BluetoothGattService服务的集合,再对集合进行遍历,获取每一个BluetoothGattService服务的特征
注意:(这里有点绕)
下面这张图为BluetoothGattService 和 BluetoothGattCharacteristic 和 BluetoothGattDescriptor的关系
private void displayGattServices(List gattServices) {
if (gattServices == null) return;
String uuid = null;
String unknownServiceString = getResources().getString(R.string.unknown_service);
String unknownCharaString = getResources().getString(R.string.unknown_characteristic);
ArrayListString, String>> gattServiceData = new ArrayListString, String>>();
ArrayListString, String>>> gattCharacteristicData
= new ArrayListString, String>>>();
mGattCharacteristics = new ArrayList>();
//遍历所有的服务
for (BluetoothGattService gattService : gattServices) {
//每一个服务的name 和 UUID保存在map中
HashMap<String, String> currentServiceData = new HashMap<String, String>();
uuid = gattService.getUuid().toString();
currentServiceData.put(
LIST_NAME, SampleGattAttributes.lookup(uuid, unknownServiceString));
currentServiceData.put(LIST_UUID, uuid);
//将 每一个 包含服务信息的map 都添加到集合中
gattServiceData.add(currentServiceData);
ArrayListString, String>> gattCharacteristicGroupData =
new ArrayListString, String>>();
List gattCharacteristics =
gattService.getCharacteristics();
ArrayList charas =
new ArrayList();
//遍历服务中的特征
for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
//将此服务中的特征添加到集合中
charas.add(gattCharacteristic);
//将此特征的UUID和name保存在map中
HashMap<String, String> currentCharaData = new HashMap<String, String>();
uuid = gattCharacteristic.getUuid().toString();
currentCharaData.put(
LIST_NAME, SampleGattAttributes.lookup(uuid, unknownCharaString));
currentCharaData.put(LIST_UUID, uuid);
//将 每一个 包含特征信息的map 都添加到集合中
gattCharacteristicGroupData.add(currentCharaData);
}
//将 每个服务中包含的所有特征的集合 添加到总集合中
mGattCharacteristics.add(charas);
//将 每个服务中包含的所有特征信息的集合 添加到总集合中
gattCharacteristicData.add(gattCharacteristicGroupData);
}
SimpleExpandableListAdapter gattServiceAdapter = new SimpleExpandableListAdapter(
this,
gattServiceData,
android.R.layout.simple_expandable_list_item_2,
new String[]{LIST_NAME, LIST_UUID},
new int[]{android.R.id.text1, android.R.id.text2},
gattCharacteristicData,
android.R.layout.simple_expandable_list_item_2,
new String[]{LIST_NAME, LIST_UUID},
new int[]{android.R.id.text1, android.R.id.text2}
);
mGattServicesList.setAdapter(gattServiceAdapter);
}
上面的代码如果不太明白可以慢慢看的,先看下面的
我们只需要知道,点击ExpandableListView group为此设备下的所有service,点击group会展开此service下的所有特征
/**
* ExpandableListView 子条目点击监听 点击条目,会把正在通信的特征关闭通知,再把点击条目的特征打开通知
*/
private final ExpandableListView.OnChildClickListener servicesListClickListener =
new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
int childPosition, long id) {
if (mGattCharacteristics != null) {
final BluetoothGattCharacteristic characteristic =
mGattCharacteristics.get(groupPosition).get(childPosition);
/**
* 正常流程
* 打开 指定接收特征 (指定UUID)通知
* 在 指定写入特征 写入数据
*/
//从指定UUID的特征打开接收(测试用)
if (characteristic.getUuid().toString().equals("0000ff02-0000-1000-8000-00805f9b34fb")) {
mBluetoothLeService.setCharacteristicNotification(
characteristic, true);
}
// //从指定UUID的特征写入(测试用)
if (characteristic.getUuid().toString().equals("0000ff01-0000-1000-8000-00805f9b34fb")) {
mBluetoothLeService.write(characteristic);
}
return true;
}
return false;
}
};
本例中
UUID为0000ff02-0000-1000-8000-00805f9b34fb的特征为读取特征
UUID为0000ff01-0000-1000-8000-00805f9b34fb的特征为写入特征
点击0000ff02-0000-1000-8000-00805f9b34fb条目打开读取通知
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
点击0000ff01-0000-1000-8000-00805f9b34fb条目写入数据
//写入数据(测试用)
public void write(BluetoothGattCharacteristic characteristic) {
//握手
characteristic.setValue(hexStringToByteArray("A2"));
mBluetoothGatt.writeCharacteristic(characteristic);
//握手和命令不能超过4秒,但又不能马上
SystemClock.sleep(500);
//命令
characteristic.setValue("SINSAM");
mBluetoothGatt.writeCharacteristic(characteristic);
}
数据写入后,就会在mGattCallback的onCharacteristicRead回调里通过characteristic.getValue()
拿到数据了
ok基本总结完了
源码点我