每日零点启动ScheduledExecutorService遇阻,求代码校验及替代方案
Let's break down what's wrong with your code first, then fix it, and also share other reliable options for daily midnight tasks on Android.
- Syntax Errors: Your code has unmatched parentheses, misplaced parameters (like the random
1500), and messy brace structure. This would prevent the code from compiling entirely. - Incorrect Delay Calculation: You’re dividing the time until midnight by 50 and using
TimeUnit.MILLISECONDS, which means your initial delay is only 1/50th of the actual needed time—way too short, and potentially negative if it’s already close to midnight. - Wrong Repeat Interval:
20*60*60*24equals 1,728,000 milliseconds (20 days), not 1 day. You need24*60*60*1000for a daily repeat. - Premature Task Cancellation: You’re scheduling a task to cancel the beeper after 36 milliseconds. That means your scheduled task gets canceled almost immediately—no wonder it never runs!
- Date Handling Bug: Using
set(Calendar.DAY_OF_YEAR, ... +1)will fail on the last day of the year. Useadd(Calendar.DAY_OF_YEAR, 1)instead to safely increment the date.
Here’s the corrected code that properly schedules a daily task at midnight:
public class MainActivity extends AppCompatActivity { ScheduledExecutorService scheduler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); scheduler = Executors.newScheduledThreadPool(1); scheduleDailyMidnightTask(); } private void scheduleDailyMidnightTask() { // Define the task to run final Runnable dailyTask = new Runnable() { @Override public void run() { // Run UI updates on the main thread runOnUiThread(() -> { Toast.makeText(getApplicationContext(), "DAILY TASK STARTED", Toast.LENGTH_LONG).show(); }); } }; // Calculate delay until next midnight Calendar midnight = Calendar.getInstance(); midnight.set(Calendar.HOUR_OF_DAY, 0); midnight.set(Calendar.MINUTE, 0); midnight.set(Calendar.SECOND, 0); midnight.set(Calendar.MILLISECOND, 0); // If current time is already past midnight, schedule for tomorrow if (System.currentTimeMillis() >= midnight.getTimeInMillis()) { midnight.add(Calendar.DAY_OF_YEAR, 1); } long initialDelay = midnight.getTimeInMillis() - System.currentTimeMillis(); long dailyInterval = 24 * 60 * 60 * 1000; // 1 day in milliseconds // Schedule the task: initial delay until midnight, then repeat daily scheduler.scheduleAtFixedRate(dailyTask, initialDelay, dailyInterval, TimeUnit.MILLISECONDS); } @Override protected void onDestroy() { super.onDestroy(); // Shutdown the scheduler to avoid memory leaks if (scheduler != null && !scheduler.isShutdown()) { scheduler.shutdown(); } } }
Key Fixes Explained:
- Fixed all syntax errors and cleaned up the code structure.
- Corrected the initial delay calculation to use the full time until midnight (no unnecessary division).
- Set the repeat interval to 1 full day (86,400,000 milliseconds).
- Removed the premature cancellation task that was killing your job instantly.
- Added a check to handle if the current time is already past midnight (so it schedules for the next day instead).
- Added
onDestroy()to shut down the scheduler and prevent memory leaks.
While ScheduledExecutorService works for simple in-app tasks, it stops working if your app is killed by the system (e.g., low memory). For reliable daily tasks, use these Android-specific solutions:
Option 1: AlarmManager
AlarmManager is designed for system-level alarms, so it will wake up your app (if needed) to run the task, even if the app isn’t active.
Example Code:
public class MainActivity extends AppCompatActivity { private static final String TAG = "MidnightAlarm"; private PendingIntent pendingIntent; private AlarmManager alarmManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setupMidnightAlarm(); } private void setupMidnightAlarm() { alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(this, MidnightAlarmReceiver.class); pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); // Calculate next midnight Calendar midnight = Calendar.getInstance(); midnight.set(Calendar.HOUR_OF_DAY, 0); midnight.set(Calendar.MINUTE, 0); midnight.set(Calendar.SECOND, 0); midnight.set(Calendar.MILLISECOND, 0); if (System.currentTimeMillis() >= midnight.getTimeInMillis()) { midnight.add(Calendar.DAY_OF_YEAR, 1); } // Set the alarm to repeat daily (adapt to Android versions) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, midnight.getTimeInMillis(), pendingIntent); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { alarmManager.setExact(AlarmManager.RTC_WAKEUP, midnight.getTimeInMillis(), pendingIntent); } else { alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, midnight.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pendingIntent); } } // BroadcastReceiver to handle the alarm trigger public static class MidnightAlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // Run your task here Toast.makeText(context, "DAILY ALARM TASK RUNNING", Toast.LENGTH_LONG).show(); // Use a Handler if you need to run UI code } } }
Don’t forget to register the receiver in your AndroidManifest.xml:
<receiver android:name=".MainActivity$MidnightAlarmReceiver" />
Option 2: WorkManager (Recommended for Android 8.0+)
WorkManager is part of Android Jetpack and handles background task scheduling with built-in support for system restrictions (like Doze mode). It’s the most reliable way to schedule periodic tasks on modern Android versions.
Step 1: Add Dependency
Add this to your module-level build.gradle:
dependencies { implementation "androidx.work:work-runtime:2.8.1" }
Step 2: Create a Worker Class
public class MidnightWorker extends Worker { public MidnightWorker(@NonNull Context context, @NonNull WorkerParameters params) { super(context, params); } @NonNull @Override public Result doWork() { // Your daily task logic here Log.d("MidnightWorker", "Daily task executed at midnight"); // Use LiveData or a broadcast if you need to update the UI return Result.success(); } }
Step 3: Schedule the Worker in Your Activity
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); scheduleDailyMidnightWork(); } private void scheduleDailyMidnightWork() { // Calculate delay until next midnight Calendar midnight = Calendar.getInstance(); midnight.set(Calendar.HOUR_OF_DAY, 0); midnight.set(Calendar.MINUTE, 0); midnight.set(Calendar.SECOND, 0); midnight.set(Calendar.MILLISECOND, 0); long initialDelay; if (System.currentTimeMillis() >= midnight.getTimeInMillis()) { midnight.add(Calendar.DAY_OF_YEAR, 1); } initialDelay = midnight.getTimeInMillis() - System.currentTimeMillis(); // Define the work request PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder( MidnightWorker.class, 1, TimeUnit.DAYS) .setInitialDelay(initialDelay, TimeUnit.MILLISECONDS) .build(); // Enqueue the work (replace existing task if it exists) WorkManager.getInstance(this).enqueueUniquePeriodicWork( "MidnightDailyTask", ExistingPeriodicWorkPolicy.REPLACE, workRequest); } }
Why WorkManager?
- Automatically adapts to Android version restrictions (e.g., Doze mode, background limits).
- Survives app restarts and system reboots (with additional setup for boot completion).
- Provides built-in retry logic for failed tasks.
内容的提问来源于stack exchange,提问作者bujji

