Android语言基础教程(57)Android基本组件之计时器:Android计时器:用对了爽到飞起,用错了卡到崩溃!

  • 时间:2025-11-08 02:11 作者: 来源: 阅读:0
  • 扫一扫,手机访问
摘要:还记得你第一次做轮播图时的崩溃吗?图片死活不自动切换,或者切换时卡得亲妈都不认识?又或者,倒计时功能写着写着就内存泄漏了? 别问我是怎么知道的(抹泪)。今天咱们就来深扒Android里这个最常用,却又最容易被“想当然”使用的组件——计时器。 一、计时器:App里的“隐形劳动力” 先别急着敲代码。咱们想想,计时器在App里到底在扮演什么角色? 轮播图:每3秒自动滑到下一张美女(哦不,是产品图

还记得你第一次做轮播图时的崩溃吗?图片死活不自动切换,或者切换时卡得亲妈都不认识?又或者,倒计时功能写着写着就内存泄漏了?

别问我是怎么知道的(抹泪)。今天咱们就来深扒Android里这个最常用,却又最容易被“想当然”使用的组件——计时器。

一、计时器:App里的“隐形劳动力”

先别急着敲代码。咱们想想,计时器在App里到底在扮演什么角色?

轮播图:每3秒自动滑到下一张美女(哦不,是产品图)消息刷新:微信那个“正在输入…”的鬼畜效果游戏技能CD:亚瑟的一技能还有2秒就好!倒计时:双11秒杀,“还有01:23:45就要开始了!”

看出来没?计时器本质上就是个“时间触发器”——到点了,就捅一下你的代码:“喂,该干活了!”

但在Android这个大江湖里,能干活的时间触发器可不止一个。接下来就请出我们的三位参赛选手!

二、三大计时器,谁是你的Mr. Right?

选手1:老古董Handler —— 基础但靠谱

这哥们是Android里的老前辈了,虽然年纪大,但地位稳如泰山。

它的工作流程是这样的:



// 1. 先雇个保姆(Handler)
private Handler mHandler = new Handler();
 
// 2. 让保姆定期叫你起床
private Runnable mRunnable = new Runnable() {
    @Override
    public void run() {
        // 这里写你要定期干的事
        updateUI(); // 比如更新界面
        
        // 重要!干完活记得再安排下一次
        mHandler.postDelayed(this, 1000); // 1秒后再来
    }
};
 
// 3. 开始计时
mHandler.postDelayed(mRunnable, 1000);
 
// 4. 想停止的时候
mHandler.removeCallbacks(mRunnable);

Handler的内心独白:“年轻人别老想着花里胡哨的,我这一套方法用了多少年,稳定!”

适用场景:简单的UI更新,比如进度条、动画效果。

坑点警告

忘掉 removeCallbacks?恭喜你,内存泄漏大礼包一份!在子线程里直接操作UI?Crash正在赶来的路上!

选手2:Timer & TimerTask —— 来自Java的“外来和尚”

这位是Java原生家族派来的代表,在很多Java项目里混得风生水起。

基本操作:



// 搭个班子
private Timer mTimer;
private TimerTask mTimerTask;
 
private void startTimer() {
    mTimer = new Timer();
    mTimerTask = new TimerTask() {
        @Override
        public void run() {
            // 注意:这里是在子线程!
            doBackgroundWork(); // 适合后台计算
            // 想更新UI?得找Handler帮忙
        }
    };
    
    // 立即开始,每隔1秒执行一次
    mTimer.schedule(mTimerTask, 0, 1000);
}
 
// 记得清理现场!
private void stopTimer() {
    if (mTimer != null) {
        mTimer.cancel();
        mTimer = null;
    }
}

Timer的傲娇宣言:“我可是正儿八经的Java血统,精度高,功能强!”

但是(敲黑板):

默认不在主线程,更新UI得绕路 Timer.cancel()有时候会耍小性子,不是100%可靠异常处理不当?整个Timer直接罢工给你看

选手3:CountDownTimer —— Android官方“亲儿子”

这是Google看大家用前两位用得实在太痛苦,于是亲手打造的“官方解决方案”。

看这优雅的写法:



// 倒计时60秒,每隔1秒回调一次
CountDownTimer countDownTimer = new CountDownTimer(60000, 1000) {
    @Override
    public void onTick(long millisUntilFinished) {
        // 每次倒计时时的回调
        long seconds = millisUntilFinished / 1000;
        mTextView.setText(seconds + "秒后世界毁灭");
    }
 
    @Override
    public void onFinish() {
        // 倒计时结束
        mTextView.setText("嘭!世界毁灭了!");
    }
};
 
// 开始倒计时
countDownTimer.start();
 
// 想提前结束?
countDownTimer.cancel();

CountDownTimer的优势:

专门为倒计时场景设计,API简单到哭自动在主线程回调,UI操作so easy生命周期管理相对省心

局限性: 只能用于倒计时,想做周期性任务?还是回去找Handler吧。

三、实战:做一个不被老板骂的秒表

光说不练假把式,来做个实际项目——健身App用的秒表!

需求分析:

开始/暂停/重置功能精确到0.1秒更新即使手机锁屏也要能继续计时(服务保活)

上代码!



public class StopwatchActivity extends AppCompatActivity {
    
    private TextView mTimeText;
    private Button mStartBtn, mPauseBtn, mResetBtn;
    
    private long mStartTime = 0;
    private boolean mIsRunning = false;
    
    // 选用我们的老将:Handler
    private Handler mHandler = new Handler();
    private Runnable mUpdateTimeRunnable = new Runnable() {
        @Override
        public void run() {
            if (mIsRunning) {
                long currentTime = System.currentTimeMillis() - mStartTime;
                updateTimeDisplay(currentTime);
                
                // 0.1秒后再次更新
                mHandler.postDelayed(this, 100);
            }
        }
    };
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_stopwatch);
        
        initViews();
        updateButtons();
    }
    
    private void initViews() {
        mTimeText = findViewById(R.id.time_text);
        mStartBtn = findViewById(R.id.start_btn);
        mPauseBtn = findViewById(R.id.pause_btn);
        mResetBtn = findViewById(R.id.reset_btn);
        
        mStartBtn.setOnClickListener(v -> start());
        mPauseBtn.setOnClickListener(v -> pause());
        mResetBtn.setOnClickListener(v -> reset());
    }
    
    private void start() {
        if (!mIsRunning) {
            mStartTime = System.currentTimeMillis() - (mStartTime == 0 ? 0 : 
                (System.currentTimeMillis() - mStartTime));
            mIsRunning = true;
            mHandler.post(mUpdateTimeRunnable);
            updateButtons();
        }
    }
    
    private void pause() {
        mIsRunning = false;
        mHandler.removeCallbacks(mUpdateTimeRunnable);
        updateButtons();
    }
    
    private void reset() {
        mIsRunning = false;
        mHandler.removeCallbacks(mUpdateTimeRunnable);
        mStartTime = 0;
        updateTimeDisplay(0);
        updateButtons();
    }
    
    private void updateTimeDisplay(long millis) {
        int totalSeconds = (int) (millis / 1000);
        int minutes = totalSeconds / 60;
        int seconds = totalSeconds % 60;
        int tenths = (int) (millis % 1000) / 100;
        
        String time = String.format("%02d:%02d.%d", minutes, seconds, tenths);
        mTimeText.setText(time);
    }
    
    private void updateButtons() {
        mStartBtn.setEnabled(!mIsRunning);
        mPauseBtn.setEnabled(mIsRunning);
        mResetBtn.setEnabled(!mIsRunning && mStartTime != 0);
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 重要!避免内存泄漏
        mHandler.removeCallbacks(mUpdateTimeRunnable);
    }
}

这个实现的关键点:

System.currentTimeMillis()做时间基准,比依赖固定的时间间隔更准确每次点击暂停时记录当前时间,恢复时重新计算起始时间在 onDestroy里一定要清理Handler,这是程序员的自我修养

四、高级玩法:让计时器更“聪明”

技巧1:应对界面销毁与重建

你的App可能会因为旋转屏幕而重启Activity,这时候计时器状态就丢了。怎么办?

解决方案: ViewModel + LiveData



public class StopwatchViewModel extends ViewModel {
    private MutableLiveData<Long> mElapsedTime = new MutableLiveData<>();
    private long mStartTime;
    private boolean mIsRunning = false;
    
    public void start() {
        if (!mIsRunning) {
            mStartTime = System.currentTimeMillis() - 
                (mElapsedTime.getValue() != null ? mElapsedTime.getValue() : 0);
            mIsRunning = true;
            
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (mIsRunning) {
                        mElapsedTime.setValue(System.currentTimeMillis() - mStartTime);
                        new Handler().postDelayed(this, 100);
                    }
                }
            }, 100);
        }
    }
    
    // 省略pause、reset等方法...
    
    @Override
    protected void onCleared() {
        super.onCleared();
        mIsRunning = false;
    }
}

技巧2:后台持续计时

用户切到后台,秒表还得继续跑啊!这就需要服务了:



public class TimerService extends Service {
    // 前台服务保证不被杀
    // 用Notification让用户知道你在运行
    // 结合BroadcastReceiver更新界面
}

不过要小心:Android对后台服务的限制越来越严,得按规矩来!

五、避坑指南:前辈们踩过的雷

内存泄漏第一名:Handler忘记移除回调 → 解决:用静态内部类+弱引用精度问题 postDelayed不保证精确时间,系统忙时会延迟 → 关键场景用 SystemClock.elapsedRealtime()线程混乱:在Timer里直接更新UI → 记住:只有主线程能动UI!生命周期失控:Activity都销毁了,计时器还在跑 → 结合生命周期回调管理电量杀手:无脑用AlarmManager做高频定时 → 考虑用WorkManager替代

六、总结:如何选择你的计时器?

简单UI动画/周期性任务 → Handler + Runnable后台计算/非UI定时 → Timer & TimerTask倒计时场景 → CountDownTimer(首选!)需要跨界面/后台存活 → Service + Handler现代架构推荐 → ViewModel + LiveData + Handler

记住,没有最好的计时器,只有最合适的场景。选对了,你的App丝般顺滑;选错了,就等着测试同事提着Bug来找你吧!

现在,去优雅地处理你的时间任务吧!毕竟,好的计时器就像好的伴侣——它在该出现的时候出现,不该出现的时候默默等待,而且永远不会让你卡住(希望如此)!


延伸思考:其实计时器的选择折射出Android开发的演进——从最初的Handler打天下,到后来各种架构组件的出现,再到如今对性能、电量的极致追求。你的代码风格,是不是也该跟着进化了呢?

(小声说:如果这篇文章帮到了你,请在心里默默点个赞~如果还有问题,欢迎在评论区“骚扰”我!)

  • 全部评论(0)
最新发布的资讯信息
【系统环境|】Java从入门到精通:2025最新版系统学习路线与实战指南(2025-11-08 02:14)
【系统环境|】域名到手,如何开启网站搭建之旅?2025最全指南!(2025-11-08 02:13)
【系统环境|】Windows部署Dify+Ollama十大致命坑!技术小白自救指南(2025-11-08 02:13)
【系统环境|】在linux上安装ollama(2025-11-08 02:12)
【系统环境|】Kubernetes 存储学习全景指南(2025-11-08 02:12)
【系统环境|】Android语言基础教程(57)Android基本组件之计时器:Android计时器:用对了爽到飞起,用错了卡到崩溃!(2025-11-08 02:11)
【系统环境|】Node.js v16 版本安装(2025-11-08 02:11)
【系统环境|】Windows 系统安装 Composer 详细教程(2025-11-08 02:10)
【系统环境|】利用python实现的弹窗冬日祝福(无需安装额外库)(2025-11-08 02:10)
【系统环境|】Node.JS 版本管理工具 Fnm 安装及配置(Windows)(2025-11-08 02:09)
手机二维码手机访问领取大礼包
返回顶部