如何借助WorkManager解决AltBeacon应用被杀后扫描中断问题
Hey there! Let’s walk through how to get your BeaconWorker playing nicely with the AltBeacon library—especially for those tricky post-app-kill scenarios. I’ve tinkered with similar setups before, so here’s a step-by-step breakdown of what you need to adjust and implement:
1. Initialize AltBeacon Properly in the Worker
Since each Worker run is isolated, you’ll need to initialize BeaconManager every time doWork() executes. Make sure to pass the Worker’s application context and configure settings like scan periods and beacon layouts to match your use case.
2. Implement a Suspended Scan with Result Collection
Your makeScan() function needs to perform a scan and wait for results in a coroutine-friendly way. We’ll use CompletableDeferred to suspend the coroutine until the scan finishes or times out, so we don’t block the Worker thread unnecessarily.
3. Handle AltBeacon Callbacks
You’ll need a RangeNotifier to collect detected beacons and a BeaconConsumer to bind the manager to the Worker’s context. Unlike Activities/Fragments, Workers don’t have a built-in lifecycle, so we’ll bind and unbind manually within the coroutine.
4. Reschedule the Worker Wisely
When rescheduling, pick a reasonable interval—keep Android 8+ background restrictions in mind! Avoid intervals shorter than 15 minutes if you want consistent behavior without triggering system throttling. Also, check if the Worker is still active before rescheduling to avoid duplicate jobs.
Modified BeaconWorker Code
import android.content.Context import android.content.Intent import android.content.ServiceConnection import org.altbeacon.beacon.* import androidx.work.* import kotlinx.coroutines.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import java.util.concurrent.TimeUnit class BeaconWorker(appCtx: Context, workerParameters: WorkerParameters) : CoroutineWorker(appCtx, workerParameters) { override val coroutineContext: CoroutineDispatcher = Dispatchers.Default // Mutex to prevent concurrent scan runs private val scanMutex = Mutex() override suspend fun doWork() = supervisorScope { // Reschedule first to ensure scanning continuity (if needed) if (isActive) { enqueNextWork() } val scanResult = runCatching { scanMutex.withLock { makeScan() } }.getOrElse { // Handle scan errors (e.g., missing permissions, Bluetooth disabled) emptyList() } // Process your scanned beacons here (e.g., send to server, save to local storage) scanResult.forEach { beacon -> println("Detected beacon: ID1=${beacon.id1}, ID2=${beacon.id2}, RSSI=${beacon.rssi}") } Result.success() } private suspend fun makeScan(): List<Beacon> { val deferred = CompletableDeferred<List<Beacon>>() val beaconManager = BeaconManager.getInstanceForApplication(applicationContext) // Configure your beacon layout(s) — update this to match your beacon type beaconManager.beaconParsers.add(BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24")) // Adjust scan periods to balance power usage and accuracy beaconManager.setForegroundScanPeriod(1000L) // Scan for 1 second per cycle beaconManager.setForegroundBetweenScanPeriod(0L) // No delay during active scan beaconManager.setBackgroundScanPeriod(1000L) beaconManager.setBackgroundBetweenScanPeriod(0L) val rangeNotifier = RangeNotifier { beacons, _ -> // Complete the deferred once we get scan results if (!deferred.isCompleted) { deferred.complete(beacons) } } val beaconConsumer = object : BeaconConsumer { override fun getApplicationContext(): Context = applicationContext override fun unbindService(p0: Runnable?) { beaconManager.unbindService(this) } override fun bindService(p0: Intent?, p1: ServiceConnection?, p2: Int): Boolean = false override fun onBeaconServiceConnect() { try { // Scan all beacons with a wildcard region beaconManager.startRangingBeaconsInRegion(Region("scan-region", null, null, null)) } catch (e: RemoteException) { if (!deferred.isCompleted) { deferred.completeExceptionally(e) } } } } beaconManager.addRangeNotifier(rangeNotifier) beaconManager.bind(beaconConsumer) // Wait for scan results or timeout after 5 seconds (adjust as needed) return withTimeoutOrNull(5000L) { deferred.await() } ?: emptyList().also { // Clean up resources if we timed out beaconManager.removeRangeNotifier(rangeNotifier) beaconManager.unbind(beaconConsumer) } } private fun enqueNextWork() { val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.NOT_REQUIRED) .setRequiresBatteryNotLow(false) // Tweak based on your power needs .build() val workRequest = OneTimeWorkRequestBuilder<BeaconWorker>() .setConstraints(constraints) .setInitialDelay(15, TimeUnit.MINUTES) // Comply with Android background limits .build() WorkManager.getInstance(applicationContext).enqueue(workRequest) } }
Key Notes to Remember
- Permissions: Ensure your app has the necessary permissions:
ACCESS_FINE_LOCATION,BLUETOOTH_SCAN,BLUETOOTH_CONNECT(varies by Android version). For Android 12+, you’ll also needACCESS_BACKGROUND_LOCATIONfor reliable background scanning. - Battery Optimization: Disable battery optimization for your app if possible—otherwise, Android may pause Worker execution when the device is idle.
- Scan Tuning: Adjust scan periods to balance battery life and scan frequency. Shorter scans use more power but deliver results faster.
- Targeted Scanning: If you only care about specific beacons, replace the wildcard region in
startRangingBeaconsInRegionwith IDs matching your beacons.
内容的提问来源于stack exchange,提问作者Draptol

