本文轉(zhuǎn)自冼東芝的文章Android BLE4.+ 藍(lán)牙開發(fā)國(guó)產(chǎn)手機(jī)兼容性解決方案瓷马。
如有版權(quán)問題难捌,請(qǐng)私信膝宁,謝謝。
轉(zhuǎn)載請(qǐng)注明出處根吁。https://blog.csdn.net/u014418171/article/details/81219297
算是做了n年的智能穿戴BLE開發(fā)了, 首先對(duì)國(guó)內(nèi)的安卓開發(fā)者提醒下 , BLE開發(fā)是真的很坑, 特別是安卓, ios端也坑, 但沒安卓坑 因?yàn)閲?guó)產(chǎn)有很多手機(jī) 各種奇葩兼容都有,
其實(shí)這些方案我很早就寫到云筆記里了,一直沒公開, 這里的解決方案大部分都是網(wǎng)上搜不到 或者網(wǎng)上搜到類似的問題, 但回復(fù)基本上是回答[無法解決] 或 [重啟手機(jī)解決]等 沒意義的解決辦法,讓我很無語…
以下內(nèi)容可能涉及到各種系統(tǒng)類源碼
你可以通過這里閱讀 https://www.androidos.net.cn/
廢話不多說, 希望對(duì)你們有用
一.刷新藍(lán)牙app的狀態(tài)
問題描述:
某些手機(jī)用久了會(huì)出現(xiàn)掃描不到任何設(shè)備的bug,
此時(shí)是因?yàn)槭謾C(jī)誤認(rèn)為本app不是[ble類] app , f**k!!!!! 還有這種操作???
但值得注意的是, 這只是一種原因,[ 掃描不到任何設(shè)備的bug] 有很多種原因, 詳情請(qǐng)看第3點(diǎn)
解決方案:
[目前網(wǎng)上沒有與我類似的解決辦法, 所以具體副作用自測(cè)]
參考IBluetoothManager.aidl
系統(tǒng)源碼
出現(xiàn)該問題時(shí)于是通過查看系統(tǒng)源碼找到isBleAppPresent
方法 ,反射調(diào)用其后居然返回false ,
換了一臺(tái)能正常使用的手機(jī) 調(diào)用該方法 返回true 因此證實(shí)了這個(gè)問題,
然后發(fā)現(xiàn)系統(tǒng)有私有的updateBleAppCount
方法, 可以刷新ble類app的狀態(tài),反射調(diào)用之…
因此解決了 [偶爾ble設(shè)備掃描不出來]的bug),
通過傳入你的app包名 以 刷新 藍(lán)牙app的錯(cuò)誤狀態(tài)
public static void refreshBleAppFromSystem(Context context, String packageName) {
//6.0以上才有該功能,不是6.0以上就算了
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return;
}
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null) {
return;
}
if (!adapter.isEnabled()) {
return;
}
try {
Object mIBluetoothManager = getIBluetoothManager(adapter);
Method isBleAppPresentM = mIBluetoothManager.getClass().getDeclaredMethod("isBleAppPresent");
isBleAppPresentM.setAccessible(true);
boolean isBleAppPresent = (Boolean) isBleAppPresentM.invoke(mIBluetoothManager);
if (isBleAppPresent) {
return;
}
Field mIBinder = BluetoothAdapter.class.getDeclaredField("mToken");
mIBinder.setAccessible(true);
Object mToken = mIBinder.get(adapter);
//刷新偶爾系統(tǒng)無故把a(bǔ)pp視為非 BLE應(yīng)用 的錯(cuò)誤標(biāo)識(shí) 導(dǎo)致無法掃描設(shè)備
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//8.0+ (部分手機(jī)是7.1.2 也是如此)
Method updateBleAppCount = mIBluetoothManager.getClass().getDeclaredMethod("updateBleAppCount", IBinder.class, boolean.class, String.class);
updateBleAppCount.setAccessible(true);
//關(guān)一下 再開
updateBleAppCount.invoke(mIBluetoothManager, mToken, false, packageName);
updateBleAppCount.invoke(mIBluetoothManager, mToken, true, packageName);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
try {
//6.0~7.1.1
Method updateBleAppCount = mIBluetoothManager.getClass().getDeclaredMethod("updateBleAppCount", IBinder.class, boolean.class);
updateBleAppCount.setAccessible(true);
//關(guān)一下 再開
updateBleAppCount.invoke(mIBluetoothManager, mToken, false);
updateBleAppCount.invoke(mIBluetoothManager, mToken, true);
} catch (NoSuchMethodException e) {
//8.0+ (部分手機(jī)是7.1.2 也是如此)
try {
Method updateBleAppCount = mIBluetoothManager.getClass().getDeclaredMethod("updateBleAppCount", IBinder.class, boolean.class, String.class);
updateBleAppCount.setAccessible(true);
//關(guān)一下 再開
updateBleAppCount.invoke(mIBluetoothManager, mToken, false, packageName);
updateBleAppCount.invoke(mIBluetoothManager, mToken, true, packageName);
} catch (NoSuchMethodException e1) {
e1.printStackTrace();
}
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
二.明明gatt.disconnect() 斷開藍(lán)牙了,甚至關(guān)閉了手機(jī)的藍(lán)牙,甚至飛行模式了, 設(shè)備仍然在 [已連接] 狀態(tài)!!! 設(shè)備離手機(jī)遠(yuǎn)了才斷開,說明,這壓根就沒斷開啊!
看到標(biāo)題知道國(guó)產(chǎn)手機(jī)奇葩了吧? 而且我相信不少人都遇到這個(gè)問題, 這個(gè)問題經(jīng)常出現(xiàn)在華為>小米>魅族>(VIVO|OPPO) 上员淫。
問題描述
首先導(dǎo)致這個(gè)原因可能是:
- 操作:
gatt.disconnect/connect
斷開,連接,斷開,連接,斷開… 反復(fù)重試n次, 有一定的幾率導(dǎo)致系統(tǒng)殘留了該gatt的引用, app這邊沒有拿到這個(gè)引用 (app操作藍(lán)牙api是通過 remote aidl 操作遠(yuǎn)程的 系統(tǒng)service ), 系統(tǒng)藍(lán)牙app 也沒有了這個(gè)引用 ,于是即使你 直接關(guān)閉手機(jī)藍(lán)牙, 也沒有斷開連接… 有些手機(jī)直到開啟飛行模式 才會(huì)斷開, 而有些手機(jī) 即使開啟飛行模式也不會(huì)斷開! 這得看這些手機(jī) 的 飛行模式 的實(shí)現(xiàn)代碼的區(qū)別了,暫時(shí)沒去研究. - 猜測(cè)連接設(shè)備后 被系統(tǒng)殺掉/ 或手動(dòng)殺掉 也會(huì)導(dǎo)致這種情況
解決方案
1.首先你可以獲取真正的連接狀態(tài):
[目前網(wǎng)上沒有與我類似的解決辦法, 所以具體副作用自測(cè)]
參看IBluetooth.aidl
系統(tǒng)源碼
出現(xiàn)這種假斷開問題時(shí), 筆者曾經(jīng)嘗試 各種gatt.getConnectionState()
,BluetoothManager.getConnectionState
都是 給我們開發(fā)者返回 已斷開!, 但實(shí)際上沒有斷開, 經(jīng)過一番研究后 發(fā)現(xiàn) 判斷內(nèi)部連接狀態(tài)可以通過另一個(gè)辦法 而不通過 gatt,
則BluetoothDevice
類 內(nèi)部的isConnected()
方法
這個(gè)方法被標(biāo)記為@SystemApi
和@hide
, 不能直接使用.
并且在低版本的手機(jī)上沒有, 查看了源碼isConnected
是由IBluetooth.getConnectionState()
實(shí)現(xiàn)的, 低版本有getConnectionState
:
@SystemApi
public boolean isConnected() {
final IBluetooth service = sService;
if (service == null) {
// BT is not enabled, we cannot be connected.
return false;
}
try {
return service.getConnectionState(this) != CONNECTION_STATE_DISCONNECTED;
} catch (RemoteException e) {
Log.e(TAG, "", e);
return false;
}
}
于是我們可以通過反射
BluetoothDevice.isConnected/ IBluetooth .getConnectionState
實(shí)現(xiàn)內(nèi)部連接狀態(tài)的判斷
高低版本兼容的代碼如下:
public static final int CONNECTION_STATE_DISCONNECTED = 0;
public static final int CONNECTION_STATE_CONNECTED = 1;
public static final int CONNECTION_STATE_UN_SUPPORT = -1;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@SuppressLint("PrivateApi")
public static int getInternalConnectionState(String mac) {
//該功能是在21 (5.1.0)以上才支持, 5.0 以及以下 都 不支持
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return CONNECTION_STATE_UN_SUPPORT;
}
if(Build.MANUFACTURER.equalsIgnoreCase("OPPO")){//OPPO勿使用這種辦法判斷, OPPO無解
return CONNECTION_STATE_UN_SUPPORT;
}
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
BluetoothDevice remoteDevice = adapter.getRemoteDevice(mac);
Object mIBluetooth = null;
try {
Field sService = BluetoothDevice.class.getDeclaredField("sService");
sService.setAccessible(true);
mIBluetooth = sService.get(null);
} catch (Exception e) {
return CONNECTION_STATE_UN_SUPPORT;
}
if (mIBluetooth == null) return CONNECTION_STATE_UN_SUPPORT;
boolean isConnected;
try {
Method isConnectedMethod = BluetoothDevice.class.getDeclaredMethod("isConnected");
isConnectedMethod.setAccessible(true);
isConnected = (Boolean) isConnectedMethod.invoke(remoteDevice);
isConnectedMethod.setAccessible(false);
} catch (Exception e) {
//如果找不到,說明不兼容isConnected, 嘗試去使用getConnectionState 判斷
try {
Method getConnectionState = mIBluetooth.getClass().getDeclaredMethod("getConnectionState", BluetoothDevice.class);
getConnectionState.setAccessible(true);
int state = (Integer) getConnectionState.invoke(mIBluetooth, remoteDevice);
getConnectionState.setAccessible(false);
isConnected = state == CONNECTION_STATE_CONNECTED;
} catch (Exception e1) {
return CONNECTION_STATE_UN_SUPPORT;
}
}
return isConnected ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED;
}
2.嘗試斷開
[目前網(wǎng)上沒有與我類似的解決辦法, 所以具體副作用自測(cè)]
參考AdapterService.java
系統(tǒng)源碼
仍然是從IBluetooth
入手, 因?yàn)?應(yīng)用層 能拿到的東西不多
研究源碼發(fā)現(xiàn) 有兩個(gè)函數(shù)可以嘗試讓 ble 服務(wù)關(guān)閉和啟動(dòng),分別是onLeServiceUp / onBrEdrDown
示例:
@RequiresApi(api = Build.VERSION_CODES.M)
public static void setLeServiceEnable(boolean isEnable) {
Object mIBluetooth;
try {
Field sService = BluetoothDevice.class.getDeclaredField("sService");
sService.setAccessible(true);
mIBluetooth = sService.get(null);
} catch (Exception e) {
return;
}
if (mIBluetooth == null) return;
try {
if (isEnable) {
Method onLeServiceUp = mIBluetooth.getClass().getDeclaredMethod("onLeServiceUp");
onLeServiceUp.setAccessible(true);
onLeServiceUp.invoke(mIBluetooth);
} else {
Method onLeServiceUp = mIBluetooth.getClass().getDeclaredMethod("onBrEdrDown");
onLeServiceUp.setAccessible(true);
onLeServiceUp.invoke(mIBluetooth);
}
} catch (Exception e) {
e.printStackTrace();
}
}
但該方法可能在某些手機(jī)上仍然無效,原因是 很多國(guó)產(chǎn)手機(jī)都重新修改了 藍(lán)牙底層相關(guān)代碼, 為了所謂的省電, 所以單靠看原生系統(tǒng)的源碼可能是無意義的
之后琢磨出另一個(gè)解決辦法 那就是…. 嘗試連接~然后斷開!
方法很簡(jiǎn)單,直接通過gatt.connectGatt()
等待連接成功后disconnect
一次, 此時(shí)設(shè)備終于斷開了!
原因可能是connect
后 刷新了殘留的gatt引用 于是app又重新拿到了最新的引用, 此時(shí)可以操作設(shè)備斷開了
...
gatt.connectGatt();
...
onConnectionStateChange(final BluetoothGatt gatt, final int status, int newState){
if (newState == BluetoothProfile.STATE_CONNECTED){
gatt.disconnect();
}
}
不過你要注意下不要和你的正常連接邏輯沖突
以上操作,手機(jī)顯示的藍(lán)牙圖標(biāo)一直是關(guān)閉的, 你可能想問我 : 那手機(jī)藍(lán)牙關(guān)了 怎么反射讓他顯示開… 這個(gè)你只能問這個(gè)手機(jī)的相關(guān)工程師為啥這么腦殘了… 無解, 我們只考慮app問題,系統(tǒng)腦殘管不了
三.多次打開app/退出app/后臺(tái)被殺等, 導(dǎo)致掃描不到設(shè)備,并返回ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED 錯(cuò)誤!
問題描述
但值得注意的是, 這只是第二種原因,[ 掃描不到任何設(shè)備的bug]還有其他原因, 詳情請(qǐng)看第4點(diǎn)
掃描周圍的BLE設(shè)備時(shí)某些手機(jī)會(huì)遇到 GATT_Register: cant Register GATT client, MAX client reached!
或者回調(diào)中的 onScanFailed 返回了 errorCode =2 則: ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED
具體表現(xiàn)則為 明明周圍有很多設(shè)備,但是掃描不到任何東西
查到
1.https://blog.csdn.net/chy555chy/article/details/53788748
2.https://stackoverflow.com/questions/27516399/solution-for-ble-scans-scan-failed-application-registration-fail
3.http://detercode121.blogspot.com/2012/04/bluetooth-lowenergy-solution-for-ble.html
等等 沒有一個(gè)是正常的解決辦法,上面這些修復(fù)方案是用代碼實(shí)現(xiàn)關(guān)閉藍(lán)牙然后重新打開藍(lán)牙來釋放 可是 國(guó)產(chǎn)的手機(jī)會(huì)彈出藍(lán)牙授權(quán)的 比如我們要后臺(tái)掃描重連設(shè)備時(shí)遇到這種情況 難道要彈出授權(quán)讓用戶確定? 那還要后臺(tái)重連功能干啥…
而且,有些手機(jī)即使關(guān)閉藍(lán)牙再打開 也無法釋放,有些手機(jī)關(guān)閉藍(lán)牙后 再打開會(huì)卡死系統(tǒng), 導(dǎo)致藍(lán)牙圖標(biāo)一直卡在那很久 才打開了藍(lán)牙…
解決方案
[目前網(wǎng)上沒有與我類似的解決辦法, 所以具體副作用自測(cè)]
參考IBluetoothGatt.aidl
參考BluetoothLeScanner.java
參考ScanManager.java
參考GattService.java
問題就在于 一些手機(jī)在startScan
掃描的過程中還沒來得及stopScan
,就被系統(tǒng)強(qiáng)制殺掉了, 導(dǎo)致mClientIf未被正常釋放,實(shí)例和相關(guān)藍(lán)牙對(duì)象已被殘留到系統(tǒng)藍(lán)牙服務(wù)中,
打開app后又重新初始化ScanCallback
多次被注冊(cè),導(dǎo)致每次的掃描mClientIf的值都在遞增, 于是mClientIf的值
在增加到一定程度時(shí)(最大mClientIf數(shù)量視國(guó)產(chǎn)系統(tǒng)而定 不做深究),onScanFailed
返回了errorCode =2
至今網(wǎng)上無任何正常的解決辦法
于是 我查看了系統(tǒng)源碼 發(fā)現(xiàn)關(guān)鍵位置BluetoothLeScanner
類下的 BleScanCallbackWrapper#startRegistration()
掃描是通過registerClient
傳入 mClientIf 來實(shí)現(xiàn)的,
在stopScan
時(shí)調(diào)用了iGatt.stopScan()
和iGatt.unregisterClient()
進(jìn)行解除注冊(cè). 了解該原理后 我們就可以反射調(diào)用這個(gè)方法 , 至于解除mClientIf哪個(gè)值 需要你自己做存儲(chǔ)記錄
這里我寫的是解除全部客戶端 mClientIf的范圍是 0~40
問題至此完美解決 這可能是目前全網(wǎng)唯一不用關(guān)閉/開啟藍(lán)牙就能完美解決該問題的方案
public static boolean releaseAllScanClient() {
try {
Object mIBluetoothManager = getIBluetoothManager(BluetoothAdapter.getDefaultAdapter());
if (mIBluetoothManager == null) return false;
Object iGatt = getIBluetoothGatt(mIBluetoothManager);
if (iGatt == null) return false;
Method unregisterClient = getDeclaredMethod(iGatt, "unregisterClient", int.class);
Method stopScan;
int type;
try {
type = 0;
stopScan = getDeclaredMethod(iGatt, "stopScan", int.class, boolean.class);
} catch (Exception e) {
type = 1;
stopScan = getDeclaredMethod(iGatt, "stopScan", int.class);
}
for (int mClientIf = 0; mClientIf <= 40; mClientIf++) {
if (type == 0) {
try {
stopScan.invoke(iGatt, mClientIf, false);
} catch (Exception ignored) {
}
}
if (type == 1) {
try {
stopScan.invoke(iGatt, mClientIf);
} catch (Exception ignored) {
}
}
try {
unregisterClient.invoke(iGatt, mClientIf);
} catch (Exception ignored) {
}
}
stopScan.setAccessible(false);
unregisterClient.setAccessible(false);
BLESupport.getDeclaredMethod(iGatt, "unregAll").invoke(iGatt);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
其中如果你想獲得 mClientIf 的值,方便研究該問題 可以嘗參考以下代碼
其中參數(shù)ScanCallback
類 是安卓6.0掃描回調(diào)類
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static boolean isScanClientInitialize(ScanCallback callback) {
try {
Field mLeScanClientsField = getDeclaredField(BluetoothLeScanner.class, "mLeScanClients");
// HashMap<ScanCallback, BleScanCallbackWrapper>()
HashMap callbackList = (HashMap) mLeScanClientsField.get(BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner());
int size = callbackList == null ? 0 : callbackList.size();
if (size > 0) {
Iterator iterator = callbackList.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
Object key = entry.getKey();
Object val = entry.getValue();
if (val != null && key != null && key == callback) {
int mClientIf = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Field mScannerIdField = getDeclaredField(val, "mScannerId");
mClientIf = mScannerIdField.getInt(val);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Field mClientIfField = getDeclaredField(val, "mClientIf");
mClientIf = mClientIfField.getInt(val);
}
System.out.println("mClientIf=" + mClientIf);
return true;
}
}
} else {
if (callback != null) {
return false;
}
}
} catch (Exception ignored) {
}
return true;
}
四、掃描不到設(shè)備
前面說了好幾個(gè)掃描不到設(shè)備的原因, 這里還有呢…
1.未開啟位置訪問權(quán)限Manifest.permission.ACCESS_COARSE_LOCATION
如果你是6.0系統(tǒng) 則需要申請(qǐng)?jiān)摍?quán)限 才能掃描設(shè)備, 檢查和申請(qǐng)網(wǎng)上有 這里不重復(fù)說了
2.檢查GPS的 LOCATION_MODE
是否開啟,否則在OPPO/VIVO等手機(jī) 無法掃描設(shè)備击敌。
//代碼 反編譯 nrfconnect 參考得來:
public static boolean hasLocationEnablePermission(Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
int locationMode = Settings.Secure.LOCATION_MODE_OFF;
try {
locationMode = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.LOCATION_MODE);
} catch (Exception ignored) {
}
if (locationMode != Settings.Secure.LOCATION_MODE_OFF) {
return true;
}
return false;
}
//沒有權(quán)限則跳轉(zhuǎn)到 gps界面授權(quán)
if(!hasLocationEnablePermission(this)){
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
3.安卓7.0不允許在30s內(nèi)連續(xù)掃描5次,否則無法掃描到任何設(shè)備,只能重啟app, 你可以寫一個(gè)算法 比如每次先延時(shí)30/5=6秒 才開始掃描, 以防止用戶一直點(diǎn)掃描按鈕 , 或者使用動(dòng)態(tài)計(jì)算 以減少用戶等待時(shí)間
五介返、其他注意點(diǎn)
1.為了加快連接設(shè)備的速度 , 你可以不掃描設(shè)備直接通過mac地址連接 ,使用 gatt.connectGatt ,但有時(shí)候連接不上是因?yàn)?設(shè)備信息可能變化了, 但系統(tǒng)緩存沒變,所以一直連接不上, 即使連接上了 馬上返回各種-133 -192等錯(cuò)誤, 解決辦法是 你需要 重新掃描這個(gè)mac一下,找到了mac ,再連接
2.掃描設(shè)備的時(shí)候 要切記, 掃描到了后 先停止掃描, 過1秒左右 再連接, 避免掃描的時(shí)候連接, 導(dǎo)致連接過程中 緩存再次被刷新
3.無任何原因, app掃描不到該設(shè)備,但能搜索到其他設(shè)備, 而另一個(gè)手機(jī)卻都能搜索到, 試試下載 nrf的 nrfconnect 去搜索測(cè)試(同一臺(tái)手機(jī)), 若 nrfconnect 能搜索到 則是app代碼問題, 否則檢測(cè) 設(shè)備藍(lán)牙晶振頻率 是否不支持該手機(jī)的藍(lán)牙頻率發(fā)現(xiàn)范圍! 聯(lián)系相關(guān)開發(fā)人員解決
4.距離防丟功能, 通過rssi可以拿到設(shè)備距離手機(jī)的信號(hào)值來判斷 設(shè)備是否遠(yuǎn)離手機(jī) 觸發(fā)防丟警報(bào), 但rssi信號(hào) 受各種環(huán)境因素影響, 所以有點(diǎn)坑 , 建議做延遲處理 ,意思是達(dá)到防丟rssi值時(shí) 延時(shí)n秒 才警報(bào), 若在n秒內(nèi) 又恢復(fù), 說明只是信號(hào)突然弱了一下. 無需 警報(bào). 還有就是因素太多了 ,和手機(jī)的藍(lán)牙模塊有關(guān), 和設(shè)備的藍(lán)牙天線有關(guān), 功率有關(guān)等, 建議在app內(nèi)添加 一個(gè)用戶可以設(shè)定的rssi防丟范圍, 因?yàn)槌绦驔]法精準(zhǔn)計(jì)算
5.掃描藍(lán)牙設(shè)備callback回調(diào)時(shí) 建議丟到另外一個(gè)線程用隊(duì)列去處理, 不要在掃描回調(diào)里處理耗時(shí)邏輯., 同理 在onCharacteristicChanged 中接收設(shè)備notify通知返回的數(shù)據(jù)時(shí), 不要在此方法內(nèi)進(jìn)行耗時(shí)處理, 否則大量數(shù)據(jù)過來時(shí)會(huì)100%丟包!!! 解決辦法和前面的一樣. (話說某BLE開源框架就有這個(gè)問題,還好我用我自己寫的)
6.同步大量數(shù)據(jù)時(shí), 某些手機(jī)完美正常, 某些手機(jī)出現(xiàn)丟包嚴(yán)重,建議修改連接間隔 同步前 使用gatt.requestConnectionPriority(CONNECTION_PRIORITY_HIGH) , 同步完成后 恢復(fù)原來的連接間隔CONNECTION_PRIORITY_BALANCED
7.對(duì)設(shè)備進(jìn)行OTA升級(jí)后,直接使用mac來連接, 連接不上, 原因是系統(tǒng)緩存沒刷新, 你需要掃描后停止掃描再連接
8.連接之前建議先把 gatt.close一下
9.使用gatt.discoverServices()發(fā)現(xiàn)服務(wù)之前,建議先 sleep 500 毫秒, 因?yàn)閯倓傔B接上, 系統(tǒng)有些東西需要刷新,同理,遇到任何問題 延時(shí)一下看看能否解決, 因?yàn)橛行┫到y(tǒng)的藍(lán)牙很慢很卡,甚至手動(dòng)關(guān)閉藍(lán)牙 都卡死在那 ,偶爾還死機(jī)重啟了…
六、補(bǔ)充
貼出一些上面缺失的函數(shù),因?yàn)榉奖愫蜏p少代碼重復(fù)量, 所以上面沒貼
@SuppressLint("PrivateApi")
public static Object getIBluetoothGatt(Object mIBluetoothManager) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Method getBluetoothGatt = getDeclaredMethod(mIBluetoothManager, "getBluetoothGatt");
return getBluetoothGatt.invoke(mIBluetoothManager);
}
@SuppressLint("PrivateApi")
public static Object getIBluetoothManager(BluetoothAdapter adapter) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Method getBluetoothManager = getDeclaredMethod(BluetoothAdapter.class, "getBluetoothManager");
return getBluetoothManager.invoke(adapter);
}
public static Field getDeclaredField(Class<?> clazz, String name) throws NoSuchFieldException {
Field declaredField = clazz.getDeclaredField(name);
declaredField.setAccessible(true);
return declaredField;
}
public static Method getDeclaredMethod(Class<?> clazz, String name, Class<?>... parameterTypes) throws NoSuchMethodException {
Method declaredMethod = clazz.getDeclaredMethod(name, parameterTypes);
declaredMethod.setAccessible(true);
return declaredMethod;
}
public static Field getDeclaredField(Object obj, String name) throws NoSuchFieldException {
Field declaredField = obj.getClass().getDeclaredField(name);
declaredField.setAccessible(true);
return declaredField;
}
public static Method getDeclaredMethod(Object obj, String name, Class<?>... parameterTypes) throws NoSuchMethodException {
Method declaredMethod = obj.getClass().getDeclaredMethod(name, parameterTypes);
declaredMethod.setAccessible(true);
return declaredMethod;
}
你可能還想問, 還有呢 還有最重要的 連接時(shí)總是返回 -133 -86 -192 這些怎么辦啊 怎么解決啊
我只想和你說, 別抱著希望了 你可能在網(wǎng)上看到很多解決辦法 但最終你使用了解決代碼, 仍然無法解決…
放棄吧, 換一種思路, 遇到這種錯(cuò)誤, 直接斷開+sleep+掃描+重連, , 若檢測(cè)到無數(shù)次返回這種錯(cuò)誤,沒一次連接成功的情況 記錄下次數(shù), 達(dá)到一定數(shù)量時(shí) 提示讓用戶關(guān)閉/開啟飛行模式 然后重試吧. 這種因素很多 有手機(jī)藍(lán)牙辣雞的,有代碼有問題的比如不掃描就連接, 有藍(lán)牙設(shè)備有問題的 各種因素都有.
七沃斤、調(diào)試
調(diào)試設(shè)備的工具 有 ios的lightblue,
安卓的推薦nrf芯片公司開發(fā)的 nrf connect 調(diào)試工具]
我也寫了兩個(gè)小應(yīng)用,有興趣可以下來看看
BLE調(diào)試器
https://www.coolapk.com/apk/com.toshiba.ble
BLE指令協(xié)議竊取工具(需要xposed), 可以竊取手機(jī)上的某app和其對(duì)應(yīng)的ble設(shè)備 正在進(jìn)行的數(shù)據(jù)通訊, 你可以理解為藍(lán)牙協(xié)議抓包 (僅供學(xué)習(xí)用途)
https://www.coolapk.com/apk/com.tos.bledetector
拓展閱讀
如果您有更好的建議歡迎評(píng)論分享圣蝎,如有錯(cuò)誤,請(qǐng)批評(píng)指正轰枝,謝謝捅彻。