I have a Service in which I run a Runnable repeatedly using a Handler obtained via a HandlerThread and getLooper(). The Runnable is invoked once when the Service starts by calling post(), and thereafter it re-schedules itself using postDelayed(). I successfully use this pattern to run tasks indefinitely in several different Services in this app, but in one particular Service, it occasionally stops working after hundreds or thousands of invocations -- I no longer get the "checking" log output or see the effects of changed settings. Here's the bare-bones relevant code:
public class SettingsMonitor extends Service {
private final HandlerThread mHandlerThread = new HandlerThread("MonitorHandler");
private Handler mMonitorHandler;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
mHandlerThread.start();
mMonitorHandler = new Handler(mHandlerThread.getLooper());
mMonitorHandler.post(checkSettings);
return START_STICKY;
}
final Runnable checkSettings = new Runnable() {
@Override
public void run() {
mLastTryMillis = SystemClock.elapsedRealtime();
Log.d(TAG, "checking device settings");
if (newSettings()) {
applySettings();
}
mMonitorHandler.removeCallbacksAndMessages(null);
mMonitorHandler.postDelayed(checkSettings, MONITOR_INTERVAL_MILLIS);
}
};
}
When I realized this was sometimes failing, as part of trying to understand what was going on, I added the following check-and-restart method that gets called periodically from the main activity. This keep-alive code runs fine -- I start seeing the "restarting" log output as soon as the Runnable is no longer running -- but the Runnable never actually runs again (I never see the "checking" log output again):
public void checkAndRestartIfNeeded() {
long maxMissedMillis = MAX_MISSED_INTERVALS * MONITOR_INTERVAL_MILLIS;
if (millisSince(mLastTryMillis) > maxMissedMillis) {
Log.d(TAG, "stalled -- restarting");
mMonitorHandler.removeCallbacksAndMessages(null);
mMonitorHandler.post(checkSettings);
}
}
Any ideas why the self-rescheduling behavior would fail in the first place? And any ideas (presumably related?!) why the keep-alive hack fails to get it going again? Is something crashing the Runnable without bringing down the rest of the Service or app but preventing it from running? I actually have an app-level uncaught-exception handler to log unexpected things like this but it doesn't fire.
In case it's not clear, the log output from a run might look like this:
checking device settings
checking device settings
checking device settings
... hundreds or thousands of repetitions, then ...
stalled -- restarting
stalled -- restarting
stalled -- restarting
... forever ...