Search code examples
androidmultithreadingbluetoothhandlerprofiler

Android memory issue when scanning bluetooth with thread and handler


I'm using a thread and handler to achieve a Bluetooth scanning service of certain scanning period and frequency (scan 1s, sleep 1s, scan 1s, etc) by BluetoothLeScanner (actually migrating from BluetoothAdapter). However, as times goes by, I realise it is eating up the memory.

Then I tried to use the profiler to see what happened and I found that there's something looping recursively. I checked my code and could not find the issue. Anyone can help/encountered the same issue?

public class BleDetectionService extends Service {

    private static Handler handler;
    private static final long SCAN_PERIOD = 1100;

    ...

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        ....

        startBLEScanning();
        return super.onStartCommand(intent, flags, startId);

    }

    private void startBLEScanning() {
        mLeDeviceAdapter = new LeDeviceAdapter();
        mScanning = true;

        Toast.makeText(this, "BLE Service sucessfully started", Toast.LENGTH_SHORT).show();

        bleScanningThread = new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare(); 
                while (mScanning) {
                    scanLeDevice(true);
                }
            }
        }, "BLE Scanning Thread");

        bleScanningThread.start();
    }

    private void scanLeDevice(final boolean enable) {
        if (enable) {
            handler = new Handler();
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    bluetoothLeScanner.stopScan(scanCallback);
                }
            }, SCAN_PERIOD);

            // filter
            ScanFilter.Builder builder = new ScanFilter.Builder();
            ScanFilter filter = builder.build();
            ArrayList<ScanFilter> filters = new ArrayList<>();
            filters.add(filter);

            // scansetting
            ScanSettings.Builder settingsBuilder = new ScanSettings.Builder();
            settingsBuilder.setScanMode(ScanSettings.SCAN_MODE_BALANCED);
            settingsBuilder.setReportDelay(0);
            ScanSettings settings = settingsBuilder.build();

            bluetoothLeScanner.startScan(filters, settings, scanCallback);

        } else {
            bluetoothLeScanner.stopScan(scanCallback);
        }
    }
}


enter image description here recorded in an earlier time

c recorded in a later time

EDIT: I have removed the thread and handler as I had mixed up the usage of BluetoothLeScanner with BluetoothAdapter. The scanLeDevice() is now called from main thread as suggested by @greeble31


Solution

  • Consider the following:

    scanLeDevice() is called in a never-ending loop, and always with a parameter of true.

    scanLeDevice() returns almost immediately (1ms or less).

    Each invocation of scanLeDevice() creates a new Handler, and a new anonymous Runnable. The binding of the Runnable to the Handler involves a Message (one for each call to postDelayed()).

    You have omitted the definition of SCAN_PERIOD from your post, but I'm going to take an educated guess, and say it's 60000 (1 minute). That means each new Handler must exist for 1 minute, because it needs to hold a Message for 1 minute, because it is tracking a Runnable which needs be executed when that minute expires.

    In other words: You are calling scanLeDevice() roughly 1000 times per second, and each call creates 3 interrelated objects that must exist for at least 1 minute.

    Your architectural mistake was probably including bleScanningThread. scanLeDevice() can be safely called on the main thread. It need only be called once. It will return immediately, but the scan will still be conducted (by the OS), in the background. ScanCallback will execute when appropriate, even though you have not supplied an extra thread. The scan will cease when your postDelayed() Runnable executes; this is probably what you intended from the beginning.