Tuesday 22 June 2010

Anroid UI setInterval

In most apps I develop, I usually have a need to update the UI within the activity (as services change, or just live time displays). Making these timers is easy in Javascript, you just use setInterval(method,nnn).

On Android it is more complex - you need a Timer and TimerTask, and then you need another Runnable to give to Activity.runOnUiThread(..), and then your actual method. It all adds a lot of code bloat, especially if you have a few activities in your app. AsyncTask is good for updating the UI while a background process is running, but for just a simple update interval it's no use.

This is a reasonably simple solution to the problem - the MyTimer class has a setInterval method that behaves similarly to Javascript. The usage is:
  • Set the timer:
    int timer=MyTimer.setInterval(this, "updateUI", 2000,true);
    (where "updateUI" is a method with the signature: public void updateUI() {..})
  • Cancel the timer:
    timer=MyTimer.cancelTimer(timer);
Some Notes:
  • The cancel operation returns -1 if the cancel was performed successfully, so you can just test if the timer ==-1 to tell if you need to start it again.
  • The code above is called from the activity, the first argument of setInterval should be an Activity object.
  • There is a static debug variable if it doesnt work as expected (MyTimer.debug=true).
  • If there is an exception in the target method -> the timer is cancelled.
  • MAKE SURE YOU CANCEL THE TIMER (in onStop or onPause). If the timer is not cancelled, then it will prevent garbage collection, a possible way around is just to return the Timer object instead of holding it in a Vector.
  • Note that this type of usage can potentially use a lot of battery if its just left running, the updateUI method (or whatever the method name is), should do the minimum processing necessary. It should just update a couple of UI elements or something - not do masses of calculations (store results as variables is a simple solution).
package co.uk.sentinelweb.util;
/*
* licensed under CC BY-SA : http://creativecommons.org/licenses/by-sa/3.0/legalcode
*/
import java.lang.reflect.Method;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import android.app.Activity;
import android.util.Log;

public class MyTimer {
public static Vector timers = new Vector();
public static boolean debug = false;// turn on to check timer activates and cancels
public static int setInterval(Activity object,String method,int interval,boolean isUI) {
Timer timeoutTimer= new Timer();
TimeoutTask timeoutTimerTask;
try {
timeoutTimerTask = new TimeoutTask(object,method,isUI,timeoutTimer.hashCode());
if (debug) {Log.d(MyTimer.class.getSimpleName(), "timer created :");}
} catch (Exception e) {
Log.d(MyTimer.class.getSimpleName(), "error creating timer :"+method,e);
return -1;
} 
timeoutTimer.scheduleAtFixedRate(timeoutTimerTask, 0, interval); 
timers.add(timeoutTimer);
return timeoutTimer.hashCode();
}

public static int cancelTimer(int code) {
Timer timeoutTimer = null;
for (int i=0;i<timers.size();i++) {
if (timers.get(i).hashCode()==code) {
timeoutTimer=timers.get(i);
}
}
if (timeoutTimer!=null) {
timers.remove(timeoutTimer);
timeoutTimer.cancel();
timeoutTimer.purge();
if (debug) {Log.d(MyTimer.class.getSimpleName(), "timer deleted :");}
return -1;
} else {
if (debug) {Log.d(MyTimer.class.getSimpleName(), "timer not found :"+code);}
}
return code;
}

private static class TimeoutTask extends TimerTask {
Activity targetObject;
Method targetMethod;
Runnable invoker;
int code ;
public TimeoutTask(Activity targetObject, String targetMethod,boolean isUI,int code) throws SecurityException, NoSuchMethodException {
super();
this.targetObject = targetObject;
this.targetMethod = targetObject.getClass().getDeclaredMethod(targetMethod, new Class[]{});
this.code=code;
if (isUI) {
invoker = new ThreadRunner(this.targetObject,this.targetMethod);
}
}

@Override
public void run() {
try {
if (debug) {Log.d(MyTimer.class.getSimpleName(), "invoke timer :");}
if (invoker==null) {
targetMethod.invoke(targetObject, new Object[]{});
} else {
targetObject.runOnUiThread(invoker);
}
} catch(Exception e) {
Log.d(MyTimer.class.getSimpleName(), "error invoking:"+targetMethod.getName(),e);
cancelTimer(code);
Log.d(MyTimer.class.getSimpleName(), "timer:"+code+" has been cancelled");
}
}

}

private static class ThreadRunner implements Runnable{
Activity targetObject;
Method targetMethod;

public ThreadRunner(Activity targetObject, Method targetMethod) {
super();
this.targetObject = targetObject;
this.targetMethod = targetMethod;
}

@Override
public void run() {
try {
targetMethod.invoke(targetObject, new Object[]{});
} catch (Exception e) {
Log.d(MyTimer.class.getSimpleName(), "error invoking:"+targetMethod.getName(),e);
}
}
}
}
CAVEAT: While I haven't found anything in the API to do this (easily), it is possible that it is in there somewhere. If it is then let me know, and I'll update this.

This code is made open under the CC BY-SA licence.

No comments:

Post a Comment