Android 藍(lán)牙監(jiān)聽與掃描

基礎(chǔ)知識(shí)

藍(lán)牙操作主要有四項(xiàng)任務(wù):設(shè)置藍(lán)牙蜻牢、查找局部區(qū)域內(nèi)的配對(duì)設(shè)備或可用設(shè)備孩饼、連接設(shè)備镀娶,以及在設(shè)備間傳輸數(shù)據(jù)梯码。

藍(lán)牙的分類

傳統(tǒng)藍(lán)牙(Classic Bluetooth)
  • 電池使用強(qiáng)度大
  • 可用于數(shù)據(jù)量較大的傳輸儿奶,如語(yǔ)音闯捎,音樂(lè)瓤鼻,較高數(shù)據(jù)量傳輸?shù)?/li>
  • 廣泛用于音箱茬祷,耳機(jī),汽車電子及傳統(tǒng)數(shù)傳行業(yè)
低功耗藍(lán)牙(Bluetooth LE)
  • 功耗低
  • 不支持音頻協(xié)議沃粗,傳輸速率較低
  • 主要用于移動(dòng)互聯(lián)和健康醫(yī)療陪每,如鼠標(biāo)檩禾,鍵盤饵婆,遙控鼠標(biāo)(Air Mouse)侨核,傳感設(shè)備的數(shù)據(jù)發(fā)送搓译,如心跳帶些己,血壓計(jì),溫度傳感器逼庞,體重秤赛糟,健康手環(huán)等虑灰。
雙模藍(lán)牙
  • 同時(shí)支持傳統(tǒng)藍(lán)牙和低功耗藍(lán)牙模組

使用方法可以參考以下官方文檔:

傳統(tǒng)藍(lán)牙

低功耗藍(lán)牙

以及簡(jiǎn)書:

android藍(lán)牙BLE掃描實(shí)現(xiàn)方法

對(duì)于藍(lán)牙掃描的說(shuō)明

Android中兩種藍(lán)牙API的選擇

傳統(tǒng)藍(lán)牙的電池使用強(qiáng)度較大颤诀,Android 4.3(API 18)中引入了面向低功耗藍(lán)牙(BLe)的API支持崖叫。但這并不是說(shuō)屈暗,4.3以上的設(shè)備就一定搭載了低功耗藍(lán)牙养叛。反而是更多地搭載“經(jīng)典藍(lán)牙”或“雙模藍(lán)牙”弃甥,畢竟要傳輸音頻阔墩。

從測(cè)試結(jié)果來(lái)看啸箫,傳統(tǒng)藍(lán)牙API 可以同時(shí)掃描出 傳統(tǒng)藍(lán)牙低功耗藍(lán)牙 筐高,而低功耗藍(lán)牙API 則只能用于掃描 低功耗藍(lán)牙

所以稽屏,千萬(wàn)別以為4.3以上的設(shè)備就應(yīng)該用BLE API開發(fā)藍(lán)牙功能狐榔,除非你的業(yè)務(wù)需求是針對(duì)BLE設(shè)備的薄腻,如果你需要掃描車載藍(lán)牙或各種使用藍(lán)牙連接的外設(shè),那么建議使用傳統(tǒng)藍(lán)牙API尽纽,不然基本上掃不到設(shè)備弄贿。

功能講解

查找已配對(duì)設(shè)備列表

權(quán)限
  • BLUETOOTH(普通權(quán)限)
  • ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION(危險(xiǎn)權(quán)限)

注:對(duì)于定位權(quán)限的依賴區(qū)分系統(tǒng)版本期奔,較老版本(大概是6.0以前)中不需要定位權(quán)限能庆,新版本(大概是6.0至9.0)需要任意一個(gè)定位權(quán)限,而從Q開始渠旁,必須擁有精確定位權(quán)限(具體的版本界限需要測(cè)試得出)顾腊。

其他要求
  • 必須開啟藍(lán)牙

注:如果設(shè)備已開啟藍(lán)牙,可以靜默獲取數(shù)據(jù)吗垮,但若是要通過(guò)代碼開啟藍(lán)牙烁登,則需要 BLUETOOTH_ADMIN 權(quán)限,系統(tǒng)會(huì)在執(zhí)行 開啟藍(lán)牙 操作時(shí)狼牺,向用戶顯示一個(gè)彈框是钥,等待用戶授權(quán)。

可獲取的數(shù)據(jù)
來(lái)自BluetoothDevice對(duì)象的數(shù)據(jù)
  • 藍(lán)牙名稱
  • 藍(lán)牙硬件地址
  • 綁定狀態(tài)
  • 藍(lán)牙類型
  • uuids
  • 該藍(lán)牙所屬設(shè)備類型大分類(詳見【藍(lán)牙相關(guān)字段說(shuō)明】)
  • 該藍(lán)牙所屬設(shè)備類型小分類(詳見【藍(lán)牙相關(guān)字段說(shuō)明】)
獲取方法

藍(lán)牙開啟的情況下,同步獲取

核心代碼
    public static BluetoothAdapter getBAdapter(Context context) {
        BluetoothAdapter mBluetoothAdapter = null;
        try {
            if (Build.VERSION.SDK_INT >= 18) {
                BluetoothManager manager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
                mBluetoothAdapter = manager.getAdapter();

            } else {
                mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
            }
        } catch (Throwable t) {
            t.printStackTrace();
        }
        return mBluetoothAdapter;
    }
    
    /**
     * 查詢已配對(duì)的藍(lán)牙設(shè)備
     *
     * @param mBluetoothAdapter
     */
    public static ArrayList<HashMap<String, Object>> getBondedDevice(BluetoothAdapter mBluetoothAdapter) {
        ArrayList<HashMap<String, Object>> result = new ArrayList<>();
        try {
            if (DeviceUtils.getInstance(context).checkPermission("android.permission.BLUETOOTH")) {
                Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
                // If there are paired devices
                if (pairedDevices.size() > 0) {
                    for (BluetoothDevice device : pairedDevices) {
                        HashMap<String, Object> deviceInfo = parseBtDevice2Map(device);
                        deviceInfo.put("__currConnected", (isConnectedBtDevice(device) ? 1 : 0));
                        result.add(deviceInfo);
                    }
                }
            }
        } catch (Throwable t) {
            t.printStackTrace();
        }
        return result;
    }

        @SuppressLint("MissingPermission")
    private static HashMap<String, Object> parseDevice2Map(BluetoothDevice device) {
        HashMap<String, Object> map = new HashMap<>();
        if (device != null) {
            try {
                map.put("name", device.getName());
                map.put("address", device.getAddress());
                map.put("bondState", device.getBondState());

                BluetoothClass btClass = device.getBluetoothClass();
                int majorClass = btClass.getMajorDeviceClass();
                int deviceClass = btClass.getDeviceClass();
                map.put("majorClass", majorClass);
                map.put("deviceClass", deviceClass);

                if (Build.VERSION.SDK_INT >= 18) {
                    map.put("type", device.getType());
                }
                // 已配對(duì)的設(shè)備,同時(shí)獲取其uuids
                if (Build.VERSION.SDK_INT >= 15 && device.getBondState() == 12) {
                    ArrayList<String> uuids = new ArrayList<>();
                    ParcelUuid[] parcelUuids = device.getUuids();
                    if (parcelUuids != null && parcelUuids.length > 0) {
                        for (ParcelUuid parcelUuid : parcelUuids) {
                            if (parcelUuid != null && parcelUuid.getUuid() != null) {
                                uuids.add(parcelUuid.getUuid().toString());
                            }
                        }
                    }
                    map.put("uuids", uuids);
                }
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }
        return map;
    }

獲取當(dāng)前連接的設(shè)備

方法一

先獲取已匹配設(shè)備列表宋舷,再利用API返回的BluetoothDevice對(duì)象,反射調(diào)用其中的 isConnected 實(shí)例方法绎狭。

核心代碼:

    public static boolean isConnectedDevice(BluetoothDevice device) {
        boolean isConnected = false;
        if (device != null) {
            try {
                if (DeviceUtils.getInstance(context).checkPermission("android.permission.BLUETOOTH")) {
                                        // isConnected方法只能反射調(diào)用
                    Boolean result = ReflectUtils.invokeInstanceMethod(device, "isConnected");
                    if (result != null) {
                        isConnected = result.booleanValue();
                    }
                }
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }
        return isConnected;
    }
方法二

廣播監(jiān)聽,向系統(tǒng)注冊(cè) BluetoothDevice.ACTION_ACL_CONNECTED 廣播蹦狂,可以監(jiān)聽藍(lán)牙狀態(tài),每次有遠(yuǎn)程設(shè)備連接至本機(jī)時(shí)啼辣,會(huì)收到廣播鸥拧。不過(guò)該方式只能監(jiān)聽廣播注冊(cè)之后的藍(lán)牙連接富弦,注冊(cè)之前已經(jīng)連接的設(shè)備當(dāng)然獲取不到。

使用時(shí)需要注意:

  1. 用完后別忘了注銷廣播接收器盏缤。
  2. 廣播接收器的 onReceive() 方法是在S 主線程 觸發(fā)的,所以不要在其中處理耗時(shí)操作潭流,如果使用了callback返回藍(lán)牙操作的相關(guān)結(jié)果給外界灰嫉,那么在callback中同樣不能做耗時(shí)操作浑厚。

核心代碼:

/**
 * 注冊(cè)廣播接收器钳幅,用于接收藍(lán)牙相關(guān)操作的結(jié)果
 */
public static void registerBOperationReceiver() {
    if (btOperationReceiver == null) {
        try {
            btOperationReceiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    try {
                        String action = intent.getAction();
                        // 藍(lán)牙開關(guān)狀態(tài)變化
                        if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                            //獲取藍(lán)牙廣播中的藍(lán)牙新狀態(tài)
                            int blueNewState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
                            //獲取藍(lán)牙廣播中的藍(lán)牙舊狀態(tài)
                            int blueOldState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, 0);
                            switch (blueNewState) {
                                //正在打開藍(lán)牙
                                case BluetoothAdapter.STATE_TURNING_ON: {
                                    Toast.makeText(context, "STATE_TURNING_ON", Toast.LENGTH_SHORT).show();
                                    break;
                                }
                                //藍(lán)牙已打開
                                case BluetoothAdapter.STATE_ON: {
                                    Toast.makeText(context, "STATE_ON", Toast.LENGTH_SHORT).show();
                                    break;
                                }
                                //正在關(guān)閉藍(lán)牙
                                case BluetoothAdapter.STATE_TURNING_OFF: {
                                    Toast.makeText(context, "STATE_TURNING_OFF", Toast.LENGTH_SHORT).show();
                                    break;
                                }
                                //藍(lán)牙已關(guān)閉
                                case BluetoothAdapter.STATE_OFF: {
                                    Toast.makeText(context, "STATE_OFF", Toast.LENGTH_SHORT).show();
                                    break;
                                }
                            }
                        }
                        /*
                         * 本機(jī)的藍(lán)牙連接狀態(tài)發(fā)生變化
                         *
                         * 特指“無(wú)任何連接”→“連接任意遠(yuǎn)程設(shè)備”厂榛,以及“連接任一或多個(gè)遠(yuǎn)程設(shè)備”→“無(wú)任何連接”的狀態(tài)變化击奶,
                         * 即“連接第一個(gè)遠(yuǎn)程設(shè)備”與“斷開最后一個(gè)遠(yuǎn)程設(shè)備”時(shí)才會(huì)觸發(fā)該Action
                         */
                        else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
                            //獲取藍(lán)牙廣播中的藍(lán)牙連接新狀態(tài)
                            int newConnState = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, 0);
                            //獲取藍(lán)牙廣播中的藍(lán)牙連接舊狀態(tài)
                            int oldConnState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_CONNECTION_STATE, 0);
                            // 當(dāng)前遠(yuǎn)程藍(lán)牙設(shè)備
                            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                            HashMap<String, Object> map = parseBtDevice2Map(device);
                            switch (newConnState) {
                                //藍(lán)牙連接中
                                case BluetoothAdapter.STATE_CONNECTING: {
                                    Log.d(TAG, "STATE_CONNECTING, " + map.get("name"));
                                    Toast.makeText(context, "STATE_CONNECTING", Toast.LENGTH_SHORT).show();
                                    break;
                                }
                                //藍(lán)牙已連接
                                case BluetoothAdapter.STATE_CONNECTED: {
                                    Log.d(TAG, "STATE_CONNECTED, " + map.get("name"));
                                    Toast.makeText(context, "STATE_CONNECTED", Toast.LENGTH_SHORT).show();
                                    break;
                                }
                                //藍(lán)牙斷開連接中
                                case BluetoothAdapter.STATE_DISCONNECTING: {
                                    Log.d(TAG, "STATE_DISCONNECTING, " + map.get("name"));
                                    Toast.makeText(context, "STATE_DISCONNECTING", Toast.LENGTH_SHORT).show();
                                    break;
                                }
                                //藍(lán)牙已斷開連接
                                case BluetoothAdapter.STATE_DISCONNECTED: {
                                    Log.d(TAG, "STATE_DISCONNECTED, " + map.get("name"));
                                    Toast.makeText(context, "STATE_DISCONNECTED", Toast.LENGTH_SHORT).show();
                                    break;
                                }
                            }
                        }
                        // 有遠(yuǎn)程設(shè)備成功連接至本機(jī)
                        else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
                            // 當(dāng)前遠(yuǎn)程藍(lán)牙設(shè)備
                            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                            HashMap<String, Object> map = parseBtDevice2Map(device);
                            Log.d(TAG, "ACTION_ACL_CONNECTED, " + map.get("name"));
                            Toast.makeText(context, "ACTION_ACL_CONNECTED", Toast.LENGTH_SHORT).show();
                        }
                        // 有遠(yuǎn)程設(shè)備斷開連接
                        else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
                            // 當(dāng)前遠(yuǎn)程藍(lán)牙設(shè)備
                            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                            HashMap<String, Object> map = parseBtDevice2Map(device);
                            Log.d(TAG, "ACTION_ACL_DISCONNECTED, " + map.get("name"));
                            Toast.makeText(context, "ACTION_ACL_DISCONNECTED", Toast.LENGTH_SHORT).show();
                        }
                    } catch (Throwable t) {
                        t.printStacktrace();
                    }
                }
            };
        } catch (Throwable t) {
            t.printStacktrace();
        }
        IntentFilter filter = new IntentFilter();
        // 藍(lán)牙開關(guān)狀態(tài)
        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
        // 本機(jī)的藍(lán)牙連接狀態(tài)發(fā)生變化(連接第一個(gè)遠(yuǎn)程設(shè)備與斷開最后一個(gè)遠(yuǎn)程設(shè)備才觸發(fā))
        filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
        // 有遠(yuǎn)程設(shè)備成功連接至本機(jī)(每個(gè)遠(yuǎn)程設(shè)備都會(huì)觸發(fā))
        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
        // 有遠(yuǎn)程設(shè)備斷開連接(每個(gè)遠(yuǎn)程設(shè)備都會(huì)觸發(fā))
        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
        try {
            context.registerReceiver(btOperationReceiver, filter);
        } catch (Throwable t) {}
    }
}

藍(lán)牙狀態(tài)廣播可根據(jù)業(yè)務(wù)需求痰驱,選擇使用以下ACTION:

  • BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED
    • 本機(jī)的藍(lán)牙連接狀態(tài)發(fā)生變化時(shí)觸發(fā)废士。特指“無(wú)任何連接”→“連接任意遠(yuǎn)程設(shè)備”官硝,以及“連接任一或多個(gè)遠(yuǎn)程設(shè)備”→“無(wú)任何連接”的狀態(tài)變化,即“連接第一個(gè)遠(yuǎn)程設(shè)備”與“斷開最后一個(gè)遠(yuǎn)程設(shè)備”時(shí)才會(huì)觸發(fā)該Action岖研。
  • BluetoothDevice.ACTION_ACL_CONNECTED / BluetoothDevice.ACTION_ACL_DISCONNECTED
    • 每個(gè)遠(yuǎn)程設(shè)備的連接與斷開都會(huì)觸發(fā)

發(fā)現(xiàn)設(shè)備

權(quán)限
  • BLUETOOTH(普通權(quán)限)
  • BLUETOOTH_ADMIN(用于掃描藍(lán)牙硬纤,不需動(dòng)態(tài)申請(qǐng),不會(huì)彈框邻辉,除非執(zhí)行“開啟藍(lán)牙”動(dòng)作)
  • ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION(危險(xiǎn)權(quán)限)

注:對(duì)于定位權(quán)限的依賴區(qū)分系統(tǒng)版本,較老版本(大概是6.0以前)中不需要定位權(quán)限吱瘩,新版本(大概是6.0至9.0)需要任意一個(gè)定位權(quán)限使碾,而從Q開始,必須擁有精確定位權(quán)限(具體的版本界限需要測(cè)試得出)矢门。

其他要求
  • 必須開啟藍(lán)牙
  • 7.0 后不能在30秒內(nèi)掃描和停止超過(guò)5次
    • 否則掃描不到結(jié)果祟剔,并收到 onScanFailed(int),返回錯(cuò)誤碼:ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED,表示app無(wú)法注冊(cè)物延,無(wú)法開始掃描)教届。
可獲取的數(shù)據(jù)
來(lái)自BluetoothDevice對(duì)象的數(shù)據(jù)
  • 藍(lán)牙名稱(傳統(tǒng)設(shè)備、混合模式設(shè)備能拿到强霎,低功耗設(shè)備基本拿不到)
  • 藍(lán)牙硬件地址
  • 綁定狀態(tài)
  • 藍(lán)牙類型
  • uuids(通常拿不到蓉冈,只有已配對(duì)的設(shè)備才能拿到)
  • 該藍(lán)牙所屬設(shè)備類型大分類(詳見【藍(lán)牙相關(guān)字段說(shuō)明】)
  • 該藍(lán)牙所屬設(shè)備類型小分類(詳見【藍(lán)牙相關(guān)字段說(shuō)明】)
藍(lán)牙發(fā)現(xiàn)功能特有的數(shù)據(jù)
  • rssi:可理解成設(shè)備的信號(hào)值城舞。該數(shù)值是一個(gè)負(fù)數(shù)轩触,越大則信號(hào)越強(qiáng)(傳統(tǒng)藍(lán)牙API與BLE API均可獲取)
  • scanRecord:遠(yuǎn)程設(shè)備提供的廣播數(shù)據(jù)的內(nèi)容脱柱,是一個(gè)二進(jìn)制數(shù)組(BLE API特有)
獲取方法
  • 異步獲取
  • 掃描時(shí)長(zhǎng)需要主動(dòng)控制(不建議一次掃描太久,耗電)
  • 注意:已匹配的設(shè)備不會(huì)出現(xiàn)在“發(fā)現(xiàn)列表”中(測(cè)試發(fā)現(xiàn)匹配的iphone會(huì)出現(xiàn)在發(fā)現(xiàn)列表)
說(shuō)明
  • 傳統(tǒng)藍(lán)牙API的掃描時(shí)長(zhǎng)拉馋,系統(tǒng)默認(rèn)是12秒左右榨为,但可以主動(dòng)停止,可以根據(jù)業(yè)務(wù)需求煌茴,設(shè)置一個(gè)掃描超時(shí)時(shí)間
傳統(tǒng)藍(lán)牙API實(shí)現(xiàn)掃描的核心代碼
    /**
     * 查找藍(lán)牙随闺,包括傳統(tǒng)藍(lán)牙和低功耗藍(lán)牙
     *
     * 注:
     * 1.該方式在查找低功耗藍(lán)牙上效率較低
     * 2.若只需要查找低功耗藍(lán)牙,應(yīng)該使用“低功耗藍(lán)牙API”蔓腐,即 findBluetoothLE() 方法
     *
     * @param scanInterval 掃描時(shí)長(zhǎng)矩乐,單位:秒
     * @param bluetoothAdapter
     * @param btScanCallback 掃描結(jié)果回調(diào)
     */
    public static void findBluetoothLEAndClassic(int scanInterval, final BluetoothAdapter bluetoothAdapter, final BtScanCallback btScanCallback) {
        try {
            if (DeviceUtils.getInstance(context).checkPermission("android.permission.BLUETOOTH")
            && DeviceUtils.getInstance(context).checkPermission("android.permission.BLUETOOTH_ADMIN")) {
                if (!bluetoothAdapter.isEnabled()) {
                    // 若藍(lán)牙未打開,直接返回
                    btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
                    return;
                }
                if (mScanning) {
                    // 正在掃描中回论,直接返回
                    btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
                    return;
                }
                // 默認(rèn)掃描6秒散罕,若scanInterval不合法,則使用默認(rèn)值
                final int defaultInterval = 6;
                if (scanInterval <= 0) {
                    scanInterval = defaultInterval;
                }

                // 通過(guò)bluetoothAdapter.startDiscovery()實(shí)現(xiàn)的掃描透葛,系統(tǒng)會(huì)在掃描結(jié)束(通常是12秒)后自動(dòng)停止笨使,
                // 而cancelDiscovery()可以提前終止掃描。 所以這里的控制邏輯僚害,相當(dāng)于設(shè)置一個(gè)最大時(shí)間硫椰,限制掃描不得超出這個(gè)時(shí)間,
                // 但是很可能提前完成掃描(比如scanInterval > 12秒)
                // 設(shè)置一段時(shí)間后停止掃描(以防系統(tǒng)未正常停止掃描)
                final Handler handler = HandlerThread.newHandler(new Handler.Callback() {
                    @Override
                    public boolean handleMessage(Message msg) {
                        Log.d(TAG, "Cancel bluetooth scan");
                        // 若已經(jīng)停止掃描(系統(tǒng)掃描結(jié)束/通過(guò)cancelDiscovery取消掃描)萨蚕,則再次調(diào)用該方法不會(huì)觸發(fā)ACTION_DISCOVERY_FINISHED
                        bluetoothAdapter.cancelDiscovery();
                        return false;
                    }
                });
                handler.sendEmptyMessageDelayed(0, scanInterval * 1000);

                // 準(zhǔn)備開始掃描
                final ArrayList<HashMap<String, Object>> scanResult = new ArrayList<>();
                btScanReceiver = new BroadcastReceiver() {
                    public void onReceive(Context context, Intent intent) {
                        String action = intent.getAction();

                        if (action.equals(BluetoothDevice.ACTION_FOUND)) { //found device
                            BluetoothDevice device = intent
                                    .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                            HashMap<String, Object> map = parseBtDevice2Map(device);
                            // 該extra取值與BluetoothDevice對(duì)象中g(shù)etName()取值一致靶草,因此不需要通過(guò)它獲取name
//                          String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
                            short defaultValue = 0;
                            short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, defaultValue);
                            map.put("rssi", rssi);
                            scanResult.add(map);
                            Log.d(TAG, "onScanResult: " + device.getAddress() + ", " + device.getName());
                        } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED)) {
                            Log.d(TAG, "正在掃描");
                        } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
                            Log.d(TAG, "掃描完成");
                            mScanning = false;
                            btScanCallback.onScan(scanResult);
                            // 若系統(tǒng)先掃描完,不需要再通過(guò)代碼主動(dòng)停止掃描
                            handler.removeMessages(0);
                            // 注銷接收器
                            unRegisterBtScanReceiver();
                        }
                    }
                };
                IntentFilter filter = new IntentFilter();
                // 用BroadcastReceiver來(lái)取得搜索結(jié)果
                filter.addAction(BluetoothDevice.ACTION_FOUND);
                filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
                // 兩種情況會(huì)觸發(fā)ACTION_DISCOVERY_FINISHED:1.系統(tǒng)結(jié)束掃描(約12秒)岳遥;2.調(diào)用cancelDiscovery()方法主動(dòng)結(jié)束掃描
                filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
                context.registerReceiver(btScanReceiver, filter);

                // 開始掃描
                mScanning = true;
                bluetoothAdapter.startDiscovery();
            } else {
                // 缺少權(quán)限奕翔,直接返回
                btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
            }
        } catch (Throwable t) {
            t.printStackTrace();
            btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
        }
    }

    public static void unRegisterBtScanReceiver() {
        if (btScanReceiver != null) {
            context.unregisterReceiver(btScanReceiver);
            btScanReceiver = null;
        }
    }
    
    public interface BtScanCallback {
        void onScan(ArrayList<HashMap<String, Object>> result);
    }
BLE API實(shí)現(xiàn)掃描的核心代碼
    /**
     * 查找低功耗藍(lán)牙,該方法在4.3(API 18)以上浩蓉,無(wú)法查找“傳統(tǒng)藍(lán)牙”
     *
     * @param scanInterval 掃描時(shí)長(zhǎng)派继,單位:秒
     * @param bluetoothAdapter
     * @param btScanCallback 掃描結(jié)果回調(diào)
     */
    public static void findBluetoothLE(int scanInterval, final BluetoothAdapter bluetoothAdapter, final BtScanCallback btScanCallback) {
        try {
            if (DeviceUtils.getInstance(context).checkPermission("android.permission.BLUETOOTH")
                    && DeviceUtils.getInstance(context).checkPermission("android.permission.BLUETOOTH_ADMIN")) {
                if (!bluetoothAdapter.isEnabled()) {
                    // 若藍(lán)牙未打開,直接返回
                    btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
                    return;
                }
                if (mScanning) {
                    // 正在掃描中捻艳,直接返回
                    btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
                    return;
                }
                // 默認(rèn)掃描6秒驾窟,若scanInterval不合法,則使用默認(rèn)值
                final int defaultInterval = 6;
                if (scanInterval <= 0) {
                    scanInterval = defaultInterval;
                }
                // 4.3的低功耗藍(lán)牙API
                if (Build.VERSION.SDK_INT >= 18) {
                    final ArrayList<HashMap<String, Object>> scanResult = new ArrayList<>();
                    // 5.0又引入了新的藍(lán)牙API(4.3版本的API仍然可用)
                    if (Build.VERSION.SDK_INT < 21) {
                        // 定義掃描結(jié)果回調(diào)
                        final BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
                            /**
                             *
                             * @param device 掃描到的設(shè)備實(shí)例认轨,可從實(shí)例中獲取到相應(yīng)的信息绅络。如:名稱,mac地址
                             * @param rssi 可理解成設(shè)備的信號(hào)值。該數(shù)值是一個(gè)負(fù)數(shù)恩急,越大則信號(hào)越強(qiáng)
                             * @param scanRecord 遠(yuǎn)程設(shè)備提供的廣播數(shù)據(jù)的內(nèi)容
                             */
                            @Override
                            public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
                                HashMap<String, Object> map = parseBtDevice2Map(device);
                                map.put("rssi", rssi);
//                      map.put("scanRecord", Data.byteToHex(scanRecord));
                                scanResult.add(map);
                            }
                        };

                        // 開始掃描
                        mScanning = true;
                        bluetoothAdapter.startLeScan(leScanCallback);

                        // 設(shè)置一段時(shí)間后停止掃描
                        Handler handler = HandlerThread.newHandler(new Handler.Callback() {
                            @Override
                            public boolean handleMessage(Message msg) {
                                mScanning = false;
                                bluetoothAdapter.stopLeScan(leScanCallback);
                                btScanCallback.onScan(scanResult);
                                return false;
                            }
                        });
                        handler.sendEmptyMessageDelayed(0, scanInterval * 1000);
                    } else {
                        // 定義掃描結(jié)果回調(diào)
                        final ScanCallback mScanCallback = new ScanCallback() {
                            //當(dāng)一個(gè)藍(lán)牙ble廣播被發(fā)現(xiàn)時(shí)回調(diào)
                            @Override
                            public void onScanResult(int callbackType, ScanResult result) {
                                Log.d(TAG, "onScanResult: " + result.getDevice().getAddress() + ", " + result.getDevice().getName());
                                super.onScanResult(callbackType, result);
                                //掃描類型有開始掃描時(shí)傳入的ScanSettings相關(guān)
                                //對(duì)掃描到的設(shè)備進(jìn)行操作杉畜。如:獲取設(shè)備信息。
                                if (result != null) {
                                    HashMap<String, Object> map = new HashMap<>();
                                    BluetoothDevice device = result.getDevice();
                                    if (device != null) {
                                        map = parseBtDevice2Map(device);
                                    }
                                    map.put("rssi", result.getRssi());
                                    ScanRecord scanRecord = result.getScanRecord();
                                    scanResult.add(map);
                                }
                            }

                            // 批量返回掃描結(jié)果衷恭。一般藍(lán)牙設(shè)備對(duì)象都是通過(guò)onScanResult(int,ScanResult)返回此叠,
                            // 而不會(huì)在onBatchScanResults(List)方法中返回,除非手機(jī)支持批量掃描模式并且開啟了批量掃描模式匾荆。
                            // 批處理的開啟請(qǐng)查看ScanSettings拌蜘。
                            //@param results 以前掃描到的掃描結(jié)果列表杆烁。
                            @Override
                            public void onBatchScanResults(List<ScanResult> results) {
                                super.onBatchScanResults(results);
                                Log.d(TAG, "onBatchScanResults");

                            }

                            //當(dāng)掃描不能開啟時(shí)回調(diào)
                            @Override
                            public void onScanFailed(int errorCode) {
                                super.onScanFailed(errorCode);
                                //掃描太頻繁會(huì)返回ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED牙丽,表示app無(wú)法注冊(cè),無(wú)法開始掃描兔魂。
                                Log.d(TAG, "onScanFailed. errorCode: " + errorCode);
                            }
                        };
                        //開始掃描
                        final BluetoothLeScanner mBLEScanner = bluetoothAdapter.getBluetoothLeScanner();
                        mScanning = true;
/** 也可指定過(guò)濾條件和掃描配置
                         //創(chuàng)建ScanSettings的build對(duì)象用于設(shè)置參數(shù)
                         ScanSettings.Builder builder = new ScanSettings.Builder()
                         //設(shè)置高功耗模式
                         .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
                         //android 6.0添加設(shè)置回調(diào)類型烤芦、匹配模式等
                         if(android.os.Build.VERSION.SDK_INT >= 23) {
                         //定義回調(diào)類型
                         builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES);
                         //設(shè)置藍(lán)牙LE掃描濾波器硬件匹配的匹配模式
                         builder.setMatchMode(ScanSettings.MATCH_MODE_STICKY);
                         }
                         // 若設(shè)備支持批處理掃描,可以選擇使用批處理析校,但此時(shí)掃描結(jié)果僅觸發(fā)onBatchScanResults()
                         //             if (bluetoothAdapter.isOffloadedScanBatchingSupported()) {
                         //                 //設(shè)置藍(lán)牙LE掃描的報(bào)告延遲的時(shí)間(以毫秒為單位)
                         //                 //設(shè)置為0以立即通知結(jié)果
                         //                 builder.setReportDelay(0L);
                         //             }
                         ScanSettings scanSettings = builder.build();
                         //可設(shè)置過(guò)濾條件构罗,在第一個(gè)參數(shù)傳入,但一般不設(shè)置過(guò)濾智玻。
                         mBLEScanner.startScan(null, scanSettings, mScanCallback);
 */
                        mBLEScanner.startScan(mScanCallback);
                        // 設(shè)置一段時(shí)間后停止掃描
                        Handler handler = HandlerThread.newHandler(new Handler.Callback() {
                            @Override
                            public boolean handleMessage(Message msg) {
                                mScanning = false;
                                mBLEScanner.stopScan((mScanCallback));
                                btScanCallback.onScan(scanResult);
                                return false;
                            }
                        });
                        handler.sendEmptyMessageDelayed(0, scanInterval * 1000);
                    }
                } else {
                    findBluetoothLEAndClassic(scanInterval, bluetoothAdapter, btScanCallback);
                }
            } else {
                // 缺少權(quán)限遂唧,直接返回
                btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
            }
        } catch (Throwable t) {
            t.printStackTrace();
            btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
        }
    }
Demo驗(yàn)證獲取結(jié)果說(shuō)明
  • 按照官方的集成指導(dǎo),4.3以上設(shè)備使用了BLE API進(jìn)行掃描吊奢,能夠掃描到很多藍(lán)牙盖彭,但是卻并不能發(fā)現(xiàn)測(cè)試目標(biāo)藍(lán)牙,包括測(cè)試車載藍(lán)牙的掃描也一樣页滚,另外召边,此方案下幾乎獲取不到藍(lán)牙名稱。
  • 反而使用傳統(tǒng)藍(lán)牙API裹驰,雖然掃描到的結(jié)果比低功耗藍(lán)牙API少隧熙,但是能掃描到測(cè)試目標(biāo)藍(lán)牙,而且很多藍(lán)牙名稱是可以獲取到的幻林。

對(duì)于以上結(jié)果贞盯,懷疑的問(wèn)題點(diǎn):

  • 官方的低功耗藍(lán)牙中提到過(guò):你只能要么掃低功耗藍(lán)牙(type=2),要么掃傳統(tǒng)藍(lán)牙(type=1)沪饺,不能同時(shí)掃躏敢。(Note: You can only scan for Bluetooth LE devices or scan for Classic Bluetooth devices, as described in Bluetooth. You cannot scan for both Bluetooth LE and classic devices at the same time.)
  • 低功耗藍(lán)牙API是不是只能掃低功耗藍(lán)牙,不能掃傳統(tǒng)藍(lán)牙随闽?
    • 測(cè)試用的兩個(gè)設(shè)備正好都是傳統(tǒng)藍(lán)牙父丰,
    • 使用低功耗藍(lán)牙API,掃描列表中掃出的藍(lán)牙只有“低功耗”和“未知”
    • 使用傳統(tǒng)藍(lán)牙API,掃描列表中同時(shí)有“低功耗”蛾扇、“傳統(tǒng)”和“混合模式”攘烛,另外“傳統(tǒng)”一般都能拿到“藍(lán)牙名稱”,“低功耗”幾乎拿不到镀首。
    • 對(duì)于藍(lán)牙掃描的說(shuō)明 中坟漱,也確實(shí)提到了:低功耗藍(lán)牙API只能掃描低功耗藍(lán)牙,而傳統(tǒng)藍(lán)牙API更哄,在大部分機(jī)型上芋齿,可以掃描“低功耗”和“傳統(tǒng)”。局限在于成翩,掃“低功耗”的效率低觅捆,不能返回“設(shè)備廣播(ScanRecord)”。

藍(lán)牙相關(guān)字段說(shuō)明

綁定狀態(tài)(bondState)
  • 10:未綁定麻敌,表示遠(yuǎn)程設(shè)備未綁定栅炒,沒(méi)有共享鏈接密鑰,因此通信(如果允許的話)將是未經(jīng)身份驗(yàn)證和未加密的术羔。
  • 11:綁定中赢赊,表示正在與遠(yuǎn)程設(shè)備進(jìn)行綁定
  • 12:已綁定,表示遠(yuǎn)程設(shè)備已綁定级历,遠(yuǎn)程設(shè)備本地存儲(chǔ)共享連接的密鑰释移,因此可以對(duì)通信進(jìn)行身份驗(yàn)證和加密。
藍(lán)牙類型(type)(API 18開始)
  • 0:Unknown
  • 1:傳統(tǒng)藍(lán)牙(Classic - BR/EDR devices)
  • 2:低功耗藍(lán)牙(Low Energy - LE-only)
  • 3:混合模式(Dual Mode - BR/EDR/LE)
遠(yuǎn)程設(shè)備支持的功能(uuids)(API 15開始)
  • the supported features (UUIDs) of the remote device
該藍(lán)牙所屬設(shè)備類型大分類(majorClass)
  • This value can be compared with the public constants in BluetoothClass.Device.Major to determine which major class is encoded in this Bluetooth class.
  • int型寥殖,取值如下:
    • 0:MISC
    • 256:COMPUTER
    • 512:PHONE
    • 768:NETWORKING
    • 1024:AUDIO_VIDEO
    • 1280:PERIPHERAL(外圍設(shè)備)
    • 1536:IMAGING
    • 1792:WEARABLE
    • 2048:TOY
    • 2304:HEALTH
    • 7936:BITMASK / UNCATEGORIZED
該藍(lán)牙所屬設(shè)備類型小分類(deviceClass)
  • This value can be compared with the public constants in BluetoothClass.Device to determine which device class is encoded in this Bluetooth class.
  • int型玩讳,取值如下:
    • 8188:BITMASK
    • 256:COMPUTER_UNCATEGORIZED
    • 260:COMPUTER_DESKTOP
    • 264:COMPUTER_SERVER
    • 268:COMPUTER_LAPTOP
    • 272:COMPUTER_HANDHELD_PC_PDA
    • 276:COMPUTER_PALM_SIZE_PC_PDA
    • 280:COMPUTER_WEARABLE
    • 512:PHONE_UNCATEGORIZED
    • 516:PHONE_CELLULAR
    • 520:PHONE_CORDLESS
    • 524:PHONE_SMART
    • 528:PHONE_MODEM_OR_GATEWAY
    • 532:PHONE_ISDN
    • 1024:AUDIO_VIDEO_UNCATEGORIZED
    • 1028:AUDIO_VIDEO_WEARABLE_HEADSET
    • 1032:AUDIO_VIDEO_HANDSFREE
    • 1040:AUDIO_VIDEO_MICROPHONE
    • 1044:AUDIO_VIDEO_LOUDSPEAKER
    • 1048:AUDIO_VIDEO_HEADPHONES
    • 1052:AUDIO_VIDEO_PORTABLE_AUDIO
    • 1056:AUDIO_VIDEO_CAR_AUDIO
    • 1060:AUDIO_VIDEO_SET_TOP_BOX
    • 1064:AUDIO_VIDEO_HIFI_AUDIO
    • 1068:AUDIO_VIDEO_VCR
    • 1072:AUDIO_VIDEO_VIDEO_CAMERA
    • 1076:AUDIO_VIDEO_CAMCORDER
    • 1080:AUDIO_VIDEO_VIDEO_MONITOR
    • 1084:AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER
    • 1088:AUDIO_VIDEO_VIDEO_CONFERENCING
    • 1096:AUDIO_VIDEO_VIDEO_GAMING_TOY
    • 1792:WEARABLE_UNCATEGORIZED
    • 1796:WEARABLE_WRIST_WATCH
    • 1800:WEARABLE_PAGER
    • 1804:WEARABLE_JACKET
    • 1808:WEARABLE_HELMET
    • 1812:WEARABLE_GLASSES
    • 2048:TOY_UNCATEGORIZED
    • 2052:TOY_ROBOT
    • 2056:TOY_VEHICLE
    • 2060:TOY_DOLL_ACTION_FIGURE
    • 2064:TOY_CONTROLLER
    • 2068:TOY_GAME
    • 2304:HEALTH_UNCATEGORIZED
    • 2308:HEALTH_BLOOD_PRESSURE
    • 2312:HEALTH_THERMOMETER
    • 2316:HEALTH_WEIGHING
    • 2320:HEALTH_GLUCOSE
    • 2324:HEALTH_PULSE_OXIMETER
    • 2328:HEALTH_PULSE_RATE
    • 2332:HEALTH_DATA_DISPLAY
    • 1280:PERIPHERAL_NON_KEYBOARD_NON_POINTING(系統(tǒng)隱藏)
    • 1344:PERIPHERAL_KEYBOARD(系統(tǒng)隱藏)
    • 1408:PERIPHERAL_POINTING(系統(tǒng)隱藏)
    • 1472:PERIPHERAL_KEYBOARD_POINTING(系統(tǒng)隱藏)

實(shí)現(xiàn)一個(gè)藍(lán)牙工具類

最后附上一個(gè)完整的藍(lán)牙操作工具類:

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.ParcelUuid;
import android.util.Log;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class BHelper {
    private static final String TAG = "BHelper";
    private static BHelper instance;
    private Context context;
    private boolean mScanning = false;
    // 藍(lán)牙開關(guān)/連接接收器
    private BroadcastReceiver bOperationReceiver;
    private boolean bOperationRegistered = false;
    // 藍(lán)牙掃描接收器
    private BroadcastReceiver bScanReceiver;
    private boolean bScanRegistered = false;
    private Map<String, BOperationCallback> bOperationCallbackMap;

    private BHelper(Context context) {
        this.context = context.getApplicationContext();
    }
    
    public static BHelper getInstance(Context context) {
        if (instance == null) {
            synchronized (BHelper.class) {
                if (instance == null) {
                    instance = new BHelper(context);
                }
            }
        }
        return instance;
    }
    
    // 根據(jù)id注銷指定的監(jiān)聽器
    public void unRegisterBOperationReceiver(String id) {
        try {
            if (bOperationCallbackMap != null && !bOperationCallbackMap.containsKey(id)) {
                bOperationCallbackMap.remove(id);
            }
            // 當(dāng)前沒(méi)有任何運(yùn)行中的監(jiān)聽器時(shí),才需要注銷廣播接收器
            if (bOperationCallbackMap.isEmpty()) {
                if (bOperationReceiver != null && bOperationRegistered) {
                    context.unregisterReceiver(bOperationReceiver);
                    bOperationRegistered = false;
                    bOperationReceiver = null;
                }
            }
        } catch (Throwable t) {
            Log.d(TAG, t.getMessage() + "", t);
        }
    }

    /**
     * 注冊(cè)廣播接收器扛禽,用于接收藍(lán)牙相關(guān)操作的結(jié)果
     * 參數(shù)中增加id锋边,目的是支持同時(shí)注冊(cè)多個(gè)監(jiān)聽器,否則后注冊(cè)的監(jiān)聽器會(huì)覆蓋前面的監(jiān)聽器编曼,導(dǎo)致同一時(shí)間只能有一個(gè)地方使用藍(lán)牙工具類
     */
    public void registerBOperationReceiver(String id, final BOperationCallback bOperationCallback) {
        if (bOperationCallback != null) {
            if (bOperationCallbackMap == null) {
                bOperationCallbackMap = new HashMap<String, BOperationCallback>();
            }
            bOperationCallbackMap.put(id, bOperationCallback);

            if (bOperationReceiver == null) {
                try {
                    bOperationReceiver = new BroadcastReceiver() {
                        @Override
                        public void onReceive(Context context, Intent intent) {
                            try {
                                String action = intent.getAction();
                                // 藍(lán)牙開關(guān)狀態(tài)變化
                                if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                                    //獲取藍(lán)牙廣播中的藍(lán)牙新狀態(tài)
                                    int bNewState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
                                    //獲取藍(lán)牙廣播中的藍(lán)牙舊狀態(tài)
                                    int bOldState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, 0);
                                    switch (bNewState) {
                                        //正在打開藍(lán)牙
                                        case BluetoothAdapter.STATE_TURNING_ON: {
                                            // no need to monitor this action
                                            break;
                                        }
                                        //藍(lán)牙已打開
                                        case BluetoothAdapter.STATE_ON: {
                                            if (bOperationCallbackMap != null && !bOperationCallbackMap.isEmpty()) {
                                                for (Map.Entry<String, BOperationCallback> entry : bOperationCallbackMap.entrySet()) {
                                                    BOperationCallback callback = entry.getValue();
                                                    if (callback != null) {
                                                        callback.onEnabled();
                                                    }
                                                }
                                            }
                                            break;
                                        }
                                        //正在關(guān)閉藍(lán)牙
                                        case BluetoothAdapter.STATE_TURNING_OFF: {
                                            // no need to monitor this action
                                            break;
                                        }
                                        //藍(lán)牙已關(guān)閉
                                        case BluetoothAdapter.STATE_OFF: {
                                            if (bOperationCallbackMap != null && !bOperationCallbackMap.isEmpty()) {
                                                for (Map.Entry<String, BOperationCallback> entry : bOperationCallbackMap.entrySet()) {
                                                    BOperationCallback callback = entry.getValue();
                                                    if (callback != null) {
                                                        callback.onDisabled();
                                                    }
                                                }
                                            }
                                            break;
                                        }
                                    }
                                }
                                /*
                                 * 本機(jī)的藍(lán)牙連接狀態(tài)發(fā)生變化
                                 *
                                 * 特指“無(wú)任何連接”→“連接任意遠(yuǎn)程設(shè)備”豆巨,以及“連接任一或多個(gè)遠(yuǎn)程設(shè)備”→“無(wú)任何連接”的狀態(tài)變化,
                                 * 即“連接第一個(gè)遠(yuǎn)程設(shè)備”與“斷開最后一個(gè)遠(yuǎn)程設(shè)備”時(shí)才會(huì)觸發(fā)該Action
                                 */
                                else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
                                    //獲取藍(lán)牙廣播中的藍(lán)牙連接新狀態(tài)
                                    int newConnState = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, 0);
                                    //獲取藍(lán)牙廣播中的藍(lán)牙連接舊狀態(tài)
                                    int oldConnState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_CONNECTION_STATE, 0);
                                    // 當(dāng)前遠(yuǎn)程藍(lán)牙設(shè)備
                                    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                                    HashMap<String, Object> map = parseDevice2Map(device);
                                    switch (newConnState) {
                                        //藍(lán)牙連接中
                                        case BluetoothAdapter.STATE_CONNECTING: {
                                            // no need to monitor this action
                                            break;
                                        }
                                        //藍(lán)牙已連接
                                        case BluetoothAdapter.STATE_CONNECTED: {
                                            if (bOperationCallbackMap != null && !bOperationCallbackMap.isEmpty()) {
                                                for (Map.Entry<String, BOperationCallback> entry : bOperationCallbackMap.entrySet()) {
                                                    BOperationCallback callback = entry.getValue();
                                                    if (callback != null) {
                                                        callback.onConnectionChanged(true, map);
                                                    }
                                                }
                                            }
                                            break;
                                        }
                                        //藍(lán)牙斷開連接中
                                        case BluetoothAdapter.STATE_DISCONNECTING: {
                                            // no need to monitor this action
                                            break;
                                        }
                                        //藍(lán)牙已斷開連接
                                        case BluetoothAdapter.STATE_DISCONNECTED: {
                                            if (bOperationCallbackMap != null && !bOperationCallbackMap.isEmpty()) {
                                                for (Map.Entry<String, BOperationCallback> entry : bOperationCallbackMap.entrySet()) {
                                                    BOperationCallback callback = entry.getValue();
                                                    if (callback != null) {
                                                        callback.onConnectionChanged(false, map);
                                                    }
                                                }
                                            }
                                            break;
                                        }
                                    }
                                }
                                // 有遠(yuǎn)程設(shè)備成功連接至本機(jī)
                                else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
                                    // 當(dāng)前遠(yuǎn)程藍(lán)牙設(shè)備
                                    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                                    HashMap<String, Object> map = parseDevice2Map(device);
                                    if (bOperationCallbackMap != null && !bOperationCallbackMap.isEmpty()) {
                                        for (Map.Entry<String, BOperationCallback> entry : bOperationCallbackMap.entrySet()) {
                                            BOperationCallback callback = entry.getValue();
                                            if (callback != null) {
                                                callback.onDeviceConnected(map);
                                            }
                                        }
                                    }
                                }
                                // 有遠(yuǎn)程設(shè)備斷開連接(連接至一個(gè)藍(lán)牙設(shè)備時(shí)掐场,若關(guān)閉藍(lán)牙往扔,則只會(huì)觸發(fā)STATE_DISCONNECTED,不會(huì)觸發(fā)ACTION_ACL_DISCONNECTED)
                                else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
                                    // 當(dāng)前遠(yuǎn)程藍(lán)牙設(shè)備
                                    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                                    HashMap<String, Object> map = parseDevice2Map(device);
                                    if (bOperationCallbackMap != null && !bOperationCallbackMap.isEmpty()) {
                                        for (Map.Entry<String, BOperationCallback> entry : bOperationCallbackMap.entrySet()) {
                                            BOperationCallback callback = entry.getValue();
                                            if (callback != null) {
                                                callback.onDeviceDisconnected(map);
                                            }
                                        }
                                    }
                                }
                            } catch (Throwable t) {
                                Log.d(TAG, t.getMessage() + "", t);
                            }
                        }
                    };
                    IntentFilter filter = new IntentFilter();
                    // 藍(lán)牙開關(guān)狀態(tài)
                    filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
                    // 本機(jī)的藍(lán)牙連接狀態(tài)發(fā)生變化(連接第一個(gè)遠(yuǎn)程設(shè)備與斷開最后一個(gè)遠(yuǎn)程設(shè)備才觸發(fā))
                    filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
                    // 有遠(yuǎn)程設(shè)備成功連接至本機(jī)(每個(gè)遠(yuǎn)程設(shè)備都會(huì)觸發(fā))
                    filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
                    // 有遠(yuǎn)程設(shè)備斷開連接(每個(gè)遠(yuǎn)程設(shè)備都會(huì)觸發(fā))
                    filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
                    context.registerReceiver(bOperationReceiver, filter);
                    bOperationRegistered = true;
                } catch (Throwable t) {
                    Log.d(TAG, t.getMessage() + "", t);
                }
            }
        }
    }

    /**
     * 打開藍(lán)牙
     *
     */
    @SuppressLint("MissingPermission")
    public void open() {
        try {
            if (DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH")
                    && DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH_ADMIN")) {
                //方式一:請(qǐng)求打開藍(lán)牙
//              Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
//              activity.startActivityForResult(intent, 1);
                //方式二:半靜默打開藍(lán)牙
                //低版本android會(huì)靜默打開藍(lán)牙熊户,高版本android會(huì)請(qǐng)求打開藍(lán)牙
                BluetoothAdapter adapter = getBAdapter();
                adapter.enable();
            }
        } catch (Throwable t) {
            Log.d(TAG, t.getMessage() + "", t);
        }
    }

    /**
     * 判斷藍(lán)牙是否已打開
     *
     * @return
     */
    @SuppressLint("MissingPermission")
    public boolean isEnabled() {
        boolean enabled = false;
        try {
            if (DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH")) {
                BluetoothAdapter adapter = getBAdapter();
                if (adapter != null) {
                    //判斷藍(lán)牙是否開啟
                    if (adapter.isEnabled()) {
                        enabled = true;
                    }
                } else {
                    // Device does not support Bluetooth
                }
            }
        } catch (Throwable t) {
            Log.d(TAG, t.getMessage() + "", t);
        }
        return enabled;
    }

    /**
     * 查詢已配對(duì)的藍(lán)牙設(shè)備
     */
    @SuppressLint("MissingPermission")
    public ArrayList<HashMap<String, Object>> getBondedDevice() {
        ArrayList<HashMap<String, Object>> result = new ArrayList<HashMap<String, Object>>();
        try {
            if (DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH")) {
                BluetoothAdapter adapter = getBAdapter();
                Set<BluetoothDevice> pairedDevices = adapter.getBondedDevices();
                // If there are paired devices
                if (pairedDevices.size() > 0) {
                    for (BluetoothDevice device : pairedDevices) {
                        HashMap<String, Object> deviceInfo = parseDevice2Map(device);
                        deviceInfo.put("__currConnected", (isConnectedDevice(device) ? 1 : 0));
                        result.add(deviceInfo);
                    }
                }
            }
        } catch (Throwable t) {
            Log.d(TAG, t.getMessage() + "", t);
        }
        return result;
    }

    public boolean isConnectedDevice(BluetoothDevice device) {
        boolean isConnected = false;
        if (device != null) {
            try {
                if (DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH")) {
                    //#if def{debuggable}
                    Boolean result = ReflectHelper.invokeInstanceMethod(device, "isConnected");
                    //#else
                    //#=Boolean result = ReflectHelper.invokeInstanceMethod(device, Strings.getString(115));
                    //#endif
                    if (result != null) {
                        isConnected = result.booleanValue();
                    }
                }
            } catch (Throwable t) {
                Log.d(TAG, t.getMessage() + "", t);
            }
        }
        return isConnected;
    }

    /**
     * 查找藍(lán)牙萍膛,包括傳統(tǒng)藍(lán)牙和低功耗藍(lán)牙
     *
     * 注:
     * 1.該方式在查找低功耗藍(lán)牙上效率較低
     * 2.若只需要查找低功耗藍(lán)牙,應(yīng)該使用“低功耗藍(lán)牙API”嚷堡,即 findLE() 方法
     * 3.為防止非正常終止掃描造成的內(nèi)存泄漏蝗罗,使用該方法后艇棕,需在適當(dāng)?shù)臅r(shí)機(jī),主動(dòng)調(diào)用一次unRegisterBtScanReceiver()串塑,以注銷接收器
     *
     * @param scanInterval 掃描時(shí)長(zhǎng)沼琉,單位:秒,建議取值范圍(0,12]
     * @param bScanCallback 掃描結(jié)果回調(diào)
     */
    @SuppressLint("MissingPermission")
    public void findLEAndClassic(int scanInterval, final BScanCallback bScanCallback) {
        try {
            if (DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH")
            && DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH_ADMIN")) {
                final BluetoothAdapter adapter = getBAdapter();
                if (!adapter.isEnabled()) {
                    // 若藍(lán)牙未打開桩匪,直接返回
                    bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
                    return;
                }
                if (mScanning) {
                    // 正在掃描中打瘪,直接返回
                    bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
                    return;
                }
                // 默認(rèn)掃描6秒,若scanInterval不合法傻昙,則使用默認(rèn)值
                final int defaultInterval = 6;
                if (scanInterval <= 0) {
                    scanInterval = defaultInterval;
                }

                // 通過(guò)bluetoothAdapter.startDiscovery()實(shí)現(xiàn)的掃描闺骚,系統(tǒng)會(huì)在掃描結(jié)束(通常是12秒)后自動(dòng)停止,
                // 而cancelDiscovery()可以提前終止掃描妆档。 所以這里的控制邏輯僻爽,相當(dāng)于設(shè)置一個(gè)最大時(shí)間,限制掃描不得超出這個(gè)時(shí)間过吻,
                // 但是很可能提前完成掃描(比如scanInterval > 12秒)
                // 設(shè)置一段時(shí)間后停止掃描(以防系統(tǒng)未正常停止掃描)
                final Handler handler = HandlerThread.newHandler(new Handler.Callback() {
                    @Override
                    public boolean handleMessage(Message msg) {
                        // 若已經(jīng)停止掃描(系統(tǒng)掃描結(jié)束/通過(guò)cancelDiscovery取消掃描)进泼,則再次調(diào)用該方法不會(huì)觸發(fā)ACTION_DISCOVERY_FINISHED
                        adapter.cancelDiscovery();
                        return false;
                    }
                });
                handler.sendEmptyMessageDelayed(0, scanInterval * 1000);

                // 準(zhǔn)備開始掃描
                final ArrayList<HashMap<String, Object>> scanResult = new ArrayList<HashMap<String, Object>>();
                bScanReceiver = new BroadcastReceiver() {
                    public void onReceive(Context context, Intent intent) {
                        try {
                            String action = intent.getAction();

                            if (action.equals(BluetoothDevice.ACTION_FOUND)) { //found device
                                BluetoothDevice device = intent
                                        .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                                HashMap<String, Object> map = parseDevice2Map(device);
                                // 該extra取值與BluetoothDevice對(duì)象中g(shù)etName()取值一致,因此不需要通過(guò)它獲取name
//                          String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
                                short defaultValue = 0;
                                short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, defaultValue);
                                map.put("rssi", rssi);
                                scanResult.add(map);
                            } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED)) {
                                Log.d(TAG, "started");
                            } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
                                Log.d(TAG, "done");
                                mScanning = false;
                                bScanCallback.onScan(scanResult);
                                // 若系統(tǒng)先掃描完纤虽,不需要再通過(guò)代碼主動(dòng)停止掃描
                                handler.removeMessages(0);
                                // 注銷接收器
                                unRegisterBScanReceiver();
                            }
                        } catch (Throwable t) {
                            Log.d(TAG, t.getMessage() + "", t);
                        }
                    }
                };
                IntentFilter filter = new IntentFilter();
                // 用BroadcastReceiver來(lái)取得搜索結(jié)果
                filter.addAction(BluetoothDevice.ACTION_FOUND);
                filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
                // 兩種情況會(huì)觸發(fā)ACTION_DISCOVERY_FINISHED:1.系統(tǒng)結(jié)束掃描(約12秒);2.調(diào)用cancelDiscovery()方法主動(dòng)結(jié)束掃描
                filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
                context.registerReceiver(bScanReceiver, filter);
                bScanRegistered = true;

                // 開始掃描
                mScanning = true;
                adapter.startDiscovery();
            } else {
                // 缺少權(quán)限绞惦,直接返回
                bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
            }
        } catch (Throwable t) {
            Log.d(TAG, t.getMessage() + "", t);
            bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
        }
    }

    public void unRegisterBScanReceiver() {
        try {
            if (bScanReceiver != null && bScanRegistered) {
                context.unregisterReceiver(bScanReceiver);
                bScanRegistered = false;
                bScanReceiver = null;
            }
        } catch (Throwable t) {
            Log.d(TAG, t.getMessage() + "", t);
        }
    }

    /**
     * 查找低功耗藍(lán)牙逼纸,該方法在4.3(API 18)以上,無(wú)法查找“傳統(tǒng)藍(lán)牙”
     *
     * @param scanInterval 掃描時(shí)長(zhǎng)济蝉,單位:秒
     * @param adapter
     * @param bScanCallback 掃描結(jié)果回調(diào)
     */
    @SuppressLint("MissingPermission")
    public void findLE(int scanInterval, final BluetoothAdapter adapter, final BScanCallback bScanCallback) {
        try {
            if (DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH")
                    && DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH_ADMIN")) {
                if (!adapter.isEnabled()) {
                    // 若藍(lán)牙未打開杰刽,直接返回
                    bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
                    return;
                }
                if (mScanning) {
                    // 正在掃描中,直接返回
                    bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
                    return;
                }
                // 默認(rèn)掃描6秒王滤,若scanInterval不合法贺嫂,則使用默認(rèn)值
                final int defaultInterval = 6;
                if (scanInterval <= 0) {
                    scanInterval = defaultInterval;
                }
                // 4.3的低功耗藍(lán)牙API
                if (Build.VERSION.SDK_INT >= 18) {
                    final ArrayList<HashMap<String, Object>> scanResult = new ArrayList<HashMap<String, Object>>();
                    // 5.0又引入了新的藍(lán)牙API(4.3版本的API仍然可用)
                    if (Build.VERSION.SDK_INT < 21) {
                        // 定義掃描結(jié)果回調(diào)
                        final BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
                            /**
                             *
                             * @param device 掃描到的設(shè)備實(shí)例,可從實(shí)例中獲取到相應(yīng)的信息雁乡。如:名稱第喳,mac地址
                             * @param rssi 可理解成設(shè)備的信號(hào)值。該數(shù)值是一個(gè)負(fù)數(shù)踱稍,越大則信號(hào)越強(qiáng)
                             * @param scanRecord 遠(yuǎn)程設(shè)備提供的廣播數(shù)據(jù)的內(nèi)容
                             */
                            @Override
                            public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
                                try {
                                    HashMap<String, Object> map = parseDevice2Map(device);
                                    map.put("rssi", rssi);
//                                  map.put("scanRecord", Data.byteToHex(scanRecord));
                                    scanResult.add(map);
                                } catch (Throwable t) {
                                    Log.d(TAG, t.getMessage() + "", t);
                                }
                            }
                        };

                        // 開始掃描
                        mScanning = true;
                        adapter.startLeScan(leScanCallback);

                        // 設(shè)置一段時(shí)間后停止掃描
                        Handler handler = HandlerThread.newHandler(new Handler.Callback() {
                            @Override
                            public boolean handleMessage(Message msg) {
                                mScanning = false;
                                adapter.stopLeScan(leScanCallback);
                                bScanCallback.onScan(scanResult);
                                return false;
                            }
                        });
                        handler.sendEmptyMessageDelayed(0, scanInterval * 1000);
                    } else {
                        // 定義掃描結(jié)果回調(diào)
                        final ScanCallback mScanCallback = new ScanCallback() {
                            //當(dāng)一個(gè)藍(lán)牙ble廣播被發(fā)現(xiàn)時(shí)回調(diào)
                            @Override
                            public void onScanResult(int callbackType, ScanResult result) {
                                super.onScanResult(callbackType, result);
                                //掃描類型有開始掃描時(shí)傳入的ScanSettings相關(guān)
                                //對(duì)掃描到的設(shè)備進(jìn)行操作曲饱。如:獲取設(shè)備信息。
                                if (result != null) {
                                    HashMap<String, Object> map = new HashMap<String, Object>();
                                    BluetoothDevice device = result.getDevice();
                                    if (device != null) {
                                        map = parseDevice2Map(device);
                                    }
                                    map.put("rssi", result.getRssi());
                                    ScanRecord scanRecord = result.getScanRecord();
                                    scanResult.add(map);
                                }
                            }

                            // 批量返回掃描結(jié)果珠月。一般藍(lán)牙設(shè)備對(duì)象都是通過(guò)onScanResult(int,ScanResult)返回扩淀,
                            // 而不會(huì)在onBatchScanResults(List)方法中返回,除非手機(jī)支持批量掃描模式并且開啟了批量掃描模式。
                            // 批處理的開啟請(qǐng)查看ScanSettings。
                            //@param results 以前掃描到的掃描結(jié)果列表爆雹。
                            @Override
                            public void onBatchScanResults(List<ScanResult> results) {
                                super.onBatchScanResults(results);
                            }

                            //當(dāng)掃描不能開啟時(shí)回調(diào)
                            @Override
                            public void onScanFailed(int errorCode) {
                                super.onScanFailed(errorCode);
                                //掃描太頻繁會(huì)返回ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED燥透,表示app無(wú)法注冊(cè)铜犬,無(wú)法開始掃描逾条。
                            }
                        };
                        //開始掃描
                        final BluetoothLeScanner mBLEScanner = adapter.getBluetoothLeScanner();
                        mScanning = true;
/** 也可指定過(guò)濾條件和掃描配置
                         //創(chuàng)建ScanSettings的build對(duì)象用于設(shè)置參數(shù)
                         ScanSettings.Builder builder = new ScanSettings.Builder()
                         //設(shè)置高功耗模式
                         .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
                         //android 6.0添加設(shè)置回調(diào)類型君账、匹配模式等
                         if(android.os.Build.VERSION.SDK_INT >= 23) {
                         //定義回調(diào)類型
                         builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES);
                         //設(shè)置藍(lán)牙LE掃描濾波器硬件匹配的匹配模式
                         builder.setMatchMode(ScanSettings.MATCH_MODE_STICKY);
                         }
                         // 若設(shè)備支持批處理掃描酝惧,可以選擇使用批處理区端,但此時(shí)掃描結(jié)果僅觸發(fā)onBatchScanResults()
                         //             if (bluetoothAdapter.isOffloadedScanBatchingSupported()) {
                         //                 //設(shè)置藍(lán)牙LE掃描的報(bào)告延遲的時(shí)間(以毫秒為單位)
                         //                 //設(shè)置為0以立即通知結(jié)果
                         //                 builder.setReportDelay(0L);
                         //             }
                         ScanSettings scanSettings = builder.build();
                         //可設(shè)置過(guò)濾條件值漫,在第一個(gè)參數(shù)傳入,但一般不設(shè)置過(guò)濾织盼。
                         mBLEScanner.startScan(null, scanSettings, mScanCallback);
 */
                        mBLEScanner.startScan(mScanCallback);
                        // 設(shè)置一段時(shí)間后停止掃描
                        Handler handler = HandlerThread.newHandler(new Handler.Callback() {
                            @Override
                            public boolean handleMessage(Message msg) {
                                mScanning = false;
                                mBLEScanner.stopScan((mScanCallback));
                                bScanCallback.onScan(scanResult);
                                return false;
                            }
                        });
                        handler.sendEmptyMessageDelayed(0, scanInterval * 1000);
                    }
                } else {
                    findLEAndClassic(scanInterval, bScanCallback);
                }
            } else {
                // 缺少權(quán)限杨何,直接返回
                bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
            }
        } catch (Throwable t) {
            Log.d(TAG, t.getMessage() + "", t);
            bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
        }
    }

    private BluetoothAdapter getBAdapter() {
        BluetoothAdapter adapter = null;
        try {
            if (Build.VERSION.SDK_INT >= 18) {
                BluetoothManager manager = (BluetoothManager) DeviceHelper.getInstance(context).getSystemServiceSafe(Context.BLUETOOTH_SERVICE);
                adapter = manager.getAdapter();

            } else {
                adapter = BluetoothAdapter.getDefaultAdapter();
            }
        } catch (Throwable t) {
            Log.d(TAG, t.getMessage() + "", t);
        }
        return adapter;
    }

    @SuppressLint("MissingPermission")
    private HashMap<String, Object> parseDevice2Map(BluetoothDevice device) {
        HashMap<String, Object> map = new HashMap<String, Object>();
        if (device != null) {
            try {
                map.put("name", device.getName());
                map.put("address", device.getAddress());
                map.put("bondState", device.getBondState());

                BluetoothClass btClass = device.getBluetoothClass();
                int majorClass = btClass.getMajorDeviceClass();
                int deviceClass = btClass.getDeviceClass();
                map.put("majorClass", majorClass);
                map.put("deviceClass", deviceClass);

                if (Build.VERSION.SDK_INT >= 18) {
                    map.put("type", device.getType());
                }
                // 已配對(duì)的設(shè)備,同時(shí)獲取其uuids
                if (Build.VERSION.SDK_INT >= 15 && device.getBondState() == 12) {
                    ArrayList<String> uuids = new ArrayList<String>();
                    ParcelUuid[] parcelUuids = device.getUuids();
                    if (parcelUuids != null && parcelUuids.length > 0) {
                        for (ParcelUuid parcelUuid : parcelUuids) {
                            if (parcelUuid != null && parcelUuid.getUuid() != null) {
                                uuids.add(parcelUuid.getUuid().toString());
                            }
                        }
                    }
                    map.put("uuids", uuids);
                }
            } catch (Throwable t) {
                Log.d(TAG, t.getMessage() + "", t);
            }
        }
        return map;
    }

    public static class BOperationCallback {
        /**
         * 打開藍(lán)牙
         */
        protected void onEnabled() {}

        /**
         * 斷開藍(lán)牙
         */
        protected void onDisabled() {}

        /**
         * 藍(lán)顏連接狀態(tài)變化
         *
         * @param connect true:連接到第一個(gè)設(shè)備沥邻,false:斷開最后一個(gè)設(shè)備
         * @param btDevice 當(dāng)前設(shè)備
         */
        protected void onConnectionChanged(boolean connect, HashMap<String, Object> btDevice) {}

        /**
         * 有遠(yuǎn)程設(shè)備成功連接
         *
         * @param btDevice
         */
        protected void onDeviceConnected(HashMap<String, Object> btDevice) {}

        /**
         * 有遠(yuǎn)程設(shè)備斷開連接
         *
         * @param btDevice
         */
        protected void onDeviceDisconnected(HashMap<String, Object> btDevice) {}
    }

    public interface BScanCallback {
        void onScan(ArrayList<HashMap<String, Object>> result);
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末危虱,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子唐全,更是在濱河造成了極大的恐慌埃跷,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邮利,死亡現(xiàn)場(chǎng)離奇詭異弥雹,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)延届,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門剪勿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人方庭,你說(shuō)我怎么就攤上這事厕吉。” “怎么了械念?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵头朱,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我龄减,道長(zhǎng)项钮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任欺殿,我火速辦了婚禮寄纵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘脖苏。我一直安慰自己程拭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布棍潘。 她就那樣靜靜地躺著恃鞋,像睡著了一般崖媚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上恤浪,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天畅哑,我揣著相機(jī)與錄音,去河邊找鬼水由。 笑死荠呐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的砂客。 我是一名探鬼主播泥张,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼鞠值!你這毒婦竟也來(lái)了媚创?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤彤恶,失蹤者是張志新(化名)和其女友劉穎钞钙,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體声离,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡芒炼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抵恋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焕议。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖弧关,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情唤锉,我是刑警寧澤世囊,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站窿祥,受9級(jí)特大地震影響株憾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晒衩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一嗤瞎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧听系,春花似錦贝奇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)毕源。三九已至,卻和暖如春陕习,著一層夾襖步出監(jiān)牢的瞬間霎褐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工该镣, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留冻璃,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓损合,卻偏偏與公主長(zhǎng)得像省艳,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子塌忽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容