3

我需要有一个在 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 可能是矫枉过正,如果它正在发射大量资源会消耗资源。更不用说重新安排了。

4

2 回答 2

2

为什么它不会在后台运行?

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

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

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

我认为一个 AlarmManager 可能是矫枉过正,如果它正在发射大量资源会消耗资源。

那是真实的。但是,让现有代码工作的唯一方法是获取 partial WakeLock,从而保持 CPU 永远运行。这将比现在的功率差几个数量级AlarmManager。并且AlarmManager已经够糟糕了,从 6.0 开始的每个 Android 版本都使得使用AlarmManager(或JobScheduler、或Timer和唤醒锁)可靠地做任何事情变得越来越困难。

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

这是我去年就该主题发表的演示文稿中的一组幻灯片Space,其中考虑了 Android 8.0(用于前进到下一张幻灯片)。您可能还阅读:

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

于 2018-05-13T20:32:45.167 回答
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
    }
}
于 2018-05-15T18:22:52.450 回答