開發者嘗試使用 java.util.Timer 每 5 秒更新一次 TextView,代碼如下:
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
textTitle.setText(title); // 直接在背景執行緒操作 UI
textArtist.setText(artist);
}
}, 0, 5000);
結果只有第一次更新成功,後續無反應,且 Logcat 可能出現 CalledFromWrongThreadException。
TimerTask.run() 運行於背景執行緒,直接調用 setText() 違反 Android 單執行緒模型。Thread.sleep() 會阻塞背景執行緒,但不影響主執行緒,導致邏輯混亂。Timer 任務異常時會終止整個排程器,後續任務不再執行。Handler 綁定主執行緒 Looper,確保 Runnable 在 UI 執行緒運行:
private static final int MSG_UPDATE_UI = 1;
private static final long UPDATE_INTERVAL = 5000;
private final Handler uiHandler = new Handler(Looper.getMainLooper(), msg -> {
if (msg.what == MSG_UPDATE_UI) {
textTitle.setText(title);
textArtist.setText(artist);
// 循環發送下一次更新
uiHandler.sendEmptyMessageDelayed(MSG_UPDATE_UI, UPDATE_INTERVAL);
return true;
}
return false;
});
@Override
protected void onResume() {
super.onResume();
uiHandler.sendEmptyMessageDelayed(MSG_UPDATE_UI, UPDATE_INTERVAL);
}
@Override
protected void onPause() {
super.onPause();
uiHandler.removeMessages(MSG_UPDATE_UI); // 防止洩漏與崩潰
}
若專案使用 Kotlin,結構化並發更簡潔:
lifecycleScope.launch {
while (isActive) {
withContext(Dispatchers.Main) {
textTitle.text = title
textArtist.text = artist
}
delay(5000)
}
}
生命周期感知自動取消,無需手動移除回調。
| 方案 | 優點 | 缺點 |
|---|---|---|
| Handler + postDelayed | API 兼容性好,輕量 | 需手動管理生命周期 |
| ScheduledExecutorService + runOnUiThread | 標準 Java API | 樣板代碼多 |
| RxJava interval + observeOn | 函數式鏈式調用 | 引入重型依賴 |
| Kotlin Coroutines | 結構化並發,生命周期綁定 | 需 Kotlin 環境 |
拋棄 Timer 進行 UI 定時更新,改用 Handler 或協程,嚴守「主執行緒操作 UI」鐵律,並妥善處理 Activity/Fragment 生命周期,才能實現穩定、無洩漏的週期性界面刷新。