Home Articles

Android BLE扫描,SCAN_FAILED_ALREADY_STARTED并获取BluetoothDevice实例

Asked
Viewed 642 times
1

我的Android应用程序仅适用于API Level 21,我正在Nexus 5上进行测试 . 它是一个BLE中心,连接到BLE外围设备,我在其上控制固件 .

首次使用startScan()扫描设备(外围设备)时,一切都很顺利 . 假设我接着传递给ScanCallback的BluetoothDevice实例 . 然后我可以在其上调用connectGatt(),发现服务和读/写特性 . 到现在为止还挺好 .

现在假设用户导航,时间过去,之后他们回到应用程序并想要做一些需要特性的事情 . 我现在必须退后一步,确保我拥有所有这些东西的非null实例:

BluetoothLeScanner
BluetoothDevice
BluetoothGatt
BluetoothGattCharacteristic

此时,我的BluetoothDevice实例为null . 没问题 - 我会再次扫描并获得一个新实例 . 啊,但是现在我根本没有调用ScanCallback.onScanResult() - 我使用错误代码调用了ScanCallback.onScanFailed()

SCAN_FAILED_ALREADY_STARTED

这是事实,BLE堆栈已被前一次扫描记住,并且拒绝复制它 . 但是,我现在如何从该扫描中获取BluetoothDevice实例?我认为在Level 21 API中无法做到这一点 .

这是我的BLE服务的完整代码,供各种活动使用 .

/* Copyright (C) Eliot Stock - All Rights Reserved
 * Unauthorized copying of this file, via any medium is strictly prohibited.
 * Proprietary and confidential.
 */
package com.eliotstock.bike.service;

import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.util.Log;

import com.eliotstock.bike.ble.BleService;
import com.eliotstock.bike.ble.Characteristic;

import java.util.ArrayList;
import java.util.List;

public class BleServiceForSoPost extends Service {

    private final String TAG = this.getClass().getSimpleName();

    private SharedPreferences sharedPreferences;

    private String deviceMacAddress;

    private BluetoothLeScanner bluetoothLeScanner;
    private BluetoothDevice bleDevice;
    private BluetoothGatt bleGatt;

    private List<ScanFilter> scanFiltersFirstTime;
    private List<ScanFilter> scanFiltersReconnect;
    private ScanSettings scanSettingsLowPower;
    private ScanSettings scanSettingsLowLatency;

    private final IBinder binder = new BikeTrackerServiceBinder();

    private static final String PREF_DEVICE_MAC_ADDRESS = "pref_device_mac_address";

    private BluetoothGattCharacteristic bikeTrackerTestMode;
    private BluetoothGattCharacteristic txPowerLevel;

    private IntentFilter bondStateChangedFilter =
            new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);

    private enum Action {
        NONE,
        READ_TEST_MODE,
        WRITE_TEST_MODE
    }

    private Action action;
    private Object actionValue;
    private Boolean stopScanInProgress = false;

    public class BikeTrackerServiceBinder extends Binder {
        public BleServiceForSoPost getService() {
            return BleServiceForSoPost.this;
        }
    }

    public BleServiceForSoPost() {
        Log.d(TAG, "Constructor " + this.hashCode());
    }

    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate() " + this.hashCode());

        action = Action.NONE;
        actionValue = null;

        sharedPreferences = PreferenceManager.getDefaultSharedPreferences(
                getApplicationContext());
        deviceMacAddress = sharedPreferences.getString(PREF_DEVICE_MAC_ADDRESS, null);

        final BluetoothManager bluetoothManager =
                (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        final BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();

        if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            enableBtIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(enableBtIntent);
            return;
        }

        bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();

        Log.d(TAG, "Bonded devices (whether connected or not):");
        for (BluetoothDevice device : bluetoothAdapter.getBondedDevices()) {
            Log.d(TAG, "* " + device.getName() + " (" + device.getAddress() + ")");
        }

        ScanFilter scanFilterFirstTime = new ScanFilter.Builder()
                .setDeviceName("Bike")
                .build();
        scanFiltersFirstTime = new ArrayList<>();
        scanFiltersFirstTime.add(scanFilterFirstTime);

        ScanFilter scanFilterReconnect = new ScanFilter.Builder()
                .setDeviceAddress(deviceMacAddress)
                .build();
        scanFiltersReconnect = new ArrayList<>();
        scanFiltersReconnect.add(scanFilterReconnect);

        scanSettingsLowPower = new ScanSettings.Builder()
                .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
                .build();

        scanSettingsLowLatency = new ScanSettings.Builder()
                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) // High power for first scan only.
                .build();

        if (deviceMacAddress == null) {
            Log.d(TAG, "We do NOT know our device MAC address. Not scanning until prompted by" +
                    " user.");
        }
        else {
            Log.d(TAG, "We know our device MAC address. Scanning only for it.");

            bluetoothLeScanner.startScan(scanFiltersReconnect, scanSettingsLowPower, scanCallback);
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand() " + this.hashCode());

        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind() " + this.hashCode());

        return binder;
    }

    public void scanAndBindToNearestDevice() {
        if (deviceMacAddress != null) {
            return;
        }

        bluetoothLeScanner.startScan(scanFiltersFirstTime, scanSettingsLowLatency, scanCallback);
    }

    public void forgetDevice() {
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putString(PREF_DEVICE_MAC_ADDRESS, deviceMacAddress);
    }

    @Override
    public void onDestroy() {
        Log.i(TAG, "onDestroy() " + this.hashCode());

        if (bleGatt == null) {
            return;
        }

        Log.d(TAG, "Closing BluetoothGatt instance.");
        bleGatt.close();
        bleGatt = null;
    }

    public void readTestMode() {
        action = Action.READ_TEST_MODE;

        reconnect();

        if (bikeTrackerTestMode == null) {
            Log.e(TAG,  "Trying to read test mode before we have a characteristic instance" +
                    " for it.");
            return;
        }

        Log.d(TAG, "Reading characteristic for test mode.");

        // Calls back to onCharacteristicRead(), which in turns calls back to
        // SettingsActivity.setTestMode().
        if (!bleGatt.readCharacteristic(bikeTrackerTestMode)) {
            Log.e(TAG, "Can't read test mode. BluetoothGatt.readCharacteristic() returned false.");
        }
    }

    public void writeTestMode(Integer value) {
        action = Action.WRITE_TEST_MODE;
        actionValue = value;

        reconnect();

        if (bikeTrackerTestMode == null) {
            Log.e(TAG,  "Trying to write test mode before we have a characteristic instance" +
                    " for it.");
            return;
        }

        Log.d(TAG, "Writing characteristic for test mode.");

        bikeTrackerTestMode.setValue(value, BluetoothGattCharacteristic.FORMAT_UINT8, 0);

        if (!bleGatt.writeCharacteristic(bikeTrackerTestMode)) {
            Log.e(TAG, "Can't write test mode. BluetoothGatt.writeCharacteristic() returned false.");
        }
    }

    public Boolean isDeviceConnected() {
        return (bleDevice != null && bleGatt != null && bikeTrackerTestMode != null);
    }

    // Check for null BLE Device, BLE GATT and BLE Characteristics, in that order, doing any
    // scanning, connecting or bonding required to get back to a state where all three are
    // available and we're bonded.
    private void reconnect() {
        if (bluetoothLeScanner == null) {
            Log.w(TAG, "No BLE scanner instance available, probably because Bluetooth is turned" +
                    " off. Bailing out. Can't reconnect.");
            return;
        }

        if (bleDevice == null) {
            bluetoothLeScanner.startScan(scanFiltersFirstTime, scanSettingsLowLatency, scanCallback);
            return;
        }

        if (bleGatt == null) {
            bleDevice.connectGatt(BleServiceForSoPost.this, true, gattCallback);
            return;
        }

        if (anyCharacteristicsAreNull()) {
            if (!bleGatt.discoverServices()) {
                Log.e(TAG, "Couldn't start discovering services."
                        + " BluetoothGatt.discoverServices() returned false. If this follows"
                        + " the status 133 problem with onConnectionStateChange(), reboot.");
            }
            return;
        }

        if (bleDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
            registerReceiver(bleBroadcastReceiver, bondStateChangedFilter);

            if (!bleDevice.createBond()) {
                Log.e(TAG,  "Can't create bond. BluetoothDevice.createBond() returned false.");
            }
        }

        Log.d(TAG, "Falling out of reconnect(). BLE device, GATT and characteristics are all ready"
                + " and we're bonded.");
    }

    private final ScanCallback scanCallback = new ScanCallback() {

        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            for (ScanResult result : results) {
                onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result);
            }
        }

        public void onScanResult(int callbackType, ScanResult result) {
            Log.i(TAG, "Advertisement: Device name: " + result.getDevice().getName()
                    + ", address: " + result.getDevice().getAddress()
                    + ", RSSI: " + result.getRssi());

            switch (result.getDevice().getBondState()) {
                case BluetoothDevice.BOND_NONE:
                    Log.d(TAG, "Bond state: BOND_NONE");
                    break;

                case BluetoothDevice.BOND_BONDING:
                    Log.d(TAG, "Bond state: BOND_BONDING");
                    break;

                case BluetoothDevice.BOND_BONDED:
                    Log.d(TAG, "Bond state: BOND_BONDED");
                    break;
            }

            if (stopScanInProgress) {
                Log.d(TAG, "Dropping onScanResult() call while we're in the process of stopping"
                        + " scanning.");
                return;
            }
            else {
                Log.d(TAG, "Found device. Stopping scanning.");
                stopScanInProgress = true;
            }

            bleDevice = result.getDevice();
            bluetoothLeScanner.stopScan(scanCallback);

            if (deviceMacAddress != null) {
                Log.d(TAG, "This is a reconnection. We already know our device's MAC address.");

                if (!result.getDevice().getAddress().equals(deviceMacAddress)) {
                    Log.w(TAG, "We scanned for a known device MAC address (" + deviceMacAddress
                            + ") but got a scan result for a different one ("
                            + result.getDevice().getAddress() + "). This should never happen.");
                    return;
                }
            }
            else {
                Log.d(TAG, "This is a first connection. Storing device MAC address as shared"
                        + " preference.");

                deviceMacAddress = result.getDevice().getAddress();             
                SharedPreferences.Editor editor = sharedPreferences.edit();
                editor.putString(PREF_DEVICE_MAC_ADDRESS, deviceMacAddress);

                editor.apply();
            }

            Log.d(TAG, "Connecting to GATT server.");

            stopScanInProgress = false;

            if (bleDevice == null) {
                Log.wtf(TAG, "BLE Device is null in main looper thread."
                        + " Why? We should be calling connectGatt() now but can't.");
                return;
            }

            bleDevice.connectGatt(BleServiceForSoPost.this, true, gattCallback);
        }

        @Override
        public void onScanFailed(int errorCode) {
            Log.w(TAG, "Scan failed.");

            switch (errorCode) {
                case SCAN_FAILED_ALREADY_STARTED:
                    Log.w(TAG, "Reason: 'Fails to start scan as BLE scan with the same settings" +
                            " is already started by the app.' Resuming reconnection from after" +
                            " scan. BLE device: " + bleDevice + ", BLE GATT: " + bleGatt);
                    break;
                case SCAN_FAILED_APPLICATION_REGISTRATION_FAILED:
                    Log.w(TAG, "Reason: 'Fails to start scan as app cannot be registered.'");
                    break;
                case SCAN_FAILED_FEATURE_UNSUPPORTED:
                    Log.w(TAG, "Reason: 'Fails to start power optimized scan as this feature is not"
                            + " supported.'");
                    break;
                case SCAN_FAILED_INTERNAL_ERROR:
                    Log.w(TAG, "Reason: 'Fails to start scan due to an internal error.'");
                    break;
            }

            bleDevice = null;
        }
    };

    private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            Log.d(TAG, "onConnectionStateChange()");

            switch (status) {
                case BluetoothGatt.GATT_SUCCESS:
                    Log.d(TAG, "Status: GATT_SUCCESS");
                    break;
                case BluetoothGatt.GATT_FAILURE:
                    Log.d(TAG, "Status: GATT_FAILURE");
                    break;
                case 8:
                    Log.d(TAG, "Status: 8. Normal if caused by peers moving apart.");
                    break;
                case 22:
                    Log.d(TAG, "Status: 22. Don't know what this is but have seen it before.");
                    break;
                case 34:
                    Log.d(TAG, "Status: 34. Don't know what this is but have seen it before.");
                    break;
                case 133:
                    Log.e(TAG, "Status 133. This may mean the connection was lost because the"
                            + " remote device dropped it. See Android 4.4 bug.");
                    gatt.close();
                    gatt.connect();
                    break;
                default:
                    Log.d(TAG, "Unrecognised status: " + status);
            }

            switch (newState) {
                case BluetoothProfile.STATE_CONNECTED:
                    Log.d(TAG, "New state: STATE_CONNECTED");           
                    break;
                case BluetoothProfile.STATE_CONNECTING:
                    Log.d(TAG, "New state: STATE_CONNECTING");              
                    break;
                case BluetoothProfile.STATE_DISCONNECTED:
                    Log.d(TAG, "New state: STATE_DISCONNECTED");            
                    break;
                case BluetoothProfile.STATE_DISCONNECTING:
                    Log.d(TAG, "New state: STATE_DISCONNECTING");           
                    break;
                default:
                    Log.d(TAG, "Unrecognised new state: " + newState);                  
            }

            if (newState == BluetoothProfile.STATE_CONNECTED) {
                Log.i(TAG, "CONNECTED to GATT server. Starting service discovery.");

                bleGatt = gatt;

                if (!gatt.discoverServices()) {
                    Log.e(TAG, "Couldn't start discovering services."
                            + " BluetoothGatt.discoverServices() returned false. If this follows"
                            + " the status 133 problem with onConnectionStateChange(), reboot.");
                }
            }
            else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                Log.i(TAG, "DISCONNECTED from GATT server.");

                if (bleGatt != null) {
                    Log.d(TAG, "Closing BluetoothGatt instance.");
                    bleGatt.close();
                }

                bleGatt = null;
                bleDevice = null;
                txPowerLevel = null;

                if (deviceMacAddress == null) {
                    Log.wtf(TAG, "We've become disconnected but don't yet know our device MAC"
                            + " address, which should be impossible.");
                    return;
                }

                Log.d(TAG, "Waiting half a second...");

                new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        Log.d(TAG, "Starting scanning at low power, looking only for our known" +
                                " device.");

                        bluetoothLeScanner.startScan(scanFiltersReconnect, scanSettingsLowPower,
                                scanCallback);
                    }
                }, 500);
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.d(TAG, "Discovered services:");

                for (BluetoothGattService gattService : gatt.getServices()) {
                    Log.d(TAG, "*" + BleService.toDebugString(gattService));

                    for (BluetoothGattCharacteristic c : gattService.getCharacteristics()) {
                        Log.d(TAG, "**" + Characteristic.toDebugString(c));

                        if (Characteristic.BIKE_TRACKER_TEST_MODE_ID.equals(c.getUuid())) {
                            bikeTrackerTestMode = c;
                        }

                        if (Characteristic.TX_POWER_LEVEL.equals(c.getUuid())) {
                            txPowerLevel = c;
                        }
                    }
                }
            }
            else if (status == 129) {
                Log.w(TAG, "onServicesDiscovered received GATT_INTERNAL_ERROR");

                bikeTrackerTestMode = null;
                txPowerLevel = null;
            }
            else {
                Log.w(TAG, "onServicesDiscovered received non-GATT_SUCCESS status: " + status);

                bikeTrackerTestMode = null;
                txPowerLevel = null;
            }

            if (bleDevice == null) {
                Log.wtf(TAG, "BluetoothDevice instance is null at service discovery. How did we"
                        + " get this far? Using the one on this GATT instance.");
                bleDevice = gatt.getDevice();
            }

            if (BluetoothDevice.BOND_NONE == bleDevice.getBondState()) {
                Log.d(TAG, "Not yet bonded. Creating bond now.");

                registerReceiver(bleBroadcastReceiver, bondStateChangedFilter);

                if (!bleDevice.createBond()) {
                    Log.e(TAG,  "Can't create bond. BluetoothDevice.createBond() returned false.");
                }

                return;
            }
            else {
                Log.d(TAG, "Bonded (or bonding) already. Good.");
            }

            if (bleGatt == null) {
                Log.w(TAG, "We have no BluetoothGatt instance and should do by now. Bailing out of"
                        + " doing anything with Characteristics.");
                return;
            }

            if (action == Action.READ_TEST_MODE) {
                Log.d(TAG, "Reading characteristic for test Mode.");

                if (!bleGatt.readCharacteristic(bikeTrackerTestMode)) {
                    Log.e(TAG, "Can't read test mode. BluetoothGatt.readCharacteristic()" +
                            " returned false.");
                }
            }

            if (action == Action.WRITE_TEST_MODE) {
                Log.d(TAG, "Writing characteristic for test mode.");

                Integer value = (Integer)actionValue;
                bikeTrackerTestMode.setValue(value, BluetoothGattCharacteristic.FORMAT_UINT8, 0);

                if (!bleGatt.writeCharacteristic(bikeTrackerTestMode)) {
                    Log.e(TAG, "Can't write test mode. BluetoothGatt.writeCharacteristic()" +
                            " returned false.");
                }

                actionValue = null;
            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic c,
                int status) {
            Log.d(TAG, "onCharacteristicRead(): " + Characteristic.toDebugString(c));

            onCharacteristicReadOrChanged(c, status);
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic c) {
            Log.d(TAG, "onCharacteristicChanged(): " + Characteristic.toDebugString(c));

            onCharacteristicReadOrChanged(c, 0);
        }

        private void onCharacteristicReadOrChanged(BluetoothGattCharacteristic c, int status) {
            if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
                Log.w(TAG, "Got GATT_INSUFFICIENT_AUTHENTICATION status. Are we bonded?");
                return;
            }
            else if (status == 132) {
                Log.w(TAG, "Got non-GATT_SUCCESS status of 132. All bets off below.");
            }
            else if (status == 133) {
                Log.w(TAG, "Got non-GATT_SUCCESS status of 133. All bets off below.");
            }
            else if (status != BluetoothGatt.GATT_SUCCESS) {
                Log.w(TAG, "Got non-GATT_SUCCESS status of " + status + ". All bets off below.");
            }

            if (bikeTrackerTestMode.equals(c)) {
                try {
                    Integer testModeInt = c.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8,
                            0);

                    Log.d(TAG, "Bike Tracker test mode: " + testModeInt);
                }
                catch (NullPointerException e) {
                    Log.w(TAG, "Can't really read test mode characteristic. Value is null.");
                }
            }
            else if (txPowerLevel.equals(c)) {
                Log.d(TAG, "Tx Power Level: " + new String(c.getValue()));
            }
            else {
                Log.w(TAG, "Unrecognised characteristic read or changed: " + c);
            }

            action = null;
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic c,
                int status) {
            Log.d(TAG, "onCharacteristicWrite(): " + Characteristic.toDebugString(c));

            if (BluetoothGatt.GATT_SUCCESS == status) {
                Log.d(TAG, "Success");
            }
            else {
                Log.e(TAG, "Error writing characteristic. Status: " + status);
            }
        }

        @Override
        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
        }
    };

    private final BroadcastReceiver bleBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d(TAG, "onReceive(): " + intent.getAction());

            if (intent.getAction().equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
                final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
                final int previousBondState =
                        intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1);

                if (previousBondState != BluetoothDevice.BOND_BONDED
                        && bondState == BluetoothDevice.BOND_BONDED) {
                    Log.d(TAG, "Bonded. Unregistering broadcast receiver.");
                    unregisterReceiver(this);

                    if (bleGatt == null) {
                        Log.w(TAG, "Can't read sensitivity. BluetoothGatt is still null.");
                        return;
                    }

                    Log.d(TAG, "Reading characteristic for test mode.");

                    if (!bleGatt.readCharacteristic(bikeTrackerTestMode)) {
                        Log.e(TAG, "Can't read test mode. BluetoothGatt.readCharacteristic()" +
                                " returned false.");
                    }
                }
                else if (previousBondState == BluetoothDevice.BOND_BONDED
                        && bondState != BluetoothDevice.BOND_BONDED) {
                    Log.d(TAG, "Unbonded.");
                }
            }
            else {
                Log.d(TAG, "Ignoring BLE irrelevant broadcast intent: " + intent.getAction());
            }
        }
    };

    private Boolean anyCharacteristicsAreNull() {
        if (bikeTrackerTestMode == null
            || txPowerLevel == null) {
            return true;
        }

        return false;
    }

}

1 Answer

  • -1

    试试这个:

    bluetoothLeScanner.stopScan(scanCallback);
    

    改成

    bluetoothLeScanner.stopScan(this);
    

Related