Android开发:启动Activity后计时器触发线程操作视图错误
问题原因分析
你遇到的Only the original thread that created a view hierarchy can touch its views错误,核心原因是:TimerTask的run()方法是在后台线程执行的——哪怕你在UI线程调用startTimer(),TimerTask的任务逻辑依然会在非UI线程运行,而Android规定只有创建View的原始线程(即UI线程)才能更新View,所以直接在TimerTask里调用timerText.setText()会触发这个错误。你之前用runOnUiThread包裹startTimer()完全没用,因为问题出在TimerTask内部的UI更新操作。
解决方案
下面给出几种可靠的修复方式,按推荐程度排序:
方案一:直接在TimerTask中包裹UI更新操作
修改startTimer()方法,仅把更新TextView的代码放到runOnUiThread里:
private void startTimer() { timerTask = new TimerTask() { @Override public void run() { time++; // 仅将UI更新逻辑切换到UI线程 runOnUiThread(new Runnable() { @Override public void run() { timerText.setText(getTimerText()); } }); } }; timer.scheduleAtFixedRate(timerTask, 0, 1000); }
方案二:用Handler替代Timer(更推荐,适配Android线程模型)
Handler天生和UI线程绑定,用它实现定时循环更安全,还能避免Timer可能出现的线程泄漏问题:
private Handler handler = new Handler(Looper.getMainLooper()); private Runnable timerRunnable; Double time = 0.0; @Override protected void onCreate(Bundle savedInstanceState) { // ... 原有初始化代码不变 timerText = (TextView) findViewById(R.id.timerText); startTimer(); } private void startTimer() { timerRunnable = new Runnable() { @Override public void run() { time++; timerText.setText(getTimerText()); // 每隔1秒重复执行当前任务 handler.postDelayed(this, 1000); } }; // 立即启动任务 handler.post(timerRunnable); } // 必须在Activity销毁时移除回调,防止内存泄漏 @Override protected void onDestroy() { super.onDestroy(); if (handler != null && timerRunnable != null) { handler.removeCallbacks(timerRunnable); } }
方案三:使用AndroidX的CountDownTimer(变种实现递增计时)
CountDownTimer是Android官方提供的定时工具,虽然设计用于倒计时,但可以改造成递增计时:
private CountDownTimer countDownTimer; private long totalSeconds = 0; private void startTimer() { // 设置总时长为24小时(足够日常使用),每隔1秒触发一次回调 countDownTimer = new CountDownTimer(86400000, 1000) { @Override public void onTick(long millisUntilFinished) { totalSeconds++; int seconds = (int) (totalSeconds % 60); int minutes = (int) ((totalSeconds / 60) % 60); timerText.setText(formatTime(seconds, minutes)); } @Override public void onFinish() { // 时长耗尽后重新启动,实现无限递增 startTimer(); } }.start(); } // 销毁时取消定时器 @Override protected void onDestroy() { super.onDestroy(); if (countDownTimer != null) { countDownTimer.cancel(); } }
注意事项
- 所有涉及View更新的代码必须放在UI线程执行
- 无论用哪种方案,都要在Activity销毁时停止定时任务,避免内存泄漏
内容的提问来源于stack exchange,提问作者Acecryz
相关产品推荐
相关产品推荐

