Android:如何在idle_maintenance模式下执行发送追踪信息的任务?
Hey there! Let's break down how to get your tracking tasks running smoothly during the idle_maintenance window in Doze mode, since it sounds like your app's getting blocked right now.
First, a quick recap: When Android enters idle (Doze) mode, it restricts most app activity to save battery. The idle_maintenance window is a short, periodic interval where the system lifts these restrictions to let apps complete critical tasks. If your app is blocked here, it’s likely because you’re using traditional background task APIs that don’t account for Doze’s constraints.
Recommended Libraries
The most reliable tools for this are built to work with Doze out of the box:
- WorkManager: The official Jetpack library for background tasks. It automatically handles Doze, App Standby, and other system restrictions—this is the easiest, most recommended choice.
- AlarmManager: Use this if you need precise, one-time or periodic scheduling (but WorkManager is preferred for most cases, as it abstracts away low-level details).
Example 1: Using WorkManager (Recommended)
WorkManager lets you define tasks with constraints that ensure they run only during idle_maintenance windows.
Step 1: Add Dependencies
Add this to your app-level build.gradle (module level):
dependencies { def work_version = "2.8.1" implementation "androidx.work:work-runtime-ktx:$work_version" }
Step 2: Create a Worker Class
This class handles your tracking task. We’ll use CoroutineWorker to run the task in a background coroutine (no manual thread management needed):
import android.content.Context import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext class TrackingWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) { override suspend fun doWork(): Result { return try { // Run tracking logic on a background dispatcher to avoid blocking withContext(Dispatchers.IO) { sendTrackingData() } Result.success() // Mark task as completed } catch (e: Exception) { // Retry if the task fails (adjust retry policy as needed) Result.retry() } } private fun sendTrackingData() { // Replace this with your actual tracking logic (e.g., API call) println("Sending tracking data during idle_maintenance window...") // Example: val response = apiClient.postTrackingEvent(trackingPayload) // if (!response.isSuccessful) throw Exception("Failed to send tracking data") } }
Step 3: Schedule the Task with Constraints
Set up constraints to ensure the task runs only when the device is in an idle state (including idle_maintenance):
import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.work.Constraints import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkManager import java.util.concurrent.TimeUnit class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Define constraints: Only run when device is idle val idleConstraints = Constraints.Builder() .setRequiresDeviceIdle(true) // Critical: Triggers during idle_maintenance .setRequiresBatteryNotLow(true) // Optional: Avoid running on low battery .build() // Create a periodic task (e.g., run every 12 hours) val trackingRequest = PeriodicWorkRequestBuilder<TrackingWorker>(12, TimeUnit.HOURS) .setConstraints(idleConstraints) .build() // Enqueue the task with WorkManager WorkManager.getInstance(this).enqueue(trackingRequest) } }
Example 2: Using AlarmManager (For Precise Scheduling)
If you need a task to run at an exact time (even in Doze), use setExactAndAllowWhileIdle() (available on Android 6.0+):
Step 1: Create a Broadcast Receiver
This receiver triggers your tracking task:
import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import kotlin.concurrent.thread class TrackingAlarmReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { // Run the task in a background thread (onReceive runs on the main thread!) thread { sendTrackingData(context) } } private fun sendTrackingData(context: Context?) { // Replace with your tracking logic println("Sending tracking data via AlarmManager during idle_maintenance...") } }
Step 2: Register the Receiver
Add this to your AndroidManifest.xml:
<receiver android:name=".TrackingAlarmReceiver" />
Step 3: Schedule the Alarm
Set up the alarm to run even in Doze mode:
import android.app.AlarmManager import android.app.PendingIntent import android.content.Context import android.content.Intent import android.os.Build import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import java.util.concurrent.TimeUnit class MainActivity : AppCompatActivity() { private val ALARM_REQUEST_CODE = 1001 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager val intent = Intent(this, TrackingAlarmReceiver::class.java) val pendingIntent = PendingIntent.getBroadcast( this, ALARM_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT or if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0 ) // Schedule the alarm to run in 12 hours, even in Doze val triggerTime = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(12) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent ) } else { // Compatibility for older Android versions alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent) } } }
Key Notes to Avoid Blocking
- Never run long tasks on the main thread: Use coroutines (WorkManager) or background threads (AlarmReceiver) to prevent ANRs.
- Keep tasks short: The
idle_maintenancewindow is brief (usually a few minutes), so focus on critical operations like sending tracking data, not heavy processing. - Test Doze mode: Use these ADB commands to simulate Doze:
adb shell dumpsys deviceidle force-idle(force device into Doze)adb shell dumpsys deviceidle step(trigger the nextidle_maintenancewindow)
内容的提问来源于stack exchange,提问作者Adan Condori C.

