From 4d72e386d10237039d3a5ab937c6df77df3d2c85 Mon Sep 17 00:00:00 2001 From: Marco Salis Date: Tue, 13 Sep 2016 12:44:58 +0100 Subject: [PATCH] Start and stop BLE scans from a background thread to prevent blocking the UI (issue #136) --- .../beacon/service/BeaconService.java | 1 + .../service/scanner/CycledLeScanner.java | 15 +++- .../CycledLeScannerForJellyBeanMr2.java | 56 ++++++++++---- .../scanner/CycledLeScannerForLollipop.java | 76 +++++++++++-------- 4 files changed, 104 insertions(+), 44 deletions(-) diff --git a/src/main/java/org/altbeacon/beacon/service/BeaconService.java b/src/main/java/org/altbeacon/beacon/service/BeaconService.java index 4bc6b4274..dfe2baa77 100644 --- a/src/main/java/org/altbeacon/beacon/service/BeaconService.java +++ b/src/main/java/org/altbeacon/beacon/service/BeaconService.java @@ -273,6 +273,7 @@ public void onDestroy() { LogManager.i(TAG, "onDestroy called. stopping scanning"); handler.removeCallbacksAndMessages(null); mCycledScanner.stop(); + mCycledScanner.destroy(); monitoringStatus.stopStatusPreservation(); } diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java index d0f4e7505..33165cdb0 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java @@ -12,6 +12,8 @@ import android.content.pm.PackageManager; import android.os.Build; import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; import android.os.SystemClock; import org.altbeacon.beacon.BeaconManager; @@ -40,7 +42,10 @@ public abstract class CycledLeScanner { private long mScanPeriod; protected long mBetweenScanPeriod; - protected final Handler mHandler = new Handler(); + + protected final Handler mHandler = new Handler(Looper.getMainLooper()); + protected final Handler mScanHandler; + private final HandlerThread mScanThread; protected final BluetoothCrashResolver mBluetoothCrashResolver; protected final CycledLeScanCallback mCycledLeScanCallback; @@ -57,6 +62,10 @@ protected CycledLeScanner(Context context, long scanPeriod, long betweenScanPeri mCycledLeScanCallback = cycledLeScanCallback; mBluetoothCrashResolver = crashResolver; mBackgroundFlag = backgroundFlag; + + mScanThread = new HandlerThread("CycledLeScannerThread"); + mScanThread.start(); + mScanHandler = new Handler(mScanThread.getLooper()); } public static CycledLeScanner createScanner(Context context, long scanPeriod, long betweenScanPeriod, boolean backgroundFlag, CycledLeScanCallback cycledLeScanCallback, BluetoothCrashResolver crashResolver) { @@ -156,6 +165,10 @@ public void stop() { } } + public void destroy() { + mScanThread.quit(); + } + protected abstract void stopScan(); protected abstract boolean deferScanIfNeeded(); diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForJellyBeanMr2.java b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForJellyBeanMr2.java index 7183b0107..b38f7281c 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForJellyBeanMr2.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForJellyBeanMr2.java @@ -18,17 +18,9 @@ public CycledLeScannerForJellyBeanMr2(Context context, long scanPeriod, long bet super(context, scanPeriod, betweenScanPeriod, backgroundFlag, cycledLeScanCallback, crashResolver); } - @SuppressWarnings("deprecation") @Override protected void stopScan() { - try { - BluetoothAdapter bluetoothAdapter = getBluetoothAdapter(); - if (bluetoothAdapter != null) { - bluetoothAdapter.stopLeScan(getLeScanCallback()); - } - } catch (Exception e) { - LogManager.e(e, TAG, "Internal Android exception scanning for beacons"); - } + postStopLeScan(); } @Override @@ -54,19 +46,57 @@ public void run() { return false; } - @SuppressWarnings("deprecation") @Override protected void startScan() { - getBluetoothAdapter().startLeScan(getLeScanCallback()); + postStartLeScan(); } - @SuppressWarnings("deprecation") @Override protected void finishScan() { - getBluetoothAdapter().stopLeScan(getLeScanCallback()); + postStopLeScan(); mScanningPaused = true; } + private void postStartLeScan() { + final BluetoothAdapter bluetoothAdapter = getBluetoothAdapter(); + if (bluetoothAdapter == null) { + return; + } + final BluetoothAdapter.LeScanCallback leScanCallback = getLeScanCallback(); + mScanHandler.removeCallbacksAndMessages(null); + mScanHandler.post(new Runnable() { + @Override + public void run() { + try { + //noinspection deprecation + bluetoothAdapter.startLeScan(leScanCallback); + } catch (Exception e) { + LogManager.e(e, TAG, "Internal Android exception in startLeScan()"); + } + } + }); + } + + private void postStopLeScan() { + final BluetoothAdapter bluetoothAdapter = getBluetoothAdapter(); + if (bluetoothAdapter == null) { + return; + } + final BluetoothAdapter.LeScanCallback leScanCallback = getLeScanCallback(); + mScanHandler.removeCallbacksAndMessages(null); + mScanHandler.post(new Runnable() { + @Override + public void run() { + try { + //noinspection deprecation + bluetoothAdapter.stopLeScan(leScanCallback); + } catch (Exception e) { + LogManager.e(e, TAG, "Internal Android exception in stopLeScan()"); + } + } + }); + } + private BluetoothAdapter.LeScanCallback getLeScanCallback() { if (leScanCallback == null) { leScanCallback = diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForLollipop.java b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForLollipop.java index 7e37b9c08..e5ac14d65 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForLollipop.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForLollipop.java @@ -8,7 +8,6 @@ import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import android.content.Context; -import android.os.Build; import android.os.ParcelUuid; import android.os.SystemClock; @@ -39,20 +38,7 @@ public CycledLeScannerForLollipop(Context context, long scanPeriod, long between @Override protected void stopScan() { - try { - if (getScanner() != null) { - try { - getScanner().stopScan((android.bluetooth.le.ScanCallback) getNewLeScanCallback()); - } - catch (NullPointerException npe) { - // Necessary because of https://code.google.com/p/android/issues/detail?id=160503 - LogManager.e(TAG, "Cannot stop scan. Unexpected NPE.", npe); - } - } - } - catch (IllegalStateException e) { - LogManager.w(TAG, "Cannot stop scan. Bluetooth may be turned off."); - } + postStopLeScan(); } /* @@ -181,21 +167,7 @@ protected void startScan() { settings = (new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)).build(); } - try { - if (getScanner() != null) { - ScanCallback callback = getNewLeScanCallback(); - try { - getScanner().startScan(filters, settings, callback); - } - catch (NullPointerException npe) { - // Necessary because of https://code.google.com/p/android/issues/detail?id=160503 - LogManager.w(TAG, "Cannot start scan. Unexpected NPE.", npe); - } - } - } - catch (IllegalStateException e) { - LogManager.w(TAG, "Cannot start scan. Bluetooth may be turned off."); - } + postStartLeScan(filters, settings); } @Override @@ -205,6 +177,50 @@ protected void finishScan() { mScanningPaused = true; } + private void postStartLeScan(final List filters, final ScanSettings settings) { + final BluetoothLeScanner scanner = getScanner(); + if (scanner == null) { + return; + } + final ScanCallback scanCallback = getNewLeScanCallback(); + mScanHandler.removeCallbacksAndMessages(null); + mScanHandler.post(new Runnable() { + @Override + public void run() { + try { + scanner.startScan(filters, settings, scanCallback); + } catch (IllegalStateException e) { + LogManager.w(TAG, "Cannot start scan. Bluetooth may be turned off."); + } catch (NullPointerException npe) { + // Necessary because of https://code.google.com/p/android/issues/detail?id=160503 + LogManager.e(TAG, "Cannot start scan. Unexpected NPE.", npe); + } + } + }); + } + + private void postStopLeScan() { + final BluetoothLeScanner scanner = getScanner(); + if (scanner == null) { + return; + } + final ScanCallback scanCallback = getNewLeScanCallback(); + mScanHandler.removeCallbacksAndMessages(null); + mScanHandler.post(new Runnable() { + @Override + public void run() { + try { + scanner.stopScan(scanCallback); + } catch (IllegalStateException e) { + LogManager.w(TAG, "Cannot stop scan. Bluetooth may be turned off."); + } catch (NullPointerException npe) { + // Necessary because of https://code.google.com/p/android/issues/detail?id=160503 + LogManager.e(TAG, "Cannot stop scan. Unexpected NPE.", npe); + } + } + }); + } + private BluetoothLeScanner getScanner() { if (mScanner == null) { LogManager.d(TAG, "Making new Android L scanner");