Android 自我重新安排任务有时会在多次调用后停止 运行

Android self-rescheduling task sometimes stops being run after many invocations

我有一个服务,其中我 运行 一个 Runnable 反复使用通过 HandlerThread 和 getLooper() 获得的处理程序。当服务通过调用 post() 启动时,Runnable 被调用一次,此后它使用 postDelayed() 重新安排自身。我成功地使用此模式在此应用程序的多个不同服务中无限期地 运行 任务,但在一个特定服务中,它偶尔会在数百或数千次调用后停止工作——我不再获得“检查”日志输出或查看更改设置的效果。这是基本的相关代码:

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);
        }
    };

}

当我意识到这有时会失败时,作为试图了解正在发生的事情的一部分,我添加了以下检查并重新启动方法,该方法会定期从主 activity 调用。这个保持活动代码 运行s 很好——一旦 Runnable 不再 运行ning 我就开始看到“重新启动”日志输出——但 Runnable 实际上从未 运行s再次(我再也没有看到“检查”日志输出):

    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);
        }
    }

知道为什么自我重新安排行为首先会失败吗?还有什么想法(可能是相关的?!)为什么 keep-alive hack 无法让它再次运行?是否有什么东西使 Runnable 崩溃而没有关闭服务或应用程序的其余部分但阻止它 运行ning?我实际上有一个应用程序级别的未捕获异常处理程序来记录这样的意外事件,但它没有触发。

如果不清楚,运行 的日志输出可能如下所示:

checking device settings
checking device settings
checking device settings
... hundreds or thousands of repetitions, then ...
stalled -- restarting
stalled -- restarting
stalled -- restarting
... forever ...

我最终能够确定没有异常被抛出,没有线程或进程正在死亡,所以没有什么可以“保持活力”。从某种意义上说,一切都运行良好,但实际上一个进程在 returning “不应该”之前阻塞了几个小时到几天。详情如下:

我们代码中的 newSettings() 方法发出了两个 HTTP 请求:一个发送到我们自己服务器上的 API 以检查对应用程序设置的任何更改(我们的设备是固定部署,无需手动远程管理-on intervention), 和一个到 api.ipify.org 来获取设备的当前 public IP 地址。这两个调用都使用了 URLConnection, and this connection was configured with a connection timeout of 10 seconds and a read timeout of 10 seconds, both set before opening the connection.

大多数时候,连接要么成功,要么超时按预期工作。但是,对于对第 3 方 API 的调用,有时——由于我不明白的原因——尝试从 API 连接和检索内容需要数小时到数天才能完成。如果我等待的时间足够长,调用最终会 return 并且服务中的自调度任务 运行 恢复正常行为,就好像从来没有问题一样。

我不明白为什么超时似乎对这些情况没有影响。我发现的唯一可能的线索是,对于解析为多个 IP 地址的域名,就像 api.ipify.org 一样 at this site and others, URLConnection will attempt to connect to each address 直到它成功,并且每次尝试都将应用指定的连接和读取超时,所以在这种情况下花费的总时间可能是各个超时的几倍。这并不能解释调用如何会阻塞数小时到数天,但这是我迄今为止遇到的唯一提示。

在解决这个问题的过程中,我意识到不需要调用第 3 方 API 来获取设备的 IP 地址,因为它可供我们自己使用 API设备检查其设置。所以我删除了第二个调用并修改了我们的设置 API 以在其响应中包含调用者的 IP 地址;我也取消了 keep-alive 机制,因为它是不必要的。这些更改是我最初发布问题的频率,但它仍然大约每 15,000 次发生一次——对于我们的用例来说仍然不能接受。

一路上的某个地方,我读到一些建议 Scanner I was using in newSettings() for processing the connection's getInputStream() was a less robust/reliable approach than using a BufferedReader on an InputStreamReader 的东西(我忘记了在哪里)。在 newSettings() 中更改为使用该方法后,在 30,000 多次调用中问题出现次数为零,因此我希望这个问题能够得到彻底解决。