【轉(zhuǎn)】Android BLE4.+ 藍(lán)牙開發(fā)國(guó)產(chǎn)手機(jī)兼容性解決方案

本文轉(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


拓展閱讀

android ble常見問題收集


如果您有更好的建議歡迎評(píng)論分享圣蝎,如有錯(cuò)誤,請(qǐng)批評(píng)指正轰枝,謝謝捅彻。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鞍陨,隨后出現(xiàn)的幾起案子步淹,更是在濱河造成了極大的恐慌,老刑警劉巖诚撵,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缭裆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡寿烟,警方通過查閱死者的電腦和手機(jī)澈驼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筛武,“玉大人缝其,你說我怎么就攤上這事挎塌。” “怎么了内边?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵榴都,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我漠其,道長(zhǎng)嘴高,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任和屎,我火速辦了婚禮拴驮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘柴信。我一直安慰自己套啤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布随常。 她就那樣靜靜地躺著纲岭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪线罕。 梳的紋絲不亂的頭發(fā)上止潮,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音钞楼,去河邊找鬼喇闸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛询件,可吹牛的內(nèi)容都是我干的燃乍。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼宛琅,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼刻蟹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嘿辟,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤舆瘪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后红伦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體英古,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年昙读,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了召调。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖唠叛,靈堂內(nèi)的尸體忽然破棺而出只嚣,到底是詐尸還是另有隱情,我是刑警寧澤艺沼,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布介牙,位于F島的核電站,受9級(jí)特大地震影響澳厢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜囚似,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一剩拢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧饶唤,春花似錦徐伐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至祸穷,卻和暖如春性穿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背雷滚。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工需曾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人祈远。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓呆万,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親车份。 傳聞我的和親對(duì)象是個(gè)殘疾皇子谋减,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348