我的Android应用程序仅适用于API Level 21,我在Nexus 5上进行测试。它是一个BLE中心,连接到BLE外围设备,我可以在其上控制固件。
首次使用startScan()扫描设备(外围设备)时,一切正常。假设我接着传递给ScanCallback的BluetoothDevice实例。然后我可以在其上调用connectGatt(),发现服务和读/写特性。到目前为止,非常好。
现在假设用户离开,时间过去,之后他们回到应用程序并想要做一些需要特性的事情。我现在必须退后一步,确保我拥有所有这些东西的非null实例:
BluetoothLeScanner
BluetoothDevice
BluetoothGatt
BluetoothGattCharacteristic
此时,我的BluetoothDevice实例为空。没问题 - 我会再次扫描并获得一个新实例。啊,但现在我根本没有调用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;
}
}
答案 0 :(得分:0)
试试这个:
bluetoothLeScanner.stopScan(scanCallback);
更改为
bluetoothLeScanner.stopScan(this);
答案 1 :(得分:0)
我在 Google Pixel 中也遇到了同样的问题 onScanFailed,错误代码为 1(已启动)。我尝试了所有可能的解决方案,例如重新启动设备、以编程方式停止扫描等。但对我来说没有任何帮助。 解决方法: 从 Playstore 下载任何蓝牙查找器或扫描仪应用程序并尝试扫描。现在尝试使用您的应用程序。这使我的应用程序扫描成功。