這兩天又在搞藍牙,藍牙傷我千百遍,我待藍牙如初戀。
有位朋友說,做個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。
不多就是這樣,我遇到的就是這些坑。