Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android WorkManager: how to handle ongoing work, like Bluetooth scanning

With the recent restrictions to background Services and implicit Broadcasts, Android devs are left with JobScheduler, and, at a higher level, WorkManager to schedule background tasks.

The Worker class for WorkManager is simple enough, but I'm a bit confused about the best way to implement ongoing work as opposed to one-off work. For our example, let's consider Bluetooth Low Energy scanning, but the same concern applies to all ongoing, indeterminate work.

Something like this obviously doesn't work:

public class MyWorker extends Worker {

    private BluetoothLeScanner mBluetoothLeScanner;

    @Override
    public Worker.Result doWork() {
        mBluetoothLeScanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();

        // Pretend there's some bluetooth setup here
        // ...

        mBluetoothLeScanner.startScan( .. ,  .. , .. );
        return Result.SUCCESS;
    }

}

Above we start scanning, then fall out of scope immediately, so scanning will not continue.

We can use wait()/notify() to get around this, but it feels very dirty. Something like this...

public class MyWorker extends Worker {

    private BluetoothLeScanner mBluetoothLeScanner;
    private final Object mLock = new Object();
    private Handler mBackgroundHandler;

    private Handler getBackgroundHandler() {
        if (mBackgroundHandler == null) {
            HandlerThread thread = new HandlerThread("background");
            thread.start();
            mBackgroundHandler = new Handler(thread.getLooper());
        }
        return mBackgroundHandler;
    }

    @Override
    public Worker.Result doWork() {
        getBackgroundHandler().post(new Runnable() {
            @Override
            public void run() {
                mBluetoothLeScanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();
                // Pretend there's some bluetooth setup here
                // ...
                mBluetoothLeScanner.startScan( .. ,  .. , mScanCallback);
            }
        });

        getBackgroundHandler().postDelayed(new Runnable() {
            @Override
            public void run() {
                mBluetoothLeScanner.stopScan(mScanCallback);
                synchronized (mLock) {
                    mLock.notify();
                }
            }
        },  60 * 1000); //stop after a minute

        try {
            synchronized (mLock) {
                mLock.wait();
            }
        }
        catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
        }

        return Result.SUCCESS;
    }

    private ScanCallback mScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);
            //We found an advertisement
            mBluetoothLeScanner.stopScan(mScanCallback);
            synchronized (mLock) {
                mLock.notify();
            }
        }

        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            super.onBatchScanResults(results);
        }

        @Override
        public void onScanFailed(int errorCode) {
            super.onScanFailed(errorCode);
            mBluetoothLeScanner.stopScan(mScanCallback);
            synchronized (mLock) {
                mLock.notify();
            }
        }
    };

    @Override
    public void onStopped(boolean cancelled) {
        if (mBackgroundHandler != null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                mBackgroundHandler.getLooper().quitSafely();
            } else {
                mBackgroundHandler.getLooper().quit();
            }
            mBackgroundHandler = null;
        }
    }
}

TLDR: What is the best way to implement ongoing background work in modern (8.1+) Android? It does appear, given the architecture of Worker/WorkManager, that this kind of ongoing background work is being snuffed out by Google. Is a wait()/notify() pattern in a Worker acceptable, or will this workaround get killed by the system?

Any tips would be appreciated.

Edit:

I was hoping to avoid using a foreground Service + ongoing notification. The answer here seemed promising, but it was apparently patched in Android 7.1. On my phone running Android 9, my wireless BLE headphones connect almost immediately when taken out of their case. The headphone vendor is NOT running a foreground service (at least not visibly -- there is no persistent notification) to detect the advertisement. I have no idea how they're doing this so reliably.

like image 626
user2988 Avatar asked Sep 16 '25 07:09

user2988


1 Answers

WorkManager is not appropriate for continuous work - that would be the use case for foreground services.

However, BLE scanning does not require your app to be continuously running on API 26+ devices with the introduction of the BluetoothLeScanner.startScan(List<ScanFilter>, ScanSettings, PendingIntent) method, which allows you to register a PendingIntent as a callback, starting your app only when scan results are available.

For previous versions of Android, you would need a constantly running service to maintain the active scanning.

like image 164
ianhanniballake Avatar answered Sep 17 '25 20:09

ianhanniballake



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!