ภาพรวม
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 หาย
}
}
วิธีทดสอบ
- รันแอปบนอุปกรณ์จริง
- กดปุ่ม "Run"
- TextView จะนับขึ้นทุก 300ms: #1, #2, #3... จนถึง #1000
- ลองกด Back/ปิดแอปขณะนับ - ควรหยุดได้โดยไม่ Crash (ด้วย flag running)