4

I need to have a Service running in Android that stores a value to database every so often. How often is based on user preferences, and also if other events have happened, which can be as often as 30 seconds or up to 30 minutes.

This is not something that is hidden from the user and in fact the user should probably be aware its running. As such I think a foreground service is probably the best approach.

I have a foreground service running, with a TimerTask that calculates how often it needs to fire. That Service is 'sticky' so it should stick around and it low on resources the OS should start it back up after a while.

My problem is that the TimerTask seems to stop running after a while when the the app is backgrounded.

Here is my service:

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

Should this work? IE can I have a simple Timer in a foreground Service that fires continuously until that service is stopped? If so is there a bug in my code?

I chose a Timer to try to keep it simple, I only ever need one timer running and I wanted it to be able to reschedule easily. I do realize that I could try a Handler, ScheduledThreadPoolExecutor, or even an AlarmManager. I thought an AlarmManager might be overkill and a drain on resources if it is firing a ton. Not to mention rescheduling.

Phantômaxx
  • 37,901
  • 21
  • 84
  • 115
lostintranslation
  • 23,756
  • 50
  • 159
  • 262

2 Answers2

2

Why won’t it run in the background?

It is running in the background. It is not running when the device is asleep, as the CPU is powered down. In Android, "background" simply means "has no foreground UI" (activity, service with a foreground Notification).

I need to have a Service running in Android that stores a value to database every so often. How often is based on user preferences, and also if other events have happened, which can be as often as 30 seconds or up to 30 minutes.

What you want has not been practical on Android since 6.0.

I thought an AlarmManager might be overkill and a drain on resources if it is firing a ton.

That is true. However, the only way to get your existing code to work would be for you to acquire a partial WakeLock, thereby keeping the CPU running forever. This will be orders of magnitude worse for power than is AlarmManager. And AlarmManager is bad enough that each Android release, starting with 6.0, has made it progressively more difficult to use AlarmManager (or JobScheduler, or Timer and a wakelock) to do anything reliably.

You are going to need to learn what Android is and is not capable of with respect to background processing, then adjust your product plans accordingly. That subject is way too long for a Stack Overflow answer.

Here is a set of slides from a presentation that I delivered last year on this subject, with Android 8.0 in mind (use Space to advance to the next slide). You might also read:

IMHO, writing an app that relies upon periodic background processing is a very risky venture nowadays.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Thanks so much for the answer. I think the confusing part to me and maybe I confused the issue is that I thought a foreground service stared with startForeground and presenting a notification would be allowed to run in the foreground, or audio or location, etc. From the android docs this seems like the case but I could very well be interpreting it incorrectly. – lostintranslation May 14 '18 at 10:46
  • @lostintranslation: A foreground service affects things somewhat. For example, a foreground service with a partial wakelock would continue to work in Android 7.0's partial Doze mode (screen is off but device is moving). However, a foreground service does not magically keep the CPU powered on, and a foreground service is not immune to full Doze mode. – CommonsWare May 14 '18 at 11:48
  • I had no idea! Wondering about locations now as well. If you have a foreground service and a LocationManager will the cpu wake up when you are moving to deliver locations to the app? – lostintranslation May 14 '18 at 12:25
  • @lostintranslation: That might depend on the hardware, but I would not count on it. – CommonsWare May 14 '18 at 12:33
  • Thanks so much for all your help! – lostintranslation May 14 '18 at 12:42
  • So whats the way to do like this.I am developing an app for video ringtone so basically it must be in background.any ways to do that – Lavanya Velusamy Oct 11 '18 at 04:06
2

You should use ScheduledExecutorService for the same. There can be many ways to schedule background task like Alarm Manager, JobDispatcher, Sync Adapter, Job Scheduler. I will suggest ScheduledExecutorService over them.

I have one good example of using ScheduledExecutorService in service. (Currently using in highly optimised location sync service)

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
    }
}
Rob
  • 26,989
  • 16
  • 82
  • 98
Khemraj Sharma
  • 57,232
  • 27
  • 203
  • 212