【问题标题】:How to achieve exact(accuracy to seconds) timing on Android service如何在 Android 服务上实现精确(精确到秒)计时
【发布时间】:2015-10-16 00:37:05
【问题描述】:

我正在制作一个按时间间隔执行操作的应用程序。我可以在两次更新之间等待的绝对最长时间是 30 秒,介于 0 和 0 之间的任何时间都是可以接受的,但我更喜欢 15 秒。然而,它并不像听起来那么容易。我尝试了4种方法,由于各种原因都无法接受。

请记住,这些问题发生在 Service 中,当此代码在 Activity 中运行时,它运行良好。我还注意到,当手机插入我的计算机进行调试时,解决方案 3 运行良好,直到我将其拔出。我检查了我的电池设置,没有电池省电模式或其他类似的东西,这可能是这个原因。

1.IntentService 做一些事情,然后在 15 秒内重新安排自己。不幸的是,当警报准确时,AlarmManager.setExact() 完全不可靠,正如我在这篇风滚草中所描述的那样:Android Alarm not triggering at exact time

2.具有Thread 的前台服务。在那个线程中,我做我的事情,然后sleep() 15 秒。结果证明这个方法比以前更糟糕,线程被唤醒到超过正确时间 5 分钟。

3.然后我尝试使用Timer(如geokavel 建议的那样)并使用scheduleAtFixedRateschedule 安排工作,但工作完成时间晚了约15-45 秒,使间隔改为约1 分钟15 秒。

4.我想到的最后一种实现方法是不要从上面的前台服务中的Thread 中睡觉。相反,我比较时间:

public void run(){
    nextTime = System.currentTimeMillis() + sleep;
    while (true){
        if (System.currentTimeMillis() >= nextTime) {
            nextTime = System.currentTimeMillis() + sleep;
            //do stuff
        }
    }
}

除了一个主要缺点之外,这种方法的工作原理很像一个魅力 - 它一直使用 20-25% 的 CPU。

所以我的问题是,有没有办法使上述解决方案正常工作(没有不可接受的缺点)?如果没有,有没有更好的方法我错过了?如果需要,请随时询问更多详细信息。

编辑:请求的 run() 代码:

public void run(){
    try {
        if (Thread.currentThread().isInterrupted()){
            throw new InterruptedException();
        }
        NetworkInfo info = cm.getActiveNetworkInfo();
        if (info == null) {
            throw new UnknownHostException();
        }
        if (info.getType() == ConnectivityManager.TYPE_MOBILE) {
            Log.i(TAG, "Won't use mobile connection");
            throw new UnknownHostException();
        } else {
            internetRestored();
            st.updateData();
        }
    } catch (MalformedURLException | UnknownHostException e) {
        internetFailed();
        Log.e(TAG, "No internet connection, cant log");
    } catch (IOException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        Log.i(TAG, "Thread iterrupted");
    }
}

【问题讨论】:

    标签: android multithreading service


    【解决方案1】:

    您使用什么 API 来定位/编译?

    编辑:检查处理程序:

    https://developer.android.com/reference/android/os/Handler.html

    处理程序有两个主要用途:(1)调度消息和 在未来某个时间点执行的可运行文件; (2) 到 将要在与您自己的线程不同的线程上执行的操作排入队列。 ... 当发布或发送到处理程序时,您可以允许在消息队列准备好后立即处理项目,或者指定处理之前的延迟或处理它的绝对时间。后两者允许您实现超时、滴答声和其他基于时间的行为。

    我在这里没有看到任何关于具体时间的警告,所以这可能是个好机会。

    一些有趣的文档:

    https://developer.android.com/reference/android/app/AlarmManager.html

    否则,警报将被设置为好像应用程序调用了 setRepeating(int, long, long, PendingIntent)。从 API 19 开始,所有重复警报都将不准确,并且无论其规定的重复间隔如何,都会与其他警报进行批处理。

    来自警报管理器的更多信息:

    注意:从 API 19 (KITKAT) 开始,警报传递不准确:操作系统 将切换警报以最大程度地减少唤醒和电池使用。那里 是支持需要严格交付的应用程序的新 API 保证;见 setWindow(int, long, long, PendingIntent) 和 setExact(int, long, PendingIntent)。应用程序 targetSdkVersion 早于 API 19 会继续看 以前的行为,其中所有警报都准确地在何时发送 请求。

    https://developer.android.com/reference/java/lang/Thread.html

    使发送此消息的线程在给定的时间内休眠 时间间隔(以毫秒和纳秒为单位)。这 不能保证精度 - 线程可能睡眠更多或更少 请求。

    计时器:

    此类不保证任务调度的实时性。

    ScheduledThreadPoolExecutor

    延迟任务在它们被启用之前执行,但没有任何 关于何时启用的实时保证,它们将 开始。安排在完全相同的执行时间的任务是 以先进先出 (FIFO) 的提交顺序启用。

    一些想法:

    根据我所见,最好的选择是降低 targetAPI

    如果这不起作用,我的下一个最佳选择是 thread.sleep(),同时使用 Thread.setPriority() 将线程执行优先级设置为尽可能高;

    如果我想办法减少您的实时需求 - 现实情况是 android 处理处理器时间的方式很复杂,而且它并不是真正的实时系统。

    如果您真的需要极其精确的计时,您可以探索 C 语言并尝试在 AudioFastPath 中执行您的代码,这可以确保您在处理器时间上尽可能保持最高的规律性。不过,这可能有点过头了。

    【讨论】:

    • 我使用 Thread.sleep(40) 进行绘图循环,它工作得非常好。因此,即使它不是非常精确,它仍然非常精确。有什么问题?
    • 我和你在一起,过去我用 thread.sleep 处理各种与绘图相关的事情。但是基于文档,它不提供准确性保证。我看到的文档中唯一的部分是 AlarmManager 的东西。就我个人而言,我认为他的代码中可能还有其他问题导致了这种情况,但很难确定。
    • 我要注意的是线程优先级 - 绘图线程将具有更高的线程优先级,因此具有更常规的处理器调度 - 比非绘图线程。请注意 Android Thread.setPriority() 文档中有关线程组的文档,以了解这些内容。
    • 无论如何,thread.sleep() 不应该有 15-45 秒的可变延迟,除非发生了奇怪的事情。我想知道这是一个模拟器还是什么的。
    • 也许这是一个无用的讨论,因为我们似乎在更大的问题上达成了一致。起初,我只是想弄清楚为什么我的答案是-1,但我不知道你是否对它们负责。它们恰好发生在您发布的同一时间。
    【解决方案2】:

    哒哒! https://developer.android.com/reference/java/util/Timer.html

    Timer timer = new Timer();
    timer.scheduleAtFixedRate(new TimerTask() {
        public void run() {
            //code to run
        }
    },initial_delay,period);
    

    【讨论】:

    • 是的……这也是我的想法。显而易见的。
    • 不,也完全不可靠。错误比使用 AlarmManager 时更糟,因为至少它只是不时延迟工作。使用Timer,所有更新都在大约 15-45 秒后发生,为时已晚。
    • 没办法,我用它来精确计时到秒。也许您的代码有其他问题。
    • 在 Timer 文档中,他们建议改用 ScheduledThreadPoolExecutor,这很相似,但在有多个线程时效果更好。也许这样会更好。 developer.android.com/intl/zh-tw/reference/java/util/concurrent/…
    【解决方案3】:
    Handler minuteHandler = new Handler();
    minuteHandler.postDelayed(runnable, 60000);
    final Runnable runnable = new Runnable() {
    @Override
    public void run() {
      // your runnable code
      minuteHandler.removeCallbacks(runnable);
      minuteHandler.postDelayed(runnable, 60000);
      }
    };
    

    【讨论】:

    【解决方案4】:

    如果定时器真的不适合你,最基本的实现就是这样。您无需为此创建新服务:

    Thread th = new Thread(new Runnable() {
        public void run() {
            while(true) {
                //do stuff
                try {
                    Thread.sleep(15000);
                }
                catch(InterruptedException ex) {}
            }
        }
    });
    th.start();
    

    这将永远持续下去,除非您在 while 循环中放置一个变量,或者添加一些条件和/或中断语句;

    【讨论】:

    • 这是解决方案 2,所有错误中最严重的错误,请阅读上文。
    • 为什么一定要做服务?如果这些方法都不起作用,那么您的代码有问题。我认为您应该发布要放入 run() 方法的代码。
    • 我需要一个服务,因为即使活动终止,这段代码也必须运行。它轮询 URL 并将新数据保存在数据库中。我将发布代码。
    • 基于该代码,我真的看不出任何问题的根源。您是否尝试过在没有服务的情况下进行测试(例如在此答案中)以进行测试?
    • 我也在活动中这样做。当活动终止时,服务以更大的间隔接管。在活动中进行时不会出现此类问题。尽管在测试您的解决方案时它确实工作得很好,直到我从我的计算机上拔下它。我检查了电池设置,当我拔掉它时它没有进入任何省电模式。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-06
    • 2017-03-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多