Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

วิธีใช้ runOnUiThread ใน Android

ภาพรวม

runOnUiThread() เป็นเมธอดของ Activity ใช้สำหรับรันโค้ดบน UI Thread (Main Thread) จาก Background Thread ช่วยแก้ปัญหา ANR (Application Not Responding) เมื่อ Main Thread ทำงานหนัก

ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่

สร้างโปรเจกต์ Android ใหม่ใน Android Studio

ขั้นตอนที่ 2: เพิ่ม Layout XML

ไฟล์ 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>

ขั้นตอนที่ 3: โค้ด Java

ไฟล์ 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) {
                new Thread() {
                    @Override
                    public void run() {
                        while (i++ < 1000) {
                            try {
                                // รันบน UI Thread
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        textView.setText("#" + i);
                                    }
                                });
                                Thread.sleep(300);  // หน่วง 300ms
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }.start();
            }
        });
    }
}

🔍 การทำงานของ runOnUiThread()

new Thread() {
    public void run() {
        // Background thread - ทำงานหนักได้
        while (condition) {
            // ...
            
            // ต้องการ Update UI -> ใช้ runOnUiThread
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    // โค้ดนี้รันบน Main Thread - Update UI ได้ปลอดภัย
                    textView.setText("Updated");
                }
            });
        }
    }
}.start();

ทางเลือกอื่นๆ สำหรับการ Update UI จาก Background Thread

1. Handler (Classic)

Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
    @Override
    public void run() {
        textView.setText("Updated via Handler");
    }
});

2. View.post() / View.postDelayed()

textView.post(new Runnable() {
    @Override
    public void run() {
        textView.setText("Updated via View.post()");
    }
});

// หรือหน่วงเวลา
textView.postDelayed(() -> textView.setText("Delayed"), 1000);

3. Kotlin Coroutines (แนะนำสำหรับโปรเจกต์ใหม่)

// ใน Coroutine Scope (lifecycleScope, viewModelScope)
lifecycleScope.launch(Dispatchers.IO) {
    // Background work
    val result = doHeavyWork()
    
    // Switch to Main thread
    withContext(Dispatchers.Main) {
        textView.text = result
    }
}

4. RxJava

Observable.fromCallable(() -> doHeavyWork())
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(result -> textView.setText(result));

5. LiveData + ViewModel (Architecture Components)

// ViewModel
MutableLiveData<String> textLiveData = new MutableLiveData<>();

// Background thread
textLiveData.postValue("New Value");  // Post to main thread automatically

// Activity/Fragment - Observe
viewModel.textLiveData.observe(this, text -> textView.setText(text));

⚠️ ข้อควรระวัง

  • ไม่ควรทำงานหนักบน runOnUiThread - จะทำให้ UI ค้าง (ANR)
  • runOnUiThread() ใช้ได้เฉพาะใน Activity เท่านั้น (Fragment ใช้ requireActivity().runOnUiThread())
  • หาก Activity ถูก Destroy แล้ว การ post อาจทำให้ Crash (Memory leak) → ใช้ WeakReference หรือ Lifecycle-aware
  • Thread.sleep() บน Background Thread OK แต่ไม่ควรทำบน UI Thread

ตัวอย่างปฏิบัติที่ดี (Best Practice)

public class MainActivity extends AppCompatActivity {
    private TextView textView;
    private volatile boolean running = false;
    private int counter = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.text);
        
        findViewById(R.id.runOn).setOnClickListener(v -> startCounter());
        findViewById(R.id.stopBtn).setOnClickListener(v -> stopCounter());
    }

    private void startCounter() {
        if (running) return;
        running = true;
        counter = 0;
        
        new Thread(() -> {
            while (running && counter < 1000) {
                final int current = counter++;
                runOnUiThread(() -> {
                    if (running) {  // ตรวจสอบอีกครั้งภายใน UI thread
                        textView.setText("#" + current);
                    }
                });
                try { Thread.sleep(300); } catch (InterruptedException ignored) {}
            }
        }).start();
    }

    private void stopCounter() {
        running = false;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        running = false;  // หยุด Background thread เมื่อ Activity หาย
    }
}

วิธีทดสอบ

  1. รันแอปบนอุปกรณ์จริง
  2. กดปุ่ม "Run"
  3. TextView จะนับขึ้นทุก 300ms: #1, #2, #3... จนถึง #1000
  4. ลองกด Back/ปิดแอปขณะนับ - ควรหยุดได้โดยไม่ Crash (ด้วย flag running)