Android - Background Processing without Multi-Threading

Most Android documentation regarding background processing and updates to the UI will have you creating separate threads but this is not strictly necessary.

Some of you with experience on other platforms might recall "timer" style mechanisms that allow one to regularly execute code on the main thread and, as long as it's kept brief, is usually imperceptible to the user. In JavaScript we have the "setTimeout" and "setInterval" methods with which you can specify a function to be executed once in the future or repeatedly. In Windows we can use the "SetTimer" function to periodically send timer events to the application's message queue or we can also use a "Timer" class if using the .Net platform.

This is possible because most modern GUIs operate using an Event Driven model whereby the main process sits around doing nothing until the user interacts with the UI. Mouse, keyboard, or touch "messages" are then popped on a queue which is then picked up by an application's message processor and passed on to the appropriate function. Background processing can then be emulated by regularly posting "Timer" messages to an application's queue. The same can be done with Android.

The key to this is the android.os.Handler class. This is not in itself very remarkable as this class is regularly used in this context. A Handler class will usually be created and attached to another thread so that it can receive messages from the main thread. The difference in approach stems from attaching a new Handler to the main thread rather than another. This then let's us post regular messages to the main thread that can be processed in-between normal UI processing. This approach is by no means new, but I'd like to present it as a more general and useful solution then it usually is.

The diagram above gives us a simplified view of how a Handler works in this scenario. Our main thread starts in a Looper (1) which goes through a list of messages, some of which were inserted by our code and some relating to the normal UI processing of our app. When our message is reached (2) it is passed to our Handler which, in turn, calls our "run" function (3). Our "run" function draws a clock (see below) and inserts a new message into the queue (4). At some point in the future, the process is repeated (5).

The Code

There's very little code required. Certainly less than for multi-threaded solutions so I'll get straight to the code. The included Activity (full code at end of article) displays a simple analogue clock together with a text input box to prove it all works even while one is typing in text and interacting with the usual UI widgets. No XML layouts are required so you can insert the Activity code straight into your own project.

Below I've extracted the Handler specific bits and we can see that they are fairly trivial. Note that the Activity itself implements the Runnable interface and that no additional threads are created. The two most significant line are 46 and 69. It's at these points that the background processing is initiated. Each "post" equates to us having placed a message in the message queue that in the future, will cause the "run" function to be executed. Though not included in this example, you will most probably have to include code to deal with the "Pause" and "Resume" cycle of your app. This usually means having to remove any outstanding messages in the "pause" phase and posting a new message in the "resume" phase.

1:   package rs.blog;  
2:    
3:   public class MainActivity   
4:   extends android.app.Activity   
5:   implements java.lang.Runnable  
6:   {  
7:     public android.os.Handler handler;  
       ...
13:     
14:    @Override  
15:    protected void onCreate(android.os.Bundle savedInstanceState)  
16:    {  
         ...
45:      this.handler=new android.os.Handler();  
46:      this.handler.post(this);  
47:    }  
48:     
49:    public void run()  
50:    {  
         ...
69:      this.handler.post(this);  
70:    }  
       ...
112: }  

The Result

The clock should look something like the following image. I haven't bothered to dynamically set the clock size so if it's too big or small for your screen, you could manually adjust it's size on line 29.

Of course not all background processing needs to involve the UI. One could do local file or database processing. I wouldn't recommend any network related work unless you are fairly sure unexpected communication hangups aren't going to mess with the timely processing of UI events.

Other Articles On the Same Subject

Complete Code

1:   package rs.blog;  
2:    
3:   public class MainActivity   
4:   extends android.app.Activity   
5:   implements java.lang.Runnable  
6:   {  
7:    public android.os.Handler handler;  
8:    public android.widget.ImageView clock;  
9:    public android.graphics.Bitmap bitmap;  
10:   public android.graphics.Paint paint;  
11:   public java.util.Calendar cal;  
12:   public int size, radius;  
13:     
14:   @Override  
15:   protected void onCreate(android.os.Bundle savedInstanceState)  
16:   {  
17:    android.widget.LinearLayout layout;  
18:    android.widget.EditText text_input;  
19:      
20:    super.onCreate(savedInstanceState);  
21:    this.requestWindowFeature(android.view.Window.FEATURE_NO_TITLE);  
22:    this.getWindow().getDecorView().setBackgroundColor(0xff000000);  
23:      
24:    this.paint=new android.graphics.Paint();  
25:    this.paint.setColor(0xff888888);  
26:    this.paint.setTextAlign(android.graphics.Paint.Align.CENTER);  
27:    this.paint.setTextSize(40);  
28:    this.cal=java.util.Calendar.getInstance();  
29:    this.size=1000;  
30:    this.radius=(int)((float)this.size/2f);  
31:    this.bitmap=android.graphics.Bitmap.createBitmap  
32:     (this.size, this.size, android.graphics.Bitmap.Config.ARGB_8888);  
33:    clock=new android.widget.ImageView(this);  
34:    clock.setImageBitmap(this.bitmap);  
35:      
36:    text_input=new android.widget.EditText(this);  
37:    text_input.setTextColor(0xffffffff);  
38:      
39:    layout=new android.widget.LinearLayout(this);  
40:    layout.setOrientation(android.widget.LinearLayout.VERTICAL);  
41:    layout.addView(clock);  
42:    layout.addView(text_input);  
43:    setContentView(layout);  
44:     
45:    this.handler=new android.os.Handler();  
46:    this.handler.post(this);  
47:   }  
48:     
49:   public void run()  
50:   {  
51:    android.graphics.Canvas canvas;  
52:    float sec, min, hr, milli;  
53:      
54:    this.cal.setTimeInMillis(System.currentTimeMillis());  
55:    milli=this.cal.get(java.util.Calendar.MILLISECOND);  
56:    sec=this.cal.get(java.util.Calendar.SECOND)+(milli/1000f);  
57:    min=this.cal.get(java.util.Calendar.MINUTE)+(sec/60f);  
58:    hr=this.cal.get(java.util.Calendar.HOUR)+(min/60f);  
59:      
60:    canvas=new android.graphics.Canvas(this.bitmap);  
61:    canvas.drawColor(0xff000000);  
62:    canvas.translate(this.radius, this.radius);  
63:    this.Draw_Numbers(canvas);  
64:    this.Draw_Hours(canvas, hr);  
65:    this.Draw_Minutes(canvas, min);  
66:    this.Draw_Seconds(canvas, sec);  
67:      
68:    this.clock.invalidate();  
69:    this.handler.post(this);  
70:   }  
71:     
72:   public void Draw_Numbers(android.graphics.Canvas canvas)  
73:   {  
74:    int c;  
75:      
76:    for (c=0; c<12; c++)  
77:    {  
78:     canvas.save();  
79:     canvas.rotate(((float)c+1f)*(360f/12f));  
80:     canvas.drawText(String.valueOf(c+1), 0, -this.radius+40f, this.paint);  
81:     canvas.restore();  
82:    }  
83:   }  
84:     
85:   public void Draw_Seconds(android.graphics.Canvas canvas, float sec)  
86:   {  
87:    canvas.save();  
88:    canvas.rotate(sec*(360f/60f));  
89:    canvas.drawLine(0, 0, 0, -this.radius+50f, this.paint);  
90:    canvas.restore();  
91:   }  
92:     
93:   public void Draw_Minutes(android.graphics.Canvas canvas, float min)  
94:   {  
95:    canvas.save();  
96:    canvas.rotate(min*(360f/60f));  
97:    canvas.drawLine(10, 0, 0, -this.radius+50, this.paint);  
98:    canvas.drawLine(0, -this.radius+50, -10, 0, this.paint);  
99:    canvas.drawLine(-10, 0, 10, 0, this.paint);  
100:    canvas.restore();  
101:   }  
102:     
103:   public void Draw_Hours(android.graphics.Canvas canvas, float hr)  
104:   {  
105:    canvas.save();  
106:    canvas.rotate(hr*(360f/12f));  
107:    canvas.drawLine(10, 0, 0, -this.radius+200, this.paint);  
108:    canvas.drawLine(0, -this.radius+200, -10, 0, this.paint);  
109:    canvas.drawLine(-10, 0, 10, 0, this.paint);  
110:    canvas.restore();  
111:   }  
112:  }  

Popular Posts