这两天又在搞蓝牙,蓝牙伤我千百遍,我待蓝牙如初恋。
有位朋友说,做个appdemo,来和他的蓝牙模块进行交互。我发现我对蓝牙真的是连冰山一角都还没了解完。说说我都遇到了什么问题吧。
1.两个手机都打开蓝牙,如果离开设置蓝牙界面,难么你会发现你们都搜索不到彼此的设备。这不是你的错,这是谷歌的一个坑。
因为如下:
解决:
//设置蓝牙可以被其他搜索到
blue.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
//设置蓝牙可见的时间。0:是永久可见
blue.setDiscoverableTimeout(0);//0:蓝牙永久可见
这个时候又郁闷了。BaseAdapter的源码里面setDiscoverableTimeout()和setScanMode()都被/*hide*/注释着。啊,是不是想要蓝牙现身有点曲折,我们想要用这两个方法还是有办法的。利用反射,调用这两个方法。
添加如下代码:
public void setDiscoverableTimeout(BluetoothAdapter adapter,int timeout) {
try {
Method setDiscoverableTimeout = BluetoothAdapter.class.getMethod("setDiscoverableTimeout", int.class);
setDiscoverableTimeout.setAccessible(true);
Method setScanMode =BluetoothAdapter.class.getMethod("setScanMode", int.class,int.class);
setScanMode.setAccessible(true);
setDiscoverableTimeout.invoke(adapter, timeout);
setScanMode.invoke(adapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,timeout);
} catch (Exception e) {
e.printStackTrace();
}
}
这样就可以调用setDiscoverableTimeout()和setScanMode()方法了,就可以让其他设备扫描到我的设备了。
2.监听手机蓝牙是否被了另一个设备匹配。
我第一次用的是这个方法
private BroadcastReceiver stateChangeRec=new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action=intent.getAction();
if(action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)){
tv_bluetooth_status.setText("蓝牙连接状态:连接成功");
Log.i("tang","连接成功");
//do something
}else if(action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)){
tv_bluetooth_status.setText("蓝牙连接状态:连接失败");
Log.i("tang","连接失败");
}
}
};
我会发现,在另外一台设备向我请求匹配的时候,就已经执行ACTION_BOND_STATE_CHANGED这个操作了。然后第一反应是绝对这个方法不适合我的情况。立刻重新找方法,
于是就得到了这个:
private BroadcastReceiver stateChangeRec=new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action=intent.getAction();
if(action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)){
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device.getBondState() == BluetoothDevice.BOND_BONDING) {
Log.i("tang","正在配对");
} else if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
Log.i("tang","完成配对");
tv_bluetooth_status.setText("蓝牙连接状态:连接成功");
} else if (device.getBondState() == BluetoothDevice.BOND_NONE) {
Log.i("tang","取消配对");
}
}
}
};
这个就是在两个设备已经匹配完成的一个完整步骤。老夫也是松了一口气。
3.还有一个,那就是修改本机蓝牙设备的名字。网上都说的是setName("xx")就好。我好你个香蕉巴拉。。。
bluetoothAdapter.enable();//设置蓝牙为可用
bluetoothAdapter.setName(deviceName);//写入你想要设置的设备名字
setName其实是发送了一个广播,我们还需要来进行接收处理,完成整个操作。(很多博主都没说啊,坑死个人啊!)
这儿,我们需要发送一个改名字的广播!向全世界宣布,我的蓝牙我做主!
IntentFilter mFilter = new IntentFilter(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
mFilter.addAction(BluetoothAdapter.EXTRA_LOCAL_NAME);
registerReceiver(mReceiver, mFilter);
下面就是接受广播操作
//蓝牙广播接收器。修改名字时会调用。
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
tv_current_name.setText("当前蓝牙名字:"+bluetoothAdapter.getName());
String action = intent.getAction();
if (BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED.equals(action)){
Toast.makeText(context,"成功", Toast.LENGTH_SHORT).show();
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Set<BluetoothDevice> devices = bluetoothAdapter.getBondedDevices();
for(int i=0; i<devices.size(); i++)
{
BluetoothDevice device1 = (BluetoothDevice) devices.iterator().next();
Log.i("tang","2-2蓝牙名字="+device1.getName());
//System.out.println(device1.getName());
}
}
else if(BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){
Toast.makeText(context,"finished", Toast.LENGTH_SHORT).show();
}
}
};
这才是一个ojbk的操作。
这里面还有一个坑,真的是没找到有博主说过这个问题。setName(“xxx”)这里面的值,是有限制的!
广播发送的字节好像是限制早<=60个字节,所以setName的值不是无限的,(如果你要广播发送包含了名字)。60个字节,30个汉字,包含所发送的其他信息,setName的值,限制也是跟随你所发送的信息多少而决定的。
当蓝牙名字>限制的个数的时候,广播发送会失败,ErrorCode=18或者1,对于BLE蓝牙的ErrorCode网上也没有人说,也搜索不到。我也不知道为啥。(有谁知道为啥蓝牙名字>5的时候广播开启会失败,且ErrorCode=18/1,麻烦留言告知一声,谢谢!)
4.还有个误区。我在断开蓝牙的时候使用的是disconnet(),但是我的小伙伴用他的手机和我测试的时候说,蓝牙没有断开啊。我说不可能啊,我又添加了close,双保险,这下没问题了吧。我小伙伴依然说,蓝牙没断开啊,一看。他看的是“已配对设备”,我特么操着键盘给他扔过去。
已配对,不代表蓝牙没断开,只是说明两个设备在下次连接的时候不需要再次配对了。大家测试的时候不要进入这个误区了。
持续更新一波,我app已经做好。小伙伴说搜索不到,让我把设备变成从设备。我这个蓝牙小白,一脸蒙圈,然后又去百度。设备分为主设备和从设备。
话不多说,到底如何把一个设备变成从设备呢。代码敬上:
这是一个设置广播,打开广播的操作。
private void sendOutSheBei() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
settings = new AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) //广播模式: 低功耗,平衡,低延迟
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) //发射功率级别: 极低,低,中,高
.setConnectable(true) //能否连接,广播分为可连接广播和不可连接广播
.build();
//广播数据(必须,广播启动就会发送)
advertiseData = new AdvertiseData.Builder()
.setIncludeDeviceName(true) //包含蓝牙名称
.setIncludeTxPowerLevel(true) //包含发射功率级别
.addManufacturerData(1, new byte[]{23, 33}) //设备厂商数据,自定义
.build();
//扫描响应数据(可选,当客户端扫描时才发送)
scanResponse = new AdvertiseData.Builder()
.addManufacturerData(2, new byte[]{66, 66}) //设备厂商数据,自定义
.addServiceUuid(new ParcelUuid(UUID_SERVICE)) //服务UUID
// .addServiceData(new ParcelUuid(UUID_SERVICE), new byte[]{2}) //服务数据,自定义
.build();
mBluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
}
mBluetoothLeAdvertiser.startAdvertising(settings, advertiseData, scanResponse, mAdvertiseCallback);
if (mBluetoothLeAdvertiser == null) {
Toast.makeText(this, "该设备不支持蓝牙低功耗从设备通讯", Toast.LENGTH_SHORT).show();
this.finish();
return;
}
}
这里面有一点需要注意,.setIncludeDeviceName(true) //包含蓝牙名称 我开始以为这只是是否传递蓝牙名字的一个设置,我给它修改为.setIncludeDeviceName(false) ,别人就搜不到我的蓝牙了。你要是想让别人搜索到你的蓝牙,那你就老老实实的写为true。
这上面的代码还不够,还需要一个监听器,监听我们的广播是不是打开了。
// BLE广播Callback
private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
Log.i("tang","BLE广播开启成功");
}
@Override
public void onStartFailure(int errorCode) {
Log.i("tang","BLE广播开启失败,错误码"+errorCode);
}
};
我们一定还想要和其他设备进行信息交互,哪里监听?第一段代码里面就有一个监听回调,好的,现在敬上回调监听代码:
// BLE服务端Callback
private BluetoothGattServerCallback mBluetoothGattServerCallback = new BluetoothGattServerCallback() {
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
Log.i("tang", "从设备连接状态="+String.format(status == 0 ? (newState == 2 ? "与[%s]连接成功" : "与[%s]连接断开") : ("与[%s]连接出错,错误码:" + status)));
if(newState==2){
//连接成功
tv_bluetooth_status.setText("蓝牙连接状态:连接成功");
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
tv_bluetooth_status.setText("蓝牙连接状态:连接中断");
Toast.makeText(mContext, "蓝牙断开", Toast.LENGTH_SHORT).show();
mBluetoothLe.disconnect();
mBluetoothLe.close();
}
}, getPostTime(deviceTime));
}else{
tv_bluetooth_status.setText("蓝牙连接状态:连接中断");
}
}
@Override
public void onServiceAdded(int status, BluetoothGattService service) {
Log.i("tang", "从设备连接状态="+String.format(status == 0 ? "添加服务[%s]成功" : "添加服务[%s]失败,错误码:" + status, service.getUuid()));
}
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
String response = "CHAR_" + (int) (Math.random() * 100); //模拟数据
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, response.getBytes());// 响应客户端
Log.i("tang","客户端读取Characteristic[" + characteristic.getUuid() + "]:\n" + response);
}
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] requestBytes) {
// 获取客户端发过来的数据
String requestStr = new String(requestBytes);
Log.i("tang","客户端写入Characteristic[" + characteristic.getUuid() + "]:\n" + requestStr);
}
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
String response = "DESC_" + (int) (Math.random() * 100); //模拟数据
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, response.getBytes()); // 响应客户端
Log.i("tang","客户端读取Descriptor[" + descriptor.getUuid() + "]:\n" + response);
}
@Override
public void onDescriptorWriteRequest(final BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
// 获取客户端发过来的数据
String valueStr = Arrays.toString(value);
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);// 响应客户端
// 简单模拟通知客户端Characteristic变化
if (Arrays.toString(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE).equals(valueStr)) { //是否开启通知
final BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
SystemClock.sleep(3000);
String response = "CHAR_" + (int) (Math.random() * 100); //模拟数据
characteristic.setValue(response);
mBluetoothGattServer.notifyCharacteristicChanged(device, characteristic, false);
Log.i("tang","通知客户端改变Characteristic[" + characteristic.getUuid() + "]:\n" + response);
//logTv("通知客户端改变Characteristic[" + characteristic.getUuid() + "]:\n" + response);
}
}
}).start();
}
}
@Override
public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
Log.i("tang",String.format("onExecuteWrite:%s,%s,%s,%s", device.getName(), device.getAddress(), requestId, execute));
@Override
public void onNotificationSent(BluetoothDevice device, int status) {
Log.i("tang",String.format("onNotificationSent:%s,%s,%s", device.getName(), device.getAddress(), status));
}
@Override
public void onMtuChanged(BluetoothDevice device, int mtu) {
Log.i("tang",String.format("onMtuChanged:%s,%s,%s", device.getName(), device.getAddress(), mtu));
}
};
我做完了这些事之后,我发现,还有坑。我每次修改蓝牙本机名字之后,前几次没啥事儿,小伙伴突然给我说,不行了。一下回到解放前的名字了。我思考啊,思考啊。。。我觉得应该是发送广播里面设置名字的锅,它发广播只有一次,但是名字早给我发出去了,那我之后设置的不是都是打水漂?但是我仍然没把这一点想通,但是时间不等人啊,压力驱使我死马当活马医啊。
然后我就在每次修改名字的时候,发送一次广播,断开前一次的广播,重新发一次广播。很鸡肋,但是却有效,要是有小伙伴知道怎么处理,麻烦给我说一下。哎~但愿天堂没有bug。
不多就是这样,我遇到的就是这些坑。