Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cách sử dụng runOnUiThread trong Android

Giới thiệu về runOnUiThread()

runOnUiThread(Runnable action) là phương thức của Activity cho phép chạy code trên UI Thread (Main Thread) từ background thread.

Quy tắc vàng Android: Chỉ UI Thread được cập nhật View. Background thread không thể touch View trực tiếp - sẽ crash hoặc hành vi không xác định.

Ví dụ minh họa

Bước 1: Tạo project

Android Studio → File → New Project.

Bước 2: Layout (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:id="@+id/parent"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:gravity="center"
    android:orientation="vertical">
    <Button
        android:id="@+id/runOn"
        android:text="Run"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/text"
        android:textSize="20sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

Bước 3: Code (src/MainActivity.java)

package com.example.andy.myapplication;

import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    int i = 0;

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final TextView textView = findViewById(R.id.text);
        final Button runOn = findViewById(R.id.runOn);

        runOn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Tạo background thread
                new Thread() {
                    @Override
                    public void run() {
                        while (i++ < 1000) {
                            try {
                                // Chạy code cập nhật UI trên MAIN THREAD
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        textView.setText("#" + i);
                                    }
                                });
                                Thread.sleep(300); // Nghỉ 300ms
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }.start();
            }
        });
    }
}

Kết quả

Click nút "Run" → TextView cập nhật số đếm từ 1 đến 1000, mỗi 300ms một lần.

Cơ chế hoạt động

// Background thread
new Thread(() -> {
    // ... heavy work ...
    
    // Cần cập nhật UI → post lên Main Thread
    runOnUiThread(() -> {
        // Code này chạy trên UI Thread
        textView.setText("Updated!");
    });
}).start();

Các cách cập nhật UI từ Background Thread

Cách API Ưu điểm Nhược điểm
runOnUiThread() Activity Đơn giản, có sẵn trong Activity Chỉ dùng trong Activity
View.post() View Dùng đâu cũng được (có View) Cần reference View
Handler(Looper.getMainLooper()) Handler Linh hoạt, delay, remove Phức tạp hơn
LiveData/Flow + observe Architecture Components Lifecycle-aware, reactive Cần setup ViewModel
Coroutines (withContext(Dispatchers.Main)) Kotlin Coroutines Structured concurrency, modern Yêu cầu Kotlin

Ví dụ các cách khác

View.post(Runnable)

textView.post(() -> textView.setText("Updated from background"));

Handler

Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(() -> textView.setText("Updated"));
// Hoặc delay
mainHandler.postDelayed(() -> textView.setText("Delayed"), 1000);

Kotlin Coroutines (Khuyến nghị hiện nay)

// Trong ViewModel hoặc CoroutineScope
viewModelScope.launch {
    val result = repository.fetchData() // IO thread
    withContext(Dispatchers.Main) {
        textView.text = result // Main thread
    }
}

Lưu ý quan trọng

  • Không bao giờ cập nhật View trực tiếp từ background thread
  • runOnUiThread() đưa Runnable vào message queue của Main Looper
  • UI Thread bị block → ANR (Application Not Responding)
  • Tránh làm việc nặng trên UI Thread (network, DB, compute)
  • Dùng isFinishing() hoặc isDestroyed() check trước khi update UI

Pattern an toàn

runOnUiThread(() -> {
    if (!isFinishing() && !isDestroyed()) {
        textView.setText("Safe update");
    }
});