Android
Threading
Example
Tutorials
Programming

Threading Example in Android

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Android threading exists to keep the main thread responsive. Any long-running work such as network calls, decoding large files, or heavy computation should happen off the UI thread, while UI updates must return to the main thread in a controlled way.

The Main Rule: Do Not Block the UI Thread

Android renders views, processes input, and dispatches lifecycle callbacks on the main thread. If you perform slow work there, the app feels frozen and may trigger an ANR. The fix is not "use threads everywhere", but "move only the expensive work off the main thread and marshal results back safely".

Basic Example with Thread and runOnUiThread

The simplest demonstration uses a plain Java thread for background work and then switches back to the activity thread for UI changes.

java
1public class MainActivity extends AppCompatActivity {
2    private TextView statusText;
3
4    @Override
5    protected void onCreate(Bundle savedInstanceState) {
6        super.onCreate(savedInstanceState);
7        setContentView(R.layout.activity_main);
8        statusText = findViewById(R.id.statusText);
9
10        findViewById(R.id.loadButton).setOnClickListener(v -> loadData());
11    }
12
13    private void loadData() {
14        new Thread(() -> {
15            String result = simulateNetworkCall();
16
17            runOnUiThread(() -> statusText.setText(result));
18        }).start();
19    }
20
21    private String simulateNetworkCall() {
22        SystemClock.sleep(1500);
23        return "Loaded from background thread";
24    }
25}

This works for small examples and makes the threading boundary obvious: background work in the thread, UI mutation on the main thread.

Prefer Executors for Repeated Work

Creating raw threads repeatedly is not ideal for real applications. Executors let you reuse worker threads and centralize scheduling.

java
1private final ExecutorService executor = Executors.newSingleThreadExecutor();
2
3private void loadDataWithExecutor() {
4    executor.execute(() -> {
5        String result = simulateNetworkCall();
6        runOnUiThread(() -> statusText.setText(result));
7    });
8}
9
10@Override
11protected void onDestroy() {
12    super.onDestroy();
13    executor.shutdown();
14}

This pattern scales better and avoids spawning an unbounded number of threads.

Handlers and the Main Looper

If you want explicit control over posting work back to the main thread, use a Handler tied to Looper.getMainLooper().

java
1private final Handler mainHandler = new Handler(Looper.getMainLooper());
2
3private void loadDataWithHandler() {
4    executor.execute(() -> {
5        String result = simulateNetworkCall();
6        mainHandler.post(() -> statusText.setText(result));
7    });
8}

This makes the main-thread hop visible even outside an Activity context.

Lifecycle-Safe Thinking

Threading bugs in Android are often lifecycle bugs in disguise. A background task may finish after the activity is paused, stopped, or destroyed. If the result blindly updates the old screen, you can crash or show stale state.

One simple defensive habit is to separate work execution from UI rendering. Let the worker produce plain data, and let the activity decide whether it is still in a valid state to display it.

java
1private void loadDataSafely() {
2    executor.execute(() -> {
3        String result = simulateNetworkCall();
4
5        mainHandler.post(() -> {
6            if (isFinishing() || isDestroyed()) {
7                return;
8            }
9            statusText.setText(result);
10        });
11    });
12}

This is not a substitute for architecture components, but it makes the lifecycle risk visible. In larger apps, ViewModel plus observable state is usually a better boundary than pushing thread results directly into views.

Modern Recommendation: Coroutines or WorkManager

If you are writing Kotlin, coroutines are usually a better abstraction than manually wiring threads and handlers. If the task must survive app restarts or run deferrable background work, WorkManager is the better tool.

Still, understanding the low-level example matters because many Android APIs and legacy codebases are built on the same main-thread rule.

Common Pitfalls

  • Doing network or file I O directly in a click handler or lifecycle callback.
  • Updating a View from a background thread and hitting CalledFromWrongThreadException.
  • Creating many raw threads instead of reusing an executor.
  • Forgetting to shut down thread pools owned by short-lived components.
  • Using complex threading when the real need is coroutine scope management or WorkManager.

Summary

  • The UI thread must stay free for rendering and input.
  • Slow work belongs on a background thread or executor.
  • UI updates must return to the main thread using runOnUiThread, a Handler, or a modern equivalent.
  • Executors are more maintainable than repeatedly creating raw threads.
  • Coroutines and WorkManager are usually better for modern Android, but the threading fundamentals stay the same.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.