You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android Oreo无前台服务永久维持BLE连接及重启恢复方案咨询

Hey there, let's tackle this problem step by step—since Android Oreo tightened up background restrictions, keeping a BLE connection alive without a foreground service and handling app recovery/auto-start requires some clever workarounds that play nice with the system rules. Here's a comprehensive solution tailored to your needs:

Android Oreo: BLE Permanent Connection, App Recovery & Auto-Start (No Foreground Service)

1. BLE Connection Persistence & Auto-Reconnect

The key here is to leverage system-managed background tasks instead of traditional background services, which get throttled or killed in Oreo. We'll use WorkManager (Google's recommended tool for deferrable background tasks) to handle reconnection attempts when the BLE link drops.

Core Logic:

  • Listen for connection state changes via BluetoothGattCallback
  • When disconnected, schedule a reconnection task with exponential backoff (to avoid spamming the system and draining battery)
  • Store target device details (MAC address, service UUIDs) in SharedPreferences so we can retrieve them after app restarts

Code Snippets:

Reconnection Worker

class BleReconnectWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
    override suspend fun doWork(): Result {
        val deviceMac = inputData.getString("DEVICE_MAC") ?: return Result.failure()
        val bluetoothManager = applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        
        return try {
            val targetDevice = bluetoothManager.adapter.getRemoteDevice(deviceMac)
            // Connect without autoConnect to prioritize immediate reconnection
            targetDevice.connectGatt(applicationContext, false, bleGattCallback)
            Result.success()
        } catch (e: Exception) {
            // Retry with exponential backoff (starts at 3s, doubles each time up to 30s)
            Result.retry()
        }
    }
}

Gatt Callback for Disconnection Handling

private val bleGattCallback = object : BluetoothGattCallback() {
    override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
        super.onConnectionStateChange(gatt, status, newState)
        val context = gatt?.context ?: return
        
        if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            val targetMac = context.getSharedPreferences("BlePrefs", Context.MODE_PRIVATE)
                .getString("TARGET_DEVICE_MAC", null) ?: return
            
            // Schedule reconnection task
            val reconnectRequest = OneTimeWorkRequestBuilder<BleReconnectWorker>()
                .setInputData(workDataOf("DEVICE_MAC" to targetMac))
                .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 3000, TimeUnit.MILLISECONDS)
                .build()
            WorkManager.getInstance(context).enqueue(reconnectRequest)
        }
    }
}

2. App Recovery After System Kill

Oreo blocks direct background service starts after the app is killed, so we rely on WorkManager's persistent tasks to wake the app back up. WorkManager stores tasks in the system's database, so even if your app is terminated, the system will restart it to execute the scheduled tasks.

Additional Tips:

  • Enable Auto-Start Permission for your app: Most Chinese OEMs (Xiaomi, Huawei, Oppo) have custom background restrictions—you'll need to guide users to manually enable auto-start in device settings, otherwise WorkManager tasks might not trigger.
  • Avoid long-running background operations: All tasks should be short-lived (under 10 minutes) to comply with Oreo's background limits.

3. Auto-Start After Device Reboot

To launch your app automatically when the device boots up, use a broadcast receiver that listens for system boot completions.

Step 1: Add Permissions & Receiver to Manifest

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

<receiver android:name=".BootCompletedReceiver"
          android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
        <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" /> <!-- For early boot trigger -->
    </intent-filter>
</receiver>

Step 2: Broadcast Receiver Implementation

class BootCompletedReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        if (intent?.action !in listOf(Intent.ACTION_BOOT_COMPLETED, Intent.ACTION_LOCKED_BOOT_COMPLETED)) {
            return
        }
        context ?: return
        
        // Start periodic BLE scan and reconnect to saved device
        startBleBackgroundTasks(context)
    }

    private fun startBleBackgroundTasks(context: Context) {
        val prefs = context.getSharedPreferences("BlePrefs", Context.MODE_PRIVATE)
        val savedMac = prefs.getString("TARGET_DEVICE_MAC", null)
        
        // Schedule periodic BLE scan (every 15 minutes, 30s duration)
        val scanRequest = PeriodicWorkRequestBuilder<BleScanWorker>(15, TimeUnit.MINUTES)
            .setConstraints(Constraints.Builder()
                .setRequiresBle(true)
                .setRequiredNetworkType(NetworkType.NOT_REQUIRED)
                .build())
            .build()
        WorkManager.getInstance(context).enqueue(scanRequest)
        
        // Reconnect to saved device if exists
        savedMac?.let {
            val reconnectRequest = OneTimeWorkRequestBuilder<BleReconnectWorker>()
                .setInputData(workDataOf("DEVICE_MAC" to it))
                .build()
            WorkManager.getInstance(context).enqueue(reconnectRequest)
        }
    }
}

4. Continuous BLE Scanning (Compliant with Oreo Rules)

Oreo limits background BLE scanning to short bursts, so we use PeriodicWorkRequest to run scans every 15 minutes (adjustable) for 30 seconds each. This stays within system limits and minimizes battery drain.

Scan Worker Implementation

class BleScanWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
    override suspend fun doWork(): Result {
        val bluetoothManager = applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        val scanner = bluetoothManager.adapter.bluetoothLeScanner ?: return Result.failure()
        
        // Filter scans to only target your BLE device's service UUID
        val scanFilters = listOf(ScanFilter.Builder()
            .setServiceUuid(ParcelUuid(UUID.fromString("YOUR_TARGET_SERVICE_UUID")))
            .build())
        
        val scanSettings = ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER) // Opt for low power to save battery
            .build()
        
        val scanCallback = object : ScanCallback() {
            override fun onScanResult(callbackType: Int, result: ScanResult?) {
                super.onScanResult(callbackType, result)
                result?.device?.let { device ->
                    // Stop scan once target device is found
                    scanner.stopScan(this)
                    // Connect to the device
                    device.connectGatt(applicationContext, false, bleGattCallback)
                    // Save device MAC for future reconnections
                    applicationContext.getSharedPreferences("BlePrefs", Context.MODE_PRIVATE)
                        .edit()
                        .putString("TARGET_DEVICE_MAC", device.address)
                        .apply()
                }
            }
        }
        
        scanner.startScan(scanFilters, scanSettings, scanCallback)
        // Run scan for 30 seconds then stop
        delay(30000)
        scanner.stopScan(scanCallback)
        
        return Result.success()
    }
}

5. Critical Notes for Success

  • Permissions: Don't forget to request runtime permissions for ACCESS_FINE_LOCATION (required for BLE scanning on Android 6.0+), plus BLUETOOTH_SCAN and BLUETOOTH_CONNECT for Android 12+.
  • OEM Adaptation: Chinese manufacturers have strict background limits—guide users to enable "Auto-Start" and "No Battery Optimization" for your app.
  • Battery Efficiency: Avoid aggressive scanning/reconnection intervals. Use exponential backoff for reconnections and low-power scan mode to prevent your app from being flagged as battery-hungry.

内容的提问来源于stack exchange,提问作者Artem Viter

火山引擎 最新活动