Foreword
Like many other GUI frameworks/toolkits Android also works with single thread model. This if not correctly handled may result in unresponsive applications, and poor user experience, especially when the UI thread performs long running operations. Another challenge is to give the user feedback of on going work.First aim of this paper will try to explain how this problem should be handled. Second aim is to introduce the user of Thread related “Design Patterns”.
Below code samples are should be treated pseudo codes, close to java, and the use of Threads will be explicitly shown.
Single Thread Model
Android follows the single threaded approach that all other GUI frameworks take. This means all the tasks that GUI needs to perform will be performed by the UI Thread and all the tasks will be performed sequentially. This includes events triggered by the user that may take some time.Although there are some attempts on designing multi-threaded GUI frameworks/toolkits all merged to single threaded model because of the problems such as deadlocks and race conditions.
Sample 1: Save Operation
An application that uses Android Internal Storage will take only small amount of time to save. However most applications today try to update it’s data to online servers in order to share the data amongst different devices. The operations that must be performed on the web would take many times more than the previous case. btnSave.setOnClickListener(new OnClickListener() {
@Override public void onClick(View v) {
saveInternalStorage();
synchronizeOnWeb(); // long running operation,
}
});
Above code piece shows a save button blocking the UI until the operation performed on the web ends.
Sample 2: Running The Save On A Background Thread
With the use of the Java Thread api we can schedule our long running operations on another thread and allow the user continue his/her work on the UI. btnSave.setOnClickListener(new OnClickListener() {
@Override public void onClick(View v) {
new Thread(new Runnable() {
@Override public void run() {
longRunningOperation(); } }).start();
}
}
);
Sample 3: Giving Feedback to User
When there is a background process running such as the one on the previous example we should inform the progress to user. Following example animates the save button to inform the progress. Here is how the animation will be performed step by step:In order to realize this we will have to change the save buttons image from an background thread:
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
...
btnSave.setImageResource(R.drawable.progress1);
...
}
}).start();
But the above code results with the following exception:
android.view.ViewRootImpl$CalledFromWrongThreadException:
Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4746)
...
at android.widget.ImageView.setImageResource(ImageView.java:352)
at mca.activities.NoteListActivity$3$1.run(NoteListActivity.java:69)
at java.lang.Thread.run(Thread.java:856)
This exception message simply tells us that access to an object controlled by the UI Thread such as view objects, have to be done in with certain patterns so that thread-safety does not breake.
Scheduling Tasks For The UI Thread
Android API gives us three main options to schedule tasks to be run on the UI thread:- Activity.runOnUiThread(Runnable)
- View.post(Runnable)
- AsyncTask
Activity.runOnUiThread and View.post
Activity.runOnUiThread ve View.post metotları birbirine oldukça yakındır. Farkları biri Activity objesi üstünde tanımlıdır diğeri view objesi üstünde, runOnUiThread eğer aktif thread zaten UI Thread ise işi anında çalıştırırken view.post işi her halükarda planlamaktadır.These two methods are quite similar. They only have trivial differences. One is defined on an Activity object other is defined on any View object. runOnUIThread runs the callback code immediately if the runOnUIThread method is called on the UI Thread. The latter one schedules by default.
Thus, we rewrite the above code:
1. With Activity.runOnUiThread(Runnable):
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
MyActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
btnTest.setImageResource(R.drawable.progress1);
}
});
}
}).start();
}
2. With View.post(Runnable):
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
myBtn.post(new Runnable() {
@Override
public void run() {
myBtn.setImageResource(R.drawable.progress1);
}
});
}
}).start();
}
Since now we know how to schedule commands for the UI thread we could write code that will change the save button into a progress indicator.
final ProgressControllerAnimationThread progressT =
new ProgressControllerAnimationThread(btnSave, R.drawable.save);
progressT.start();
new Thread(new Runnable() {
@Override
public void run() {
longRunningOperation();
progressT.end(true);
}
}).start();
Progress animation is controlled by the ProgressControllerAnimationThread class which extends the Thread class. With the and of the worker thread this thread is signaled via the end method with parameter true or false indicating success or failure. This aproach decouples the part that does the work and the one that controls the animation.
AsyncTask
Android api’ı arka plan işler için AsyncTask nesnesini sunmaktadır. Kendi threadlerimizi oluşturmak yerine uygun durumlarda bu sınıfta kullanılabilir. AsyncTask template tasarım örüntüsü ile tasarlanmış soyut bir sınıftır. Params, Progress, Result isimli üç generic tanımlar. AsyncTask oluşturulduktan sonra execute method’u ile task başlatılır.AsyncTask kullanıcının türetebileceği dört metot sunar:
Another option is to extend from the AsyncTask abstract class. It follows the template method pattern. Needs three generics to be defined Params, Progress, Result.
Pattern provides us the following methods to override:
onPreExecute
Intended to do the necessary setup on the UI Thread.
doInBackground(Params...)
Must be implemented. Works in a background thread. Our long running operation should be defined here. In order to send progress message to UI Thread publishProgress method should be used.
onProgressUpdate(Progress...)
Run on the UI Thread. Run as the progress is reported by calls to the publishProgress method.
onPostExecute(Result)
A breakdown method. Runs on the UI Thread.
Plus there is the execute method which starts up the task.
Solution to problem defined above will be similar to this with the AsyncTask class:
public void onClick(View v) {
AsyncTask testTask = new AsyncTask...{
protected Void doInBackground(Void... params) {
...
while (System.currentTimeMillis() - start < 2000) {
doApartOfLongRunningOp();
if (System.currentTimeMillis() - changeTime > 100) {
changeTime = System.currentTimeMillis();
publishProgress(progress++);
}
}
return null;
}
protected void onProgressUpdate(Integer... values) {...}
};
testTask.execute();
}
Note that this solution is much more coupled than the previous one.
Handler and Looper Pattern
Although useful threads are costly; so we must be sure that threads our application create are not many and they get to end. Handler & Looper pattern helps us with that. It allows us to create a thread which will be used to handle background tasks removing the need to instantiate a new thread every time. Basic usage pattern: class BackgroundThread extends Thread {
public Handler handler;
public void run() {
Looper.prepare();
handler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
Looper.loop will associate the running thread as a ‘looper’. So make sure that it’s on the run method of the new thread. Handler object will associate itself with the looper. Looper.loop will cause the thread to keep running and handle messages until Looper.quit is called. Through the handler object this thread will execute the messages, events send to it’s queue. Handler.sendMessage or Handler.post maybe used to schedule messages and events.
UI thread has a predefined looper. So a new handler defined on the UI thread may be used as a new queue that holds messages to UI thread. Trying to create a handler without the looper will cause an exception.
Thread Priorities
By default background threads have much less priorities then than the UI thread. If it’s required that your thread has more CPU time you should give it a higher priority with Thread.setPriority method.Last Words
Like many other Android GUI is designed with single threaded model. For better user experience we must learn how we should design an multithreaded applications with the API.Resources
- API Guide: “Processes and Threads”, http://developer.android.com/guide/components/processes-and-threads.html
- “Concurrency In Action”, Brian Goetz