In Android development, it’s common to perform time-consuming operations on background threads to avoid blocking the main thread and ensure a responsive user interface. However, when updating the UI from these background threads, you may encounter the "Only the original thread that created a view hierarchy can touch its views" error. This tutorial will explain why this happens and provide solutions to update the Android UI safely from background threads.
Understanding the Problem
In Android, each application has its own main thread (also known as the UI thread), which is responsible for handling user interactions and updating the UI. When you create a view hierarchy in an Activity’s onCreate
method, it’s associated with this main thread. However, when you try to update the UI from a background thread, Android throws a CalledFromWrongThreadException
, because only the original thread that created the view hierarchy can touch its views.
Solution 1: Using runOnUiThread
One way to solve this problem is by using the runOnUiThread
method provided by the Activity class. This method allows you to execute a Runnable on the main thread, which can then update the UI safely. Here’s an example:
public void run() {
int pos = 0;
int total = mp.getDuration();
while (mp != null && pos < total) {
try {
Thread.sleep(1000);
pos = appService.getSongPosition();
} catch (InterruptedException e) {
return;
} catch (Exception e) {
return;
}
// Update the UI on the main thread
runOnUiThread(new Runnable() {
@Override
public void run() {
progress.setProgress(pos);
String time = String.format("%d:%02d",
TimeUnit.MILLISECONDS.toMinutes(pos),
TimeUnit.MILLISECONDS.toSeconds(pos) % 60);
currentTime.setText(time);
}
});
}
}
Solution 2: Using Handlers
Another way to update the UI from a background thread is by using Handlers. A Handler allows you to send and process messages (Runnable or Message objects) between threads. Here’s an example:
private Handler handler = new Handler(Looper.getMainLooper());
public void run() {
int pos = 0;
int total = mp.getDuration();
while (mp != null && pos < total) {
try {
Thread.sleep(1000);
pos = appService.getSongPosition();
} catch (InterruptedException e) {
return;
} catch (Exception e) {
return;
}
// Update the UI on the main thread using a Handler
handler.post(new Runnable() {
@Override
public void run() {
progress.setProgress(pos);
String time = String.format("%d:%02d",
TimeUnit.MILLISECONDS.toMinutes(pos),
TimeUnit.MILLISECONDS.toSeconds(pos) % 60);
currentTime.setText(time);
}
});
}
}
Solution 3: Using Kotlin Coroutines
If you’re using Kotlin, you can use coroutines to simplify your code and update the UI safely. Here’s an example:
lifecycleScope.launch {
withContext(Dispatchers.Default) {
// Background processing...
}
// Update the UI on the main thread
progress.progress = pos
val time = String.format("%d:%02d",
TimeUnit.MILLISECONDS.toMinutes(pos),
TimeUnit.MILLISECONDS.toSeconds(pos) % 60)
currentTime.text = time
}
Best Practices
When updating the UI from background threads, keep the following best practices in mind:
- Always update the UI on the main thread to avoid
CalledFromWrongThreadException
. - Use
runOnUiThread
or Handlers to execute Runnables on the main thread. - Keep the background processing code separate from the UI update code for better readability and maintainability.
- Avoid blocking the main thread with time-consuming operations; instead, perform them on background threads.
By following these guidelines and using the solutions provided in this tutorial, you can safely update your Android app’s UI from background threads and ensure a responsive user experience.