Battery Optimization for Background Tasks
Whistl provides continuous protection while minimising battery impact. This technical guide explains iOS Background App Refresh, Android WorkManager, adaptive task scheduling, and how Whistl balances real-time protection with battery efficiency.
The Battery Challenge
Continuous protection requires background processing, which consumes battery:
- Location monitoring: GPS for venue geofencing
- Transaction sync: Background fetch for new transactions
- ML inference: Periodic impulse prediction
- Network activity: DNS filtering, API calls
- Biometric sync: HealthKit, Oura data updates
Whistl uses sophisticated optimization to keep battery impact under 7% per day.
iOS Background Execution
iOS provides multiple background execution modes:
Background Modes Used
| Mode | Purpose | Budget |
|---|---|---|
| Background Fetch | Transaction sync, model updates | ~30 seconds, 2-4x/day |
| Location Updates | Venue geofencing | Continuous (region monitoring) |
| Remote Notifications | Server-initiated sync | ~30 seconds per notification |
| Background Processing | Critical tasks (iOS 13+) | Variable, system-determined |
Background Fetch Implementation
import UIKit
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Configure background fetch
UIApplication.shared.setMinimumBackgroundFetchInterval(
UIApplication.backgroundFetchIntervalMinimum
)
return true
}
func application(
_ application: UIApplication,
performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
let startTime = Date()
Task {
do {
// Perform background tasks
try await syncTransactions()
try await syncBiometricData()
try await checkForModelUpdates()
let duration = Date().timeIntervalSince(startTime)
let result: UIBackgroundFetchResult = duration < 25 ? .newData : .noData
completionHandler(result)
} catch {
completionHandler(.failed)
}
}
}
}
Background Task Scheduling (iOS 13+)
import BackgroundTasks
class BackgroundTaskScheduler {
static let shared = BackgroundTaskScheduler()
func scheduleTasks() {
// Schedule background refresh
let refreshRequest = BGAppRefreshTaskRequest(identifier: "com.whistl.refresh")
refreshRequest.earliestBeginDate = Date().addingTimeInterval(3600)
try? BGTaskScheduler.shared.submit(refreshRequest)
// Schedule background processing
let processingRequest = BGProcessingTaskRequest(identifier: "com.whistl.processing")
processingRequest.earliestBeginDate = Date().addingTimeInterval(3600 * 4)
processingRequest.requiresExternalPower = false
processingRequest.requiresNetworkConnectivity = true
try? BGTaskScheduler.shared.submit(processingRequest)
}
func handleRefresh(task: BGAppRefreshTask) {
task.expirationHandler = {
task.setTaskCompleted(success: false)
}
Task {
do {
try await performRefreshTasks()
task.setTaskCompleted(success: true)
} catch {
task.setTaskCompleted(success: false)
}
scheduleTasks() // Re-schedule
}
}
}
Android Background Execution
Android provides WorkManager for reliable background tasks:
WorkManager Implementation
import androidx.work.*
class BackgroundTaskScheduler(private val context: Context) {
fun scheduleTasks() {
// Transaction sync - periodic
val transactionWork = PeriodicWorkRequestBuilder(
4, TimeUnit.HOURS
)
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
WorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
)
.build()
// Model update - daily
val modelWork = PeriodicWorkRequestBuilder(
1, TimeUnit.DAYS
)
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED) // WiFi only
.setRequiresCharging(true)
.build()
)
.build()
// Biometric sync - every 6 hours
val biometricWork = PeriodicWorkRequestBuilder(
6, TimeUnit.HOURS
)
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.build()
WorkManager.getInstance(context).enqueue(
transactionWork, modelWork, biometricWork
)
}
}
Worker Implementation
class TransactionSyncWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
withTimeout(25 * 60 * 1000) { // 25 minute timeout
val syncManager = SyncManager(applicationContext)
await syncManager.syncTransactions()
Result.success()
}
} catch (e: TimeoutCancellationException) {
Result.retry()
} catch (e: Exception) {
if (runAttemptCount >= 3) {
Result.failure()
} else {
Result.retry()
}
}
}
}
Adaptive Task Scheduling
Whistl adjusts background activity based on risk level:
Risk-Based Scheduling
class AdaptiveScheduler {
func calculateSyncInterval(riskLevel: RiskLevel) -> TimeInterval {
switch riskLevel {
case .low:
return 300 // 5 minutes
case .elevated:
return 60 // 1 minute
case .high:
return 30 // 30 seconds
case .critical:
return 10 // 10 seconds
}
}
func calculateLocationUpdateInterval(riskLevel: RiskLevel) -> TimeInterval {
switch riskLevel {
case .low:
return 300 // 5 minutes (significant changes)
case .elevated:
return 60 // 1 minute
case .high:
return 30 // 30 seconds
case .critical:
return 10 // 10 seconds (continuous)
}
}
}
Sleep Mode Optimization
class SleepModeOptimizer {
func isSleepTime() -> Bool {
let hour = Calendar.current.component(.hour, from: Date())
return hour >= 23 || hour < 6
}
func isStationary() -> Bool {
// Check if device hasn't moved significantly
return locationManager.distanceFilter > 1000
}
func getSleepInterval() -> TimeInterval {
if isSleepTime() && isStationary() {
return 1800 // 30 minutes during sleep
}
return 300 // 5 minutes normal
}
}
Location Optimization
Geofencing is optimized for battery efficiency:
Region Monitoring vs. Continuous GPS
| Method | Battery/Hour | Accuracy | Use Case |
|---|---|---|---|
| Region Monitoring | ~0.5% | 100-200m | Normal protection |
| Significant Changes | ~0.1% | 500m | Sleep mode |
| Continuous GPS | ~15% | 5-10m | Critical risk only |
| Adaptive (Whistl) | ~2-3% | 50m | Balanced approach |
Geofence Management
class GeofenceManager {
private let maxMonitoredRegions = 20 // iOS limit
func updateMonitoredRegions(userLocation: CLLocation) {
// Get nearby venues
let nearbyVenues = venueDatabase.getVenues(
within: 5000, // 5km radius
of: userLocation
)
// Prioritize by risk
let sortedVenues = nearbyVenues.sorted {
$0.riskScore > $1.riskScore
}
// Monitor top 20
let venuesToMonitor = sortedVenues.prefix(maxMonitoredRegions)
// Update regions
for venue in venuesToMonitor {
startMonitoring(venue)
}
}
}
Network Optimization
Network activity is batched and deferred:
Request Batching
class NetworkBatcher {
private var pendingRequests: [APIRequest] = []
private let maxBatchSize = 10
private let maxWaitTime: TimeInterval = 30
func queueRequest(_ request: APIRequest) {
pendingRequests.append(request)
if pendingRequests.count >= maxBatchSize {
flush()
} else if pendingRequests.count == 1 {
// First request - schedule flush
DispatchQueue.main.asyncAfter(deadline: .now() + maxWaitTime) {
self.flush()
}
}
}
func flush() {
guard !pendingRequests.isEmpty else { return }
let batch = pendingRequests
pendingRequests = []
Task {
try? await apiClient.sendBatch(batch)
}
}
}
WiFi-Preferred Sync
class SyncScheduler {
func shouldSyncNow(dataType: DataType) -> Bool {
let networkStatus = NetworkMonitor.shared.status
switch dataType {
case .transactions:
// Large data - prefer WiFi
return networkStatus == .wifi
case .settings:
// Small data - any network
return networkStatus != .offline
case .modelUpdate:
// Very large - WiFi + charging
return networkStatus == .wifi && BatteryMonitor.shared.isCharging
}
}
}
Battery Impact Measurements
Whistl tracks and reports battery usage:
Battery Breakdown
| Component | Battery/Day | Percentage |
|---|---|---|
| Location Services | 2.5% | 36% |
| Network Activity | 1.8% | 26% |
| ML Inference | 1.2% | 17% |
| Background Fetch | 0.8% | 11% |
| UI Rendering | 0.7% | 10% |
| Total | 7.0% | 100% |
Battery Usage Reporting
class BatteryReporter {
func getBatteryUsage() -> BatteryReport {
return BatteryReport(
totalUsage: getTotalBatteryUsage(),
breakdown: [
.location: getLocationUsage(),
.network: getNetworkUsage(),
.ml: getMLUsage(),
.background: getBackgroundUsage()
],
averagePerDay: getAverageDailyUsage(),
trend: getUsageTrend()
)
}
// Users can view this in Settings → Battery Usage
}
Power Saving Mode
Users can enable power saving mode for reduced battery impact:
Power Saving Features
- Extended sync intervals: 15 minutes instead of 5
- Reduced location accuracy: Region monitoring only
- Deferred ML inference: Every 15 minutes instead of 5
- WiFi-only sync: No cellular data for background sync
- Suspended animations: Reduced UI effects
Power Saving Impact
| Metric | Normal Mode | Power Saving | Reduction |
|---|---|---|---|
| Battery/Day | 7.0% | 3.5% | 50% |
| Location Updates | Every 5 min | Every 15 min | 67% |
| ML Inference | Every 5 min | Every 15 min | 67% |
| Sync Frequency | Every 4 hours | Every 8 hours | 50% |
User Testimonials
"I was worried about battery drain but Whistl uses maybe 5-7% per day. Totally worth it for the protection." — Emma, 26
"Power saving mode gets me through long days when I can't charge. Still protected, just less frequent updates." — Marcus, 28
"Battery usage is way better than other protection apps I've tried. Whistl clearly optimized this well." — Jake, 31
Conclusion
Whistl balances continuous protection with battery efficiency through adaptive scheduling, intelligent location management, and network optimization. At 7% daily battery impact (3.5% in power saving mode), protection doesn't come at the cost of all-day battery life.
Background tasks are scheduled intelligently—more frequent during high-risk periods, reduced during sleep—to maximise protection while minimising battery drain.
Get Efficient Protection
Whistl provides continuous protection with minimal battery impact. Download free and experience efficient background monitoring.
Download Whistl FreeRelated: GPS Geofencing | On-Device ML | Offline-First Architecture