фоновое сканирование BLE, иногда bluetoothAdapter.isEnabled() имеет значение false. Почему это происходит?
фоновое сканирование BLE, иногда bluetoothAdapter.isEnabled() имеет значение false. Почему это происходит?
Сначала я попробовал фоновое сканирование BLE. (Это периодически.)
Приложение использует много фона, но мой клиент хочет именно этого.
Моя реализация была выполнена следующими способами:
Я пробуждаю приложение каждый час с помощью диспетчера будильника.
Если получен обратный вызов от широковещательного приемника приложения, запланируйте следующий сигнал тревоги, и WorkManager выполнит его с ускоренной опцией.
Запустите сканирование BLE 40 секунд -> отправьте данные устройства BLE на сервер.
Манифест.xml
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
<uses-feature android:name="android.hardware.location.gps" android:required="true" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation"
tools:targetApi="s" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
scanCallbackImpl.java
public class ScanCallbackImpl extends ScanCallback {
private final PublishSubject<ScanResult> onScanResult$;
private final Context context;
private static final String SCAN_TEST = "SCAN_TEST";
public ScanCallbackImpl(Context context) {
super();
this.context = context;
onScanResult$ = PublishSubject.create();
}
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
Log.d(SCAN_TEST, "onScanResult");
onScanResult$.onNext(result);
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
Log.d(SCAN_TEST, "onBatchScanResult");
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.d(SCAN_TEST, "onScanFailed");
switch (errorCode) {
case ScanCallback.SCAN_FAILED_ALREADY_STARTED:
onScanResult$.onError(new AlreadyStartedException(context));
break;
case ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED:
onScanResult$.onError(new ApplicationRegistrationFailedException(context));
break;
case ScanCallback.SCAN_FAILED_INTERNAL_ERROR:
onScanResult$.onError(new InternalErrorException(context));
break;
case ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED:
onScanResult$.onError(new FeatureUnsupportedException(context));
break;
}
}
public PublishSubject<ScanResult> getOnScanResult$() {
return onScanResult$;
}
}
ScanService.java
private void scanClose() {
if(onScanResult != null && !onScanResult.isDisposed()){
onScanResult.dispose();
}
if(startScan != null && !startScan.isDisposed()){
startScan.dispose();
}
}
public Observable<ScanDeviceVo> startScan(String deviceName, String macAddress, long timeoutSec, ScanMode scanMode) {
scanClose();
Observable<ScanResult> observable = Observable.create(emitter -> {
if(!checkBleScanSupport()){
emitter.onError(new BleScanUnsupportedException(context));
return;
}
if(!checkEnable(emitter)){
return;
}
if(!isAlwaysLocation()){
emitter.onError(new LocationPermissionException(context));
return;
}
BluetoothLeScanner bleScanner = bluetoothAdapter.getBluetoothLeScanner();
scanCallback = new ScanCallbackImpl(context);
scanCallback.getOnScanResult$()
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.doOnSubscribe(disposable -> onScanResult = disposable)
.subscribe(emitter::onNext, emitter::onError);
isScanning = true;
bleScanner.startScan(setScanFilterList(deviceName, macAddress), setScanSetting(scanMode), scanCallback);
});
return observable
.observeOn(Schedulers.io())
.take(timeoutSec, TimeUnit.SECONDS)
.timeout(timeoutSec, TimeUnit.SECONDS)
.onErrorResumeNext(throwable -> throwable instanceof TimeoutException ? Observable.error(new NotFoundException(context)) : Observable.error(throwable))
.distinct(scanResult -> scanResult.getScanRecord().getBytes())
.map(scanResult -> ScanDeviceVo.builder()
.name(scanResult.getDevice().getName())
.macAddr(scanResult.getDevice().getAddress())
.packet(scanResult.getScanRecord().getBytes())
.rssi(scanResult.getRssi()).build())
.doOnSubscribe(disposable -> startScan = disposable)
.doOnTerminate(this::stopScan);
}
private ScanSettings setScanSetting(ScanMode scanMode){
ScanSettings.Builder builder = new ScanSettings.Builder();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
builder = builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE);
}
return builder.setScanMode(scanMode.rawValue())
.setReportDelay(0)
.build();
}
private List<ScanFilter> setScanFilterList(String deviceName, String macAddress) {
List<ScanFilter> scanFilterList = new Vector<>();
ScanFilter.Builder builder = new ScanFilter.Builder();
if(!deviceName.equals("")){
builder = builder.setDeviceName(deviceName);
}
if(!macAddress.equals("")){
builder = builder.setDeviceAddress(macAddress);
}
scanFilterList.add(builder.build());
return scanFilterList;
}
ScanWorker.java
....
....
ScanService scanService = new ScanService(context);
scanService.startScan(deviceName, syncedMac, 40, ScanMode.BALANCED)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.firstOrError()
.onErrorResumeNext(throwable -> {
scanService.stopScan();
return throwable instanceof NoSuchElementException ? Single.error(new NotFoundException(context)) : Single.error(throwable);
})
.map(scanDeviceVo -> new AdvData(scanDeviceVo.getPacket()))
.subscribe(advData -> { ..send with retrofit.. });
....
....
Если возникает ошибка, она предназначена для отправки в Firebase Crashlytics (например, отключение Bluetooth, исключение разрешений и т. д.).
public CheckStatus checkBleSupport(){
if(bluetoothAdapter == null) {
return CheckStatus.BLUETOOTH_UNSUPPORTED;
}
if(!bluetoothAdapter.isEnabled()) { //<---- here, i desigend this logic to be sent to firebase crashlytics. this line is false sometimes, when i BLE scanning a background that works periodically.
return CheckStatus.BLUETOOTH_OFF;
}
if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
return CheckStatus.BLE_UNSUPPORTED;
}
return CheckStatus.BLUETOOTH_ENABLE;
}
Я заметил, что на некоторых мобильных телефонах иногда возникают исключения Bluetooth Off. (это означает, что bluetoothAdapter.isEnabled() имеет значение false.)
возникает ошибка спецификация телефона: Android 13Galaxy S21 5G
Почему это происходит?
Я обнаружил веб-сайт для разработчиков Android. (https://developer.android.com/about/versions/13/changes/battery)
Что изменилось в Android 13, так это то, что «если вы получаете слишком много широковещательных приемников, они добавляются в корзину приложений с ограниченным доступом».
Я ввел следующую команду в терминале.
adb shell am get-standby-bucket PACKAGE_NAME
//20
Связано ли значение резервного сегмента 20 с тем, что фон bluetoothAdapter.isEnabled() иногда имеет значение false?
Я был обеспокоен в течение нескольких дней ...