在 Android 開發中,許多初學者會嘗試使用 Thread 配合 Runnable 實作簡單的秒表功能。然而,直接呼叫 Thread.stop() 來停止執行緒是極不推薦的做法,因為此方法已被標記為 Deprecated(棄用),且會導致嚴重的線程安全問題,例如資料不一致、鎖釋放異常等。本文將分析一個典型的「秒表無法停止」案例,並提供正確的解決方案。
原始代碼中,開發者建立了一個 Thread 實例 currentThread,並在 run() 方法中使用 while(true) 無限迴圈,配合 sleep(1000) 實現每秒更新 UI。停止按鈕綁定了 currentThread.stop(),但這在現代 Android 版本中往往無效,甚至拋出異常。
// 錯誤示範:直接呼叫 stop()
b2.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
currentThread.stop(); // 已棄用,不可靠
}
});
UnsupportedOperationException。while(true) 沒有中斷判斷,導致執行緒無法優雅結束。Handler 傳遞訊息更新 TextView 是可行的,但配合錯誤的停止機制會造成記憶體洩漏或 ANR。最佳實踐是引入一個 volatile boolean 標誌位(如 shouldRun),在迴圈中檢查該標誌,並在需要停止時將其設為 false。配合 Thread.interrupt() 可打斷 sleep() 狀態,實現響應式停止。
public class StopWatchActivity extends AppCompatActivity implements Runnable {
private TextView tvTimer;
private Button btnStart, btnStop, btnReset;
private Thread timerThread;
private volatile boolean shouldRun = false; // volatile 保證可見性
private int elapsedSeconds = 0;
private Handler uiHandler = new Handler(Looper.getMainLooper());
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_stopwatch);
tvTimer = findViewById(R.id.tv_timer);
btnStart = findViewById(R.id.btn_start);
btnStop = findViewById(R.id.btn_stop);
btnReset = findViewById(R.id.btn_reset);
btnStart.setOnClickListener(v -> startTimer());
btnStop.setOnClickListener(v -> stopTimer());
btnReset.setOnClickListener(v -> resetTimer());
}
private void startTimer() {
if (timerThread == null || !timerThread.isAlive()) {
shouldRun = true;
timerThread = new Thread(this);
timerThread.start();
}
}
private void stopTimer() {
shouldRun = false;
if (timerThread != null) {
timerThread.interrupt(); // 打斷 sleep,讓執行緒盡快檢查標誌位
}
}
private void resetTimer() {
stopTimer();
elapsedSeconds = 0;
uiHandler.post(() -> tvTimer.setText("0"));
}
@Override
public void run() {
while (shouldRun) {
try {
Thread.sleep(1000);
if (!shouldRun) break; // 雙重檢查,防止 sleep 後繼續
elapsedSeconds++;
uiHandler.post(() -> tvTimer.setText(String.valueOf(elapsedSeconds)));
} catch (InterruptedException e) {
// 執行緒被 interrupt,正常退出迴圈
Thread.currentThread().interrupt(); // 保留中斷狀態
break;
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
stopTimer(); // 確保 Activity 銷毀時執行緒正確停止
}
}
shouldRun 的可見性,讀取到最新值。Handler 或 runOnUiThread 更新 View。對於現代 Android 開發,建議使用 Kotlin 協程 或 Flow 實現計時器,代碼更簡潔、結構化並發處理更安全。例如:
// Kotlin 協程寫法(簡略)
lifecycleScope.launch {
while (isActive) {
delay(1000)
_elapsedSeconds.value++
}
}
這樣自動綁定生命週期,無需手動管理執行緒停止。
秒表無法停止的根本原因在於使用了已棄用且不安全的 Thread.stop()。採用 標誌位 + interrupt() 模式是 Java 基礎執行緒控制的標準做法;而在新專案中,優先考慮協程等現代異步框架,能從架構層面避免此類問題。