2

I'm making an app that does stuff in time intervals. The absolute longest i can wait between the updates is 30 seconds, anything between that and 0 is acceptable but i prefer 15 seconds for good measure. However, its not as easy as it sounds. I tried 4 methods, all of which are unacceptable for various reasons.

Keep in mind these problems occur in a Service, when this code is run in an Activity it runs fine. I also noticed that when the phone is plugged into my computer for debugging purposes, solution 3 run perfectly fine until i plug it out. I checked my battery settings, and there are no battery saving modes or other stuff like that, that could be the reason for this.

1.IntentService that does stuff and then reschedules itself in 15 seconds. Unfortunately AlarmManager.setExact() is completely unreliable when it comes to the alarms being exact, as i described in this tumbleweed: Android Alarm not triggering at exact time

2.Foreground service that has a Thread. In that thread i do my stuff and then sleep() for 15 seconds. This method turned out to be even worse than previous, the thread was being woken up to 5 minutes past the proper time.

3.Then i tried using Timer(as geokavel suggested) and scheduling work using scheduleAtFixedRate and schedule but the work was being done about 15-45 seconds too late, making the intervals to about 1 minute instead of 15 seconds.

4.The last way i thought of to achieve this is to not sleep in the Thread in the foreground service from above. Instead, i compare the time:

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

This method works like a charm except for one major downside- it uses 20-25% of the CPU all the time.

So my question is, is there maybe a way to make one the above solutions work properly(without their unacceptable downsides)? If not, is there a better way that i missed? Feel free to ask for more detail if needed.

EDIT: Requested code of 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");
    }
}
Community
  • 1
  • 1
JamMaster
  • 1,446
  • 1
  • 14
  • 24

4 Answers4

0

What API are you targeting/compiling with?

Edit: Check out Handlers:

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

There are two main uses for a Handler: (1) to schedule messages and runnables to be executed as some point in the future; and (2) to enqueue an action to be performed on a different thread than your own. ... When posting or sending to a Handler, you can either allow the item to be processed as soon as the message queue is ready to do so, or specify a delay before it gets processed or absolute time for it to be processed. The latter two allow you to implement timeouts, ticks, and other timing-based behavior.

I don't see any of the caveats about specific timing here so It might be a good shot.

Some fun docs:

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

Otherwise, the alarm will be set as though the application had called setRepeating(int, long, long, PendingIntent). As of API 19, all repeating alarms will be inexact and subject to batching with other alarms regardless of their stated repeat interval.

more from Alarm Manager:

Note: Beginning with API 19 (KITKAT) alarm delivery is inexact: the OS will shift alarms in order to minimize wakeups and battery use. There are new APIs to support applications which need strict delivery guarantees; see setWindow(int, long, long, PendingIntent) and setExact(int, long, PendingIntent). Applications whose targetSdkVersion is earlier than API 19 will continue to see the previous behavior in which all alarms are delivered exactly when requested.

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

Causes the thread which sent this message to sleep for the given interval of time (given in milliseconds and nanoseconds). The precision is not guaranteed - the Thread may sleep more or less than requested.

Timer:

This class does not offer guarantees about the real-time nature of task scheduling.

ScheduledThreadPoolExecutor

Delayed tasks execute no sooner than they are enabled, but without any real-time guarantees about when, after they are enabled, they will commence. Tasks scheduled for exactly the same execution time are enabled in first-in-first-out (FIFO) order of submission.

A few thoughts:

Based on what I've seen your best bet is to lower your targetAPI < 19 and use Alarm Manager.

If that doesn't work my next best option is thread.sleep() while Setting your threads execution priority to as high as possible using Thread.setPriority();

Failing that I would look into ways to lessen your real time needs - the reality is that the way android handles processor time is complicated and it isn't really intended to be a real time system.

If you REALLY need extremely accurate timing you might explore C and trying to execute your code in the AudioFastPath which garuntees you the highest possible regularity in processor time. Thats probably wayyyy over kill though.

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Nathaniel D. Waggoner
  • 2,856
  • 2
  • 19
  • 41
  • 1
    I've used Thread.sleep(40) for a drawing loop, and it worked perfectly fine. So even if it's not perfectly precise, it's still pretty darn precise. What is the issue? – geokavel Oct 16 '15 at 20:18
  • I'm with you, I've used thread.sleep for various drawing related things in the past. Based on the docs however it does not provide an accuracy garuntee. The only section of the docs I've seen offer that is AlarmManager stuff. Personally I think he probably has other issues in his code causing this, but its tough to know for sure. – Nathaniel D. Waggoner Oct 16 '15 at 20:20
  • One thing I'd note is the thread priority - a drawing thread will have a much higher thread priority, and thus have more regular processor scheduling - than a non drawing thread. Note the docs about thread groups in the Android Thread.setPriority() docs to get a glimpse of that stuff. – Nathaniel D. Waggoner Oct 16 '15 at 20:24
  • So if the processor sees that most of the code in your thread is related to drawing, it automatically gives it higher priority, without you specifically telling it to? – geokavel Oct 16 '15 at 20:29
  • No, the UI thread is going to have a higher thread priority. Any code executed in the UI thread is going to be executed with that priority. You typically don't do things like thread.sleep() there because it blocks the UI Thread, so this is a case you wouldn't really notice since the app would freeze for 15 seconds. – Nathaniel D. Waggoner Oct 16 '15 at 20:29
  • Ok, then just to clarify, my example where I used Thread.sleep(40) wasn't on the UI thread. It was just where I drew to a Canvas from a SurfaceHolder. – geokavel Oct 16 '15 at 20:32
  • Yea where was that work done? In a onDraw() call? That would be a uiThread execution. Unless your code was specifically being run in a secondary thread, the UI Thread and the Application Thread are the same thing. – Nathaniel D. Waggoner Oct 16 '15 at 20:33
  • 1
    Either way though, thread.sleep() shouldn't have a 15-45s variable delay unless something weird is going on. I wonder if this is an emulator or something. – Nathaniel D. Waggoner Oct 16 '15 at 20:34
  • Nope, it was a thread I made myself with `new Thread(this)` (class implements Runnable). – geokavel Oct 16 '15 at 20:35
  • Yea, as I said above i've never had any major issues with thread.sleep(). Only follow up I would have here is to ask how closely you observed your thread timing? If it looked good you may not have noticed minor inconsistencies in timing. Thats just being nit picky at this point. – Nathaniel D. Waggoner Oct 16 '15 at 20:39
  • 1
    Maybe this was a useless discussion, because we seem to agree on the bigger issue. At first, I was just trying to figure out why a got the -1's on my answers, but I don't know if you were responsible for them. They just occurred at the exact same time as you posted. – geokavel Oct 16 '15 at 20:41
-1

Ta-Da! 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);
Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
geokavel
  • 619
  • 4
  • 12
  • Yes... that was my idea too. The obvious one. – greenapps Oct 16 '15 at 08:56
  • Nope, also completely unreliable. The errors are worse than when using AlarmManager, because then at least it only delayed the work from time to time. Using `Timer` all the updates occur at about 15-45 seconds too late. – JamMaster Oct 16 '15 at 19:44
  • 1
    No way, I've used this for precise timing to the second. Maybe something else is wrong with your code. – geokavel Oct 16 '15 at 19:48
  • In the Timer documentation they recommend using ScheduledThreadPoolExecutor instead, which is similar, but works better when there are multiple threads. Maybe that will work better. http://developer.android.com/intl/zh-tw/reference/java/util/concurrent/ScheduledThreadPoolExecutor.html – geokavel Oct 16 '15 at 19:52
-1
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);
  }
};
Prasanth
  • 217
  • 1
  • 2
  • 10
  • HI, Anyone is there to fix the above mentioned issue.(How to achieve exact(accuracy to seconds) timing on Android service.Please refer the link for my question . https://stackoverflow.com/questions/51558952/call-api-for-every-1-minute601000-milliseconds-upto-10-hours – Prasanth Jul 30 '18 at 09:58
-2

If Timers really don't work for you, the most basic implementation would be something like this. You don't need to make a new Service for this to work:

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

This will go on forever unless you put a variable in the while loop, or add some conditionals and/or break statements;

geokavel
  • 619
  • 4
  • 12
  • This is solution number 2, worst errors of them all, read above. – JamMaster Oct 16 '15 at 20:02
  • Why do you have to make a Service? If neither of those methods work, then there is something wrong with your code. I think you should post the code you're putting in the run() method. – geokavel Oct 16 '15 at 20:04
  • I need a service because this code has to run even if the activity dies. Its polling a URL and saving the new data in a database. I will post the code. – JamMaster Oct 16 '15 at 20:06
  • Based on that code, I can't really see any source of problems. Have you tried doing it without a Service (like in this answer) for testing purposes? – geokavel Oct 16 '15 at 20:15
  • I am doing it in an activity as well. When the activity dies, the service takes over with bigger interval. No problems like this occur when doing it in activity. Although when testing your solution it did work really good untill i unplugged it from my computer. I chcked battery settings, it doesnt go into any battery saving mode when i unplug it. – JamMaster Oct 16 '15 at 20:19
  • Alright, maybe you should clarify in your question and in the title, that you're only having problems in the Service. – geokavel Oct 16 '15 at 20:21