安卓 Thread.sleep 有时等待时间过长

3

编辑:

这并不是关于精度问题的讨论。从下面的代码和日志可以看出,我请求睡眠1秒钟,但结果却接近200秒,有时候甚至会跳到600秒,这不能是精度问题。


之前我在使用 handlerthread,有时候提交给 handler 的作业就是无法按时启动,为了获取更多细节,我将它改成了基本的 Thread,结果发现 Thread.sleep() 是问题所在,但我不知道该如何解决,可能的原因是什么呢?

hGpsThread = new Thread(mGpsWorker);
hGpsThread.start();

private final Runnable mGpsWorker = new Runnable() {
    @Override
    public void run() {
        long lastGpsRequestTime = 0;
        l.Write("GPS thread started.");
        while (isRunning) {
            l.Write("GPS thread loop start.");
            try {
                long currentTimeMillis = System.currentTimeMillis();
                if (currentTimeMillis >= lastGpsRequestTime + gpsUpdateInterval) {
                    l.Write("Requesting location update");
                    gpslib.getLocation();
                    lastGpsRequestTime = currentTimeMillis;
                }
                l.Write("GPS thread before sleep");
                Thread.sleep(1000);
                l.Write("GPS thread after sleep");
            } catch (InterruptedException e) {
            }
            l.Write("GPS thread loop end.");
        }
        l.Write("GPS thread ended.");
    }
};

注意:getLocation()调用requestLocationUpdates,使用来自另一个线程的looper,因此位置更新不应影响此线程,据我所知。

getLocation()还创建了一个计时器并安排了超时,这可能是问题吗?据我所知,这不应该是问题。

这是日志记录:(这仅偶尔发生,例如机会接近0.5%)

Wed Jul 10 11:45:46 AEST 2013 GPS thread loop start.
Wed Jul 10 11:45:46 AEST 2013 GPS thread before sleep
Wed Jul 10 11:49:04 AEST 2013 GPS thread after sleep
Wed Jul 10 11:49:04 AEST 2013 GPS thread loop end.

感谢

测试环境为:HTC Aria,Android 2.2 并且看起来只有在电池模式下运行时才会发生,但我的应用程序在充电状态下不会有任何不同。


当你注释掉 getLocation() 时,这种情况是否会发生?看来另一个线程在这个进入 sleep 后没有放弃 CPU。 - Aert
@Aert,谢谢,我现在正在监视CPU使用情况,如果我发现了什么,我会更新问题的,谢谢。 - agou
当 Android 显示屏关闭时,您是通过电源按钮或显示超时来执行此操作的吗? - Royston Pinto
@RoystonPinto 是的,上面的代码来自一个前台服务类。实际上,它似乎只发生在显示关闭时!这也可以解释为什么连接到电源时不会发生,因为我将显示设置为始终开启状态。那么有什么区别吗? - agou
1
是的,当显示屏关闭时,处理程序和线程会被暂停,直到它们被其他应用程序中的某个唤醒锁或显示屏打开唤醒。因此,您观察到的随机行为是正确的。如果您需要在显示屏关闭时获得正确的行为,请切换到AlarmManager,因为即使显示屏关闭,它也可以正常工作。 - Royston Pinto
显示剩余2条评论
3个回答

9
基本上,只要屏幕是开着的(意味着Android不处于深度休眠状态/屏幕关闭状态),Thread.sleep()Handlers 就可以正常工作。
当屏幕关闭时,默认的Android行为是暂停 Thread.sleep()Handlers(),直到屏幕再次打开或者被某个应用程序抓取唤醒锁定。因此,如果您的应用程序在整个过程中都保持屏幕开启状态,那么它将完美地运行,但是当它关闭时,它将表现出错乱的行为。
最好的解决方法是切换到 AlarmManager,因为当闹钟触发时,onRecieve() 默认会抓取唤醒锁定,导致Android唤醒并执行。

谢谢。我有几个问题:这只适用于由UI线程直接创建的线程,还是也适用于由设置为前台的Service/IntentService创建的线程?这是否也适用于wait(timeout)?建议的AlarmManager是否必须点亮屏幕?(我需要每10秒运行一次操作。)您能提供有关此问题的文档链接吗?再次感谢。 - GozzoMan

0

sleep()文档警告说它不是精确的:

精度不能得到保证 - 线程可能会睡得比要求多或少。

sleep()缺乏精度相关的问题有很多,比如这个这个和其他一些。在Google上搜索"java thread sleep precision"或"android thread sleep precision"。


谢谢回复,但这不是关于精度的问题,我请求睡眠1秒钟,结果几乎是200秒,有时甚至会跳到600秒,这不可能是一个精度问题... - agou
我同意你提供的数字相差很大,但API的描述并没有做出任何承诺,特别是其中说“线程可能会比请求的时间更长或更短”。如果你找到了更有用的关于如何精确休眠的信息,请发布为答案并接受它。这是公平竞争的。 - Christian Garbin

-1

我也遇到过这个问题,不是因为它不够精确,而是线程等待了30分钟而不是10秒钟 - 对于一个运行数周并需要定期执行请求的应用程序来说,这不是解决方案。因此,我做了BetterSleeper :-)

import java.sql.Timestamp;

public class BetterSleeper {

    public static void sleepSeconds(int seconds_to_wait) {
        System.out.println("START WAIT FOR "+seconds_to_wait+" SECONDS");
        BetterSleeper.sleepMillis(seconds_to_wait*1000);
        System.out.println("END WAIT");
    }

    public static void sleepMillis(int milliseconds_to_wait) {
        System.out.println("START WAIT FOR "+milliseconds_to_wait+" MILLISECONDS");
        Timestamp timestamp = new Timestamp(System.currentTimeMillis());
        long start_milliseconds = timestamp.getTime();
        long end_milliseconds = start_milliseconds + milliseconds_to_wait;
        while (true) {
            Timestamp endtime = new Timestamp(System.currentTimeMillis());
            if ((endtime.getTime()) >= end_milliseconds) {
                break;
            }
        }
        System.out.println("END WAIT");
    }
}

如果考虑到处理时间实际上几乎没有影响,因为它在开始时就被记住了,并且应该在一开始就停止,这实际上证明更准确。

另一个优点是您不会遇到像Thread.sleep那样的分派睡眠线程的所有缺点,其中后台线程在终止主线程时被放弃。


注意 我与Android开发无关 - 我的问题出现在运行Windows 10的Windows PC上。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接