开发者

Running tasks at regular intervals in android

I have an app which has tasks that run on regular intervals.

Instead of using timers I use Handler.sendMessageDelayed(Message, long).

My service starts a Thread which, when done, sends a message to the service's handler which in turn sends a message with a delay to itself saying it should start the task again.

This works fine, at a start.

As a test I set the delay to 10 seconds and left the phone for an hour or so expecting to see a log entry every 10 seconds (plus a few milliseconds for execution) and at a start that is what happened but as soon as I close the screen this happens:

06-15 23:19:46.237: INFO/Service(12932): Starting
06-15 23:19:46.667: VERBOSE/DebugTask(12932): Fire
06-15 23:19:56.687: VERBOSE/DebugTask(12932): Fire
06-15 23:20:04.237: DEBUG/dalvikvm(12932): Debugger has detached; object registry had 1 entries
06-15 23:25:34.788: VERBOSE/DebugTask(12932): Fire
06-15 23:27:11.338: VERBOSE/DebugTask(12932): Fire <- Screen on
06-15 23:27:21.348: VERBOSE/DebugTask(12932): Fire
06-15 23:27:31.368: VERBOSE/DebugTask(12932): Fire
06-15 23:27:41.378: VERBOSE/DebugTask(12932): Fire
06-15 23:27:51.388: VERBOSE/DebugTask(12932): Fire
06-15 23:27:55.158: DEBUG/dalvikvm(12932): GC_EXTERNAL_ALLOC freed 127K, 47% free 2891K/5447K, external 2114K/2137K, paused 94ms
06-15 23:27:56.788: INFO/Service(12932): Shutting down
06-15 23:27:56.808: INFO/Service(12932): Service has stopped

It only seems to happen when the screen is off. Could this be a priority issue? Could service get paused while other stuff is running?

In this case would changing it to timers make a difference?

I'd rather not use timer due to threading issues, handlers solve this nicely for me.

Below I've attached relevant code for the app, I'd be happy to provide anything else that you might require.

My service's handler handles other messages than starting tasks but none of the other types of messages are long running, they involve showing notifications and delegating messages to other handlers so I don't believe that the service thread is busy with other messages.

The service might be busy with other stuff however, I'm not sure what calls a service might get during it's lifetime.

Service.java:

private void startTask(Class<? extends Task> taskClass) {
    Task task = null;
    try {
        Constructor<? extends Task> c = taskClass.getConstructor();
        task = c.newInstance();
    } catch (Exception e) {
        Log.e("Service", "Failed to create class of type " + taskClass.getName(), e);
    }

    if(task != null) {          
        runningTasks.put(taskClass, task);
        task.start();
    }
}

private void taskDone(Class<? extends Task> taskClass) {
    runningTasks.remove(taskClass);

    SharedPreferences prefs = getPreferences();

    try {
        long interval = (Long)taskClass.getDeclaredMethod("getInterval", SharedPreferences.class).invoke(null, prefs);

        if(interval < 0)
            return;
        else {
            Message msgTmp = handler.obtainMessage(ServiceHandler.START_TASK, taskClass);
            handler.sendMessageDelayed(msgTmp, interval*1000);
        }

    }
    catch(NoSuchMethodException e) {
        Log.w("Service", String.format("Task %s does not implement method getInterval", taskClass.getSimpleName()));
    } catch (Exception e) {
        Log.w("Service", String.format("Could not call getInterval on task %s", taskClass.getSimpleName()), e);
    }
}

public class ServiceHandler extends Handler {
    /* Constants */
    public static final int TASK_DONE = 1; // obj contains the class
    public static final int START_TASK = 2; // obj contains the class

    @SuppressWarnings("unchecked")
    public void handleMessage(Message msg) {
        if(msg.what == TASK_DONE)
            taskDone((Class<? extends Task>)msg.obj);

        else if(msg.what == START_TASK)
            startTask((Class<? extends Task>) msg.obj);
    };
};

Task.java:

public abstract class Task extends Thread {
    @Override
    public final void run() {
        preRunTask();
        runTask();
        postRunTask();
        sendFinish();
    }

    private synchronized void sendFinish() {
        if(serviceHandler == null)
            return;
        Message m = serviceHandler.obtainMessage(ServiceHandler.TASK_DONE, getClass());
        serviceHandler.sendMessage(m);
    }
}

DebugTask.java:

public DebugTask extends Task {
    public static long getInterval(SharedPreferences prefs) {
        return Long.parseLong(prefs.getString(FREQUENCY_KEY, "-1"));
    }

    @Override
    protected void runTask() {
        Log.v("DebugTask", "Fire");
    }
}

Update 1

I tried calling Service.startForeground(int, Notification) but it made no difference.


Update 2

After a lot of digging it turns out I basically need to acquire a partial wakelock in order for my service to run when the screen is shut down.

This of course will drain the battery of the phone which is not really desired.

Is there any way to run my tasks at a certain interval without having a constant wakelock?

I basically want my app to sleep when there isn't any work to be done and wake up when a task to run, run that task and then go back to sleep, is that possible?

I know there is AlarmManager which I could potentially use but that will be very hard to implement since that will run my service at a regular intervals when in reality I have multiple threads which all needs to run a different intervals.


Update 3

I'm working on a fairly complex solution using partial wake locks and the alarm manager, I'll post back when I succeed/fail.开发者_如何学JAVA


Final update

As I wrote in update 3 my final solution skips sendDelayed completely and use the alarm manager instead, while each task is running a partial wakelock is acquired and it's released when the task is done.


The Service will pause its execution when the device goes to sleep. There's no way around that. The only two solutions are the ones you mentioned in the updates, namely a WakeLock and the AlarmManager.

The AlarmManager will be the preferred solution to avoid battery drain. If you need to perform a task at different intervals, you may use setInexactRepeating(). You can set one alarm for each task you need to perform.

You will still need to add a wakelock for the time it takes to finish the job, as the alarm will wake the device but it will not keep it awake for long. For an implementation of that, check CommonsWare's example code here: https://github.com/commonsguy/cwac-wakeful


Try by this one :

    private void doSomethingRepeatedly() {
      timer.scheduleAtFixedRate( new TimerTask() {
            public void run() {

                  try{

                     new SendToServer().execute(); 

                  }
                  catch (Exception e) {
                      // TODO: handle exception
                  }

             }
            }, 0, 10000);
                     }
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜