在网上找了很多安卓蓝牙服务端(从设备)的编程示例,绝大部分都是客户端的示例,偶尔有一两个服务端的盒子,却也不完整,经过一系列折腾,总算是搞通了一个相对完整的例子,写出来给大家参考。
首先说一下作为服务端的几个基本过程:
第一步 申请权限
在MainActivity里面:
private void myRequetPermission() {
if (ContextCompat.checkSelfPermission(this,
ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH) != PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADMIN) != PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.ACCESS_FINE_LOCATION
},
1);
}
}
把myRequestPermisssion方法在onCreate()方法里调用一下。
同时接收一下权限申请的结果:
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode != 1) {
return;
}
for (int i = 0; i < permissions.length; i++) {
if (grantResults[i] == PERMISSION_GRANTED) {//选择了“始终允许”
Toast.makeText(this, "" + "权限" + permissions[i] + "申请成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "" + "权限" + permissions[i] + "申请失败", Toast.LENGTH_LONG).show();
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i])) {//用户选择了禁止不再询问
Log.e("PPPPP", "用户禁用权限并不再询问:" + permissions[i]);
} else {//选择禁止
Log.e("PPPPP", "用户选择了此次禁止权限");
}
}
}
}
第二步 创建安卓Service,在Service中初始化需要用的一些蓝牙相关成员
注:也不一定要创建安卓的Service,可以直接在Activity中实现下面的步骤,根据需要和习惯来吧。
private BluetoothAdapter mBluetoothAdapter; //蓝牙适配器对象
private BluetoothManager bluetoothManager = null; //系统的蓝牙管理器
private BluetoothLeAdvertiser mBluetoothLeAdvertiser; //作为蓝牙服务设备的广播发送器
private BluetoothGattServer mBluetoothGattServer; //蓝牙服务器对象
private BluetoothGattCharacteristic mCharacteristicRead; //蓝牙读数据的特征值
private BluetoothGattCharacteristic mCharacteristicWrite; //蓝牙写(发送)数据的特征值
private BluetoothGattService mGattService; //系统蓝牙服务吧?
//蓝牙服务的ID
private static UUID UUID_SERVER = UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb");
//蓝牙读特征值的UUID,具体的意义和作用不在这里介绍了,我这写死了。
private static UUID UUID_CHARREAD = UUID.fromString("0000fff1-0000-1000-8000-00805f9b34fb");
//蓝牙写特征值的UUID。
private static UUID UUID_CHARWRITE = UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb");
//蓝牙的描述信息特征值UUID
private static UUID UUID_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
第三步 初始化蓝牙并开启蓝牙广播
初始化蓝牙广播的主要目的是为了让其它设备扫描能发现自己。有些时候可能不要广播也可以由客户端主动来连接,这个情况可能主要用在不想暴露自己的蓝牙信息的场景,我没有试过。
private void initBluetooth() {
//获取蓝牙适配器对象
bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Toast.makeText(context, "蓝牙未启用", Toast.LENGTH_LONG);
//打开系统蓝牙,此处做法并不是很规范,因为开启蓝牙后其实还是需要再继续往下面进行下面的代码的,这里大家将就一下,认为蓝牙打开成功了吧。
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivity(enableBtIntent);
//TODO
// startActivityForResult(enableBtIntent, 1);
}
//如果没有启用蓝牙,启用蓝牙,感觉有点奇怪,跟上面有点重复,但是没影响
if (!mBluetoothAdapter.isEnabled()) {
Log.e("BBBBB", "启用蓝牙");
mBluetoothAdapter.enable();
}
//设置蓝牙名字,在别人扫描你的时候你显示的名字,如果你不要设置蓝牙名字,那跳过这一步
Log.e("BBBBB", "蓝牙名字:"+mBluetoothAdapter.getName());
mBluetoothAdapter.setName("CM-1900-1976");
//获取广播对象
mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
if(mBluetoothLeAdvertiser == null){
Log.e("BBBBB", "设备不支持广播");
Toast.makeText(this,"设备不支持蓝牙广播",Toast.LENGTH_SHORT).show();
return;
}
//准备广播的参数,最后这个setTimeout是设置广播 发送的时间间隔吧,0应该是默认值,
//如果填的太高,可能会导致别人扫描的时候不一定能扫描得到你
AdvertiseSettings.Builder settingsBuilder=new AdvertiseSettings.Builder();
settingsBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
.setConnectable(true)
.setTimeout(0);
AdvertiseSettings advertiseSettings = settingsBuilder.build();
//初始化广播数据
AdvertiseData.Builder dataBuilder = new AdvertiseData.Builder();
dataBuilder.setIncludeDeviceName(true)
.addServiceUuid(new ParcelUuid(UUID_SERVER))
.setIncludeTxPowerLevel(true);
AdvertiseData advertiseData = dataBuilder.build();
//初始化广播的应答数据(当别人扫描时,我们返回这个数据)
AdvertiseData scanResponse = new AdvertiseData.Builder()
//.addManufacturerData(MANUFACTURE_ID_RESPONSE, SPECIFIC_DATA_RESPONSE) //可以加上设备的制造商信息
.addServiceUuid(new ParcelUuid(UUID_SERVER))
.build();
//开始广播, mAdertiseCallback是开启广播时的回调,我们在下面定义它
mBluetoothLeAdvertiser.startAdvertising(advertiseSettings, advertiseData, scanResponse, mAdertiseCallback);
}
//广播成功或失败时的回调处理
private AdvertiseCallback mAdertiseCallback = new AdvertiseCallback() {
@Override
public void onStartFailure(int errorCode){
super.onStartFailure(errorCode);
Log.e("BBBBB","广播失败:"+errorCode);
}
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect){
super.onStartSuccess(settingsInEffect);
Log.e("BBBBB","服务端的广播成功开启");
//广播开启成功后,我们初始化并蓝牙服务(实现见下面)
initBleServer(getBaseContext());
}
};
第四步 添加启动蓝牙服务
//添加一个蓝牙服务,该服务下两个特征,一个读特征、一个写特征。
private void initBleServer(Context context){
//创建蓝牙服务对象
mGattService = new BluetoothGattService(UUID_SERVER, BluetoothGattService.SERVICE_TYPE_PRIMARY);
//创建读特征值,主要用于客户端获取蓝牙的基本信息
mCharacteristicRead = new BluetoothGattCharacteristic(UUID_CHARREAD, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ);
BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(UUID_DESCRIPTOR,
BluetoothGattCharacteristic.PERMISSION_READ);
mCharacteristicRead.addDescriptor(descriptor);
//给特征值添加描述符,我理解相当于告诉客户端这个特征值能干嘛,在这个例子里这个特征值就纯用于发送蓝牙的信息到客户端
mGattService.addCharacteristic(mCharacteristicRead);
//创建写特征值,这个特征值有读属性(READ),有写属性(WRITE,发送数据),还可以用于通知客户端接收数据(NOTIFY)
mCharacteristicWrite = new BluetoothGattCharacteristic(UUID_CHARWRITE,
BluetoothGattCharacteristic.PROPERTY_WRITE
| BluetoothGattCharacteristic.PROPERTY_READ
| BluetoothGattCharacteristic.PROPERTY_NOTIFY,
BluetoothGattCharacteristic.PERMISSION_WRITE
|BluetoothGattCharacteristic.PERMISSION_READ);
Log.e("BBBBB", "写UUID:"+UUID_CHARWRITE);
//设置描述符的值,此处要注意:如果要用通知方式向客户端发数据,需要加上此两行
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mCharacteristicWrite.addDescriptor(descriptor);
//mBluetoothGatt.writeDescriptor(descriptor);
//mCharacteristicWrite.setCharacteristicNotification(characteristic, enabled);
//将写特征值加入到服务
mGattService.addCharacteristic(mCharacteristicWrite);
//启动蓝牙服务,完成这个步骤后,我们就可以收发数据了
//注意:此处需要传入gattServerCallback回调,它的实现在下面
mBluetoothGattServer = bluetoothManager.openGattServer(context, gattServerCallback);
mBluetoothGattServer.addService(mGattService);
Log.e("BBBBB","初始化服务成功:initServices ok");
}
第五步:通过服务端发送数据
服务端(设备端)向客户端发送蓝牙数据时,有两种方式,一种是客户端主动向服务端发一个读的请求,服务端在收到读请求时,在服务的回调处理里去处理这个请求操作(见下面第六步中的回调),向客户端发送其所需要的数据。另一种方式是服务端通过通知方式主动向客户端发送数据,任何时候调用下面代码即可:
String str = "这是要发送的数据";
mCharacteristicWrite.setValue(str.getBytes());
Log.e("BBBBB", "发送数据到:" + bleClientDevice.getAddress());
//bleClientDevice是已连接到服务端的蓝牙客户端设备
mBluetoothGattServer.notifyCharacteristicChanged(bleClientDevice, mCharacteristicWrite, true);
第六步 实现蓝牙服务的回调处理
蓝牙服务的回调处理主要有以下几个,有点多,基本都在这了:
1. 客户端设备连接状态改变
2. 客户端读的请求的处理(向客户端发数据)
3. 客户端写的请求的处理(接收客户端的数据)
4. 客户端查询特征值描述的处理
5. 客户端描述请求被写入的处理
6. 蓝牙服务添加完成结果的处理
7. 向客户端发送通知数据的结果处理
8. 执行写数据的处理(我未用到过)
//clients是客户端蓝牙设备的MAP,我这个例子里将所有连上来的客户端都放到这个MAP里了
private ConcurrentHashMap<String, BluetoothDevice> clients = new ConcurrentHashMap<>();
//服务事件的回调
private BluetoothGattServerCallback gattServerCallback = new BluetoothGattServerCallback() {
//1、首先是连接状态的回调
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
super.onConnectionStateChange(device, status, newState);
Log.e("BBBBB","连接状态发生改变,安卓系统回调onConnectionStateChange:device name="+device.getName()+"address="+device.getAddress()+"status="+status+"newstate="+newState);
if(newState == BluetoothProfile.STATE_CONNECTED) {
Log.e("BBBBB", "Add device to clents:"+device.getName()+","+device.getAddress());
clients.put(device.getAddress(), device);
} else if(newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.e("BBBBB", "Remove device from clents:"+device.getName()+","+device.getAddress());
clients.remove(device.getAddress());
}
}
//处理客户端主动发起的读的操作,通过sendResponse发送数据过去,这个数据可以被多次请求重复发送
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
Log.e("BBBBB","客户端有读的请求,安卓系统回调该onCharacteristicReadRequest()方法");
Log.e("BBBBB", "offset="+offset+","+characteristic.getUuid().toString()+",value:"+StringUtils.toHexString(characteristic.getValue()));
mBluetoothGattServer.sendResponse(device,requestId, BluetoothGatt.GATT_SUCCESS,offset,characteristic.getValue());
}
//接收客户端发过来的数据,当有特征被写入时,回调该方法,写入的数据为参数中的value
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
Log.e("BBBBB","客户端有写的请求,安卓系统回调该onCharacteristicWriteRequest()方法");
//特征被读取,回复客户端接收数据成功,注意:此处作用为通知客户端已收到该数据,否则客户端可能会认为数据传输失败而断开蓝牙
mBluetoothGattServer.sendResponse(device,requestId,BluetoothGatt.GATT_SUCCESS,offset,value);
//客户端发送过来的数据,此处是给客户端又发送了回去
Log.e("BBBBB","收到"+requestBytes.length+"字节");
//响应客户端
mCharacteristicWrite.setValue(requestBytes);
mBluetoothGattServer.notifyCharacteristicChanged(device, mCharacteristicWrite,false);//用该方法通知characteristicRead数据发生改变
}
//特征被读取。当回复相应成功后,客户端胡读取然后触发本方法
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
super.onDescriptorReadRequest(device, requestId, offset, descriptor);
Log.e("BBBBB", "onDescriptorReadRequest调用了"+requestId);
mBluetoothGattServer.sendResponse(device,requestId,BluetoothGatt.GATT_SUCCESS,offset,null);
}
//当有描述请求被写入时,回调该方法,
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
mBluetoothGattServer.sendResponse(device,requestId,BluetoothGatt.GATT_SUCCESS,offset,value);
Log.e("BBBBB", "有数据写入");
// onResponseToClient(value,device,requestId,descriptor.getCharacteristic());
}
//蓝牙服务添加成功时的回调
@Override
public void onServiceAdded(int status, BluetoothGattService service){
super.onServiceAdded(status,service);
Log.e("BBBBB","添加服务成功,安卓系统回调该onServiceAdded()方法");
}
//MTU改变时的回调,MTU为每个蓝牙数据包最大的字节数,通常默认是20,
//如果你有一个数据超过20字节,那需要分成很多个小于或等于20字节的包来发送
@Override
public void onMtuChanged(BluetoothDevice device, int mtu) {
super.onMtuChanged(device, mtu);
Log.e("BBBBB", "MTU改变:"+mtu);
}
//当数据向客户端发送成功时,会调用 此回调
@Override
public void onNotificationSent(BluetoothDevice device, int status) {
super.onNotificationSent(device, status);
Log.e("BBBBB", "Notification Sent:"+ status);
}
//没试,不知道作用
@Override
public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
super.onExecuteWrite(device, requestId, execute);
Log.e("BBBBB", "执行写");
}
};
差不多就这么多吧。