【问题标题】:TimerTask stops firing in ServiceTimerTask 在服务中停止触发
【发布时间】:2018-05-13 18:27:12
【问题描述】:

我需要有一个在 Android 中运行的服务,它经常将值存储到数据库中。频率取决于用户的偏好,以及是否发生了其他事件,可能长达 30 秒或长达 30 分钟。

这不是对用户隐藏的东西,实际上用户应该知道它的运行。因此,我认为前台服务可能是最好的方法。

我有一个前台服务正在运行,其中有一个 TimerTask 可以计算它需要触发的频率。该服务是“粘性的”,因此它应该一直存在,并且操作系统应该在一段时间后启动它的资源不足。

我的问题是当应用程序在后台运行一段时间后 TimerTask 似乎停止运行。

这是我的服务:

public class TimerService extends Service {

    private static final String LOG_NAME = TimerService.class.getName();
    private Timer timer;
    private final Handler timerHandler = new Handler();

    @Override
    public void onCreate() {
        super.onCreate();

        Notification notification = new NotificationCompat.Builder(this, "MY_APP_CHANNEL_ID")
                .setContentTitle("My Timer Service")
                .setContentText("Background timer task")
                .setSmallIcon(R.drawable.timer)
                .build();

        startForeground(1, notification);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        startTimer();
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        stopTimer();
    }


    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    private void stopTimer() {
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
    }

    private void startTimer() {
        stopTimer();

        timer = new Timer();

        long frequency = // calculate frequency
        long delay = // calculate delay
        timer.scheduleAtFixedRate(new MyTimerTask(), delay, frequency);
    }

    private void saveToDatabase() {
        // Save some stuff to the database...
        if (some condition) {
          // might need to reschedule timer delay and frequency.
          startTimer();
        }
    }

    private class MyTimerTask extends TimerTask {
        @Override
        public void run() {
            timerHandler.post(new Runnable() {
                @Override
                public void run() {
                  onTimerFire();
                }
            });
        }

        private void onTimerFire() {
          try {
            saveToDatabase();
          } catch (Exception e) {
            Log.e(LOG_NAME, "Error in onTimerFire", e);
          }    
        }
    }
}

这应该有效吗? IE 我可以在前台服务中有一个简单的计时器,它会持续触发直到该服务停止?如果是这样,我的代码中是否存在错误?

我选择了一个定时器来保持简单,我只需要运行一个定时器,我希望它能够轻松地重新安排。我确实意识到我可以尝试使用 Handler、ScheduledThreadPoolExecutor 甚至是 AlarmManager。我认为一个 AlarmManager 可能是矫枉过正,如果它正在发射大量资源会消耗资源。更不用说重新安排了。

【问题讨论】:

  • 您的代码在设备休眠时不会运行。这是你的症状吗?
  • 是的,这就是问题所在。为什么它不会在后台运行?服务以“startForeground”启动,这意味着即使应用程序在后台,服务也会继续在前台运行。
  • 您找到解决方案了吗?

标签: android android-service android-timer


【解决方案1】:

为什么不在后台运行?

正在在后台运行。当设备处于睡眠状态时,它不会运行,因为 CPU 已断电。在 Android 中,“后台”仅表示“没有前台 UI”(活动、具有前台的服务 Notification)。

我需要有一个在 Android 中运行的服务,它经常将值存储到数据库中。频率取决于用户的偏好,以及是否发生了其他事件,可能长达 30 秒或长达 30 分钟。

自 6.0 以来,您想要的东西在 Android 上并不实用。

我认为 AlarmManager 可能会过度杀伤力,并且如果它发射大量资源会消耗资源。

确实如此。但是,让现有代码工作的唯一方法是获取部分WakeLock,从而保持 CPU 永远运行。这将比AlarmManager 的功率差几个数量级。而且AlarmManager 已经够糟糕了,以至于从 6.0 开始的每个 Android 版本都越来越难以使用AlarmManager(或JobScheduler,或Timer 和唤醒锁)来可靠地执行任何操作。

您将需要了解 Android 在后台处理方面的能力和不能力,然后相应地调整您的产品计划。对于 Stack Overflow 的答案,该主题方式太长了。

Here is a set of slides 来自我去年就该主题发表的一次演讲,并考虑了 Android 8.0(使用 Space 前进到下一张幻灯片)。您还可以阅读:

恕我直言,如今编写依赖定期后台处理的应用程序是一项非常冒险的冒险。

【讨论】:

  • 非常感谢您的回答。我认为对我来说令人困惑的部分,也许我混淆的问题是我认为一个前台服务盯着 startForeground 并显示一个通知将被允许在前台、音频或位置等运行。从 android 文档看来,这就像这个案子,但我很可能解释错了。
  • @lostintranslation:前台服务会影响一些事情。例如,具有部分唤醒锁的前台服务将继续在 Android 7.0 的部分打盹模式下工作(屏幕关闭但设备正在移动)。但是,前台服务不会神奇地让 CPU 保持开机状态,并且前台服务也不能不受完全打盹模式的影响。
  • 我不知道!现在也在想地点。如果您有前台服务和 LocationManager,那么当您移动以将位置传送到应用程序时,CPU 会唤醒吗?
  • @lostintranslation:这可能取决于硬件,但我不会指望它。
  • 非常感谢您的帮助!
【解决方案2】:

应该同样使用ScheduledExecutorService。可以有多种方式来安排后台任务,例如Alarm ManagerJobDispatcherSync AdapterJob Scheduler。我会向他们推荐ScheduledExecutorService

我有一个在服务中使用 ScheduledExecutorService 的好例子。 (目前在高度优化的位置同步服务中使用)

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * Created by KHEMRAJ on 1/29/2018.
 */

public class SyncService extends Service {
    private Thread mThread;
    ScheduledExecutorService worker;
    private static final int SYNC_TIME = 60 * 1000; // 60 seconds

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        startSyncService();
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        stopThread();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    private void stopThread() {
        worker = null;
        if (mThread != null && mThread.isAlive()) mThread.interrupt();
    }

    private void startSyncService() {
        if (worker == null) worker = Executors.newSingleThreadScheduledExecutor();
        if (mThread == null || !mThread.isAlive()) {
            mThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    saveToDb();
                    if (worker != null) {
                        worker.schedule(this, SYNC_TIME, TimeUnit.MILLISECONDS);
                    }
                }
            });
            mThread.start();
        }
    }


    private void saveToDb() {
        // TODO: 5/15/2018
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-15
    • 1970-01-01
    • 2018-07-19
    相关资源
    最近更新 更多