千家信息网

如何利用Android设计一个倒计时组件

发表于:2025-02-08 作者:千家信息网编辑
千家信息网最后更新 2025年02月08日,今天就跟大家聊聊有关如何利用Android设计一个倒计时组件,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。1 背景我们在项目中经常有倒计时的场
千家信息网最后更新 2025年02月08日如何利用Android设计一个倒计时组件

今天就跟大家聊聊有关如何利用Android设计一个倒计时组件,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。

1 背景

我们在项目中经常有倒计时的场景,比如活动倒计时、抢红包倒计时等等。通常情况下,我们实现倒计时的方案有Android中的CountDownTimerJava中自带的TimerScheduleExcutorServiceRxJava中的interval操作符。 在实际项目中存在2个典型的问题,一是倒计时的实现形式不统一,不统一的原因分为认知不一致、每种倒计时方案各有优势;二是存在大量倒计时同时执行。

2 对比分析

关于几种方案的用法不是本文要讨论的重点,在此我们通过表格的方式列出来各自的特性,表格底部的CountDownTimerManager就是本文要为大家介绍的新鲜出炉的中心化倒计时组件。

2.1 是否是倒计时

Rx中的interval操作符是每隔一段时间会发送一个事件,可以说是一个计数器,而不是倒计时,在实际项目中会发现很多同学都把它当做倒计时在使用。下图是RxJava官方对interval的图解:

interval.png *The Interval operator returns an Observable that emits an infinite sequence of ascending integers, with a constant interval of time of your choosing between emissions.(简单理解就是固定间隔时间进行回调)

通过源码,我们也可以看出在ObservableInterval中实际也是进行了周期性调度。

public final class ObservableInterval extends Observable {    @Override    public void subscribeActual(Observer observer) {        IntervalObserver is = new IntervalObserver(observer);        observer.onSubscribe(is);        Scheduler sch = scheduler;        if (sch instanceof TrampolineScheduler) {            Worker worker = sch.createWorker();            is.setResource(worker);            // 以给定的初始时间延迟、周期时间进行周期性执行            worker.schedulePeriodically(is, initialDelay, period, unit);        } else {            // 以给定的初始时间延迟、周期时间进行周期性执行            Disposable d = sch.schedulePeriodicallyDirect(is, initialDelay, period, unit);            is.setResource(d);        }    }

那么作为倒计时使用会有什么问题呢?

问题一是回调可能不准确,假设倒计时9.5秒,每1秒刷新一次view,该怎么设置回调间隔时间呢?

问题二是在手机长时间息屏后,某些厂商会将CPU休眠,RxJavainterval操作符此时将被按下暂停键,当APP再次回到前台,interval会继续执行,假设暂停时倒计时剩余100秒,回到前台后实际只有10秒了,但是interval还是从100继续执行。

2.2 支持多任务

Timer是单线程串行执行多任务,假设taskA设定1秒后执行,taskB设定2秒后执行,实际上taskB是在taskA执行结束后才执行taskB,所以taskB的执行时间是在第3秒,所以Timer只算是伪支持多任务。ScheduledExecutorService是利用线程池支持了多任务调度的。

2.3 支持时间校准

CountDownTimer中每次onTick()方法回调,都会重新计算下一次onTick的时间。其中主要优化有2点,一是减去onTick执行耗时;二是针对特殊情况(如1.2.1中提到的手机息屏后CPU休眠场景),对比delay是否小于0,如果小于0则需要累加mCountdownInterval。

long lastTickStart = SystemClock.elapsedRealtime();    onTick(millisLeft);    long lastTickDuration = SystemClock.elapsedRealtime() - lastTickStart;    long delay;    if (millisLeft < mCountdownInterval) {        // 减去上面onTick方法执行耗时        delay = millisLeft - lastTickDuration;        if (delay < 0) {            delay = 0;        } else {            delay = mCountdownInterval - lastTickDuration;            // 针对特殊情况(如1.2.1中提到的手机息屏后CPU休眠场景)            // 对比delay是否小于0,如果小于0则需要累加mCountdownInterval            while (delay < 0) {               delay += mCountdownInterval;            }        }        sendMessageDelayed(obtainMessage(MSG), delay);     }

2.4 支持同帧刷新

我们项目中有很多场景是这样的:

倒计时A先执行,倒计时B后执行,A和B的倒计时结束时间是一致的,那么我们假设倒计时时间为10秒,每1秒刷新一次,A在剩余10秒时执行,B在剩余9.5秒执行,当二者在同一页面显示时,就会刷新不一致,这个问题在我们新的倒计时组件中将得到解决,文章后面将会详细说明。

2.5 支持延迟执行

延迟1分钟再执行10秒的倒计时?Android中提供的CountDownTimer是做不到的,只能额外写一个1分钟的定时器,到时间后再启动倒计时。

2.6 支持CPU休眠

我们这里提到的支持CPU休眠,并不是指CPU休眠期间倒计时仍能得到执行,而是在CPU休眠后能够恢复正常执行。和1.2.3中提到的时间校准类似,解决了时间校准的问题也就支持了CPU休眠的特性。

3 需求目标

  • 设计一个中心化的倒计时组件,同时支持上述提到的一系列特性。

  • 接口易于调用,使用者只需关注计时回调的逻辑。

4 设计类结构

CountDownTimer采用静态内部类形式实现单例,暴露countdown()timer()方法供业务方ClientA/ClientB/ClientC等调用,Task是抽象任务,每次调用countdown()timer()后都生成一个task,交给优先级队列管理,内部通过handler不断从队列中取task执行。

5 具体实现

5.1 收口

收口可以理解为进行统一管理,这里我们通过一个优先级队列管理所有倒计时、定时器,优先级队列可以直接采用Java中已有的数据结构PriorityQueue,设置队列大小默认为5,根据task中的mExecuteTimeInNext进行正序排序。这里有一个特别需要注意的点,PriorityQueue需要传入实现Comparator接口的对象,在实现Comparator时,因为mExecuteTimeInNext的数据类型是long类型,而compare()方法返回的是int类型,如果直接使用二者相减再强制转换为int,会有溢出的风险,所以可以使用Long.compare()来实现大小比较。

  /**   * 优先级队列,保存task,以 {@link Task#mExecuteTimeInNext} 作为基准   */  private final Queue mTaskQueue = new PriorityQueue<>(DEFAULT_INITIAL_CAPACITY,      new Comparator() {        @Override        public int compare(Task task1, Task task2) {          // return (int) (task1.mExecuteTimeInNext - task2.mExecuteTimeInNext); 错误示范          return Long.compare(task1.mExecuteTimeInNext, task2.mExecuteTimeInNext);        }      });

5.2 支持与RxJava协同

提供倒计时countdown、定时器timer操作符,直接返回Observable,方便与RxJava框架协同。

  /**   * 倒计时   *   * @param millisInFuture    Millis since epoch when alarm should stop.   * @param countDownInterval The interval in millis that the user receives callbacks.   * @param delayMillis       The delay time in millis.   * @return Observable   */  public synchronized Observable countdown(long millisInFuture, long countDownInterval, long delayMillis) {    AtomicReference taskAtomicReference = new AtomicReference<>();    return Observable.create((ObservableOnSubscribe) emitter -> {      Task newTask = new Task(millisInFuture, countDownInterval, delayMillis, emitter);      taskAtomicReference.set(newTask);      synchronized (CountDownTimerManager.this) {        Task topTask = mTaskQueue.peek();        if (topTask == null || newTask.mExecuteTimeInNext < topTask.mExecuteTimeInNext) {          cancel();        }        mTaskQueue.offer(newTask);        if (mCancelled) {          start();        }      }    }).doOnDispose(() -> {      if (taskAtomicReference.get() != null) {        taskAtomicReference.get().dispose();      }    });  }
  /**   * 定时器   *   * @param millisInFuture   Millis since epoch when alarm should stop.   * @return Observable   */  public synchronized Observable timer(long millisInFuture) {    return countdown(0, 0, millisInFuture);  }  private synchronized void remove(Task task) {    mTaskQueue.remove(task);    if (mTaskQueue.size() == 0) {      cancel();    }  }

5.3 支持时间校准

不推荐使用RxJava中的interval,因为RxJava中的实现无法保障倒计时的准确执行,如在手机CPU进入休眠之后再恢复到前台。那么如何实现呢?这里借鉴了AndroidCountDownTimer的设计思路,在每次onTick后重新计算了下一次onTick的时间,比如前文提到的"CPU进入休眠"的情况,我们通过一个while循环,计算出下一次onTick的时间(其条件是大于当前时间)。

  mTaskQueue.poll();          if (!task.isDisposed()) {            if (stopMillisLeft <= 0 || task.mCountdownInterval == 0) {              task.mDisposed = true;              task.mEmitter.onNext(0L);              task.mEmitter.onComplete();            } else {              task.mEmitter.onNext(stopMillisLeft % task.mCountdownInterval == 0 ? stopMillisLeft                  : (stopMillisLeft / task.mCountdownInterval + 1) * task.mCountdownInterval);              // 时间校准               // special case:              // user's onTick took more than interval to complete              // cpu slept              do {                task.mExecuteTimeInNext += task.mCountdownInterval;              } while (task.mExecuteTimeInNext < SystemClock.elapsedRealtime());              mTaskQueue.offer(task);            }          }

5.4 支持同步刷新

针对多个倒计时在同一时刻结束的情况,优化了刷新不同步的问题。 mExecuteTimeInNext是下一次任务执行时间,假设倒计时剩余时间为9.5秒,每1秒刷新,那么下一次的执行时间则是在0.5秒之后。

private Task(long millisInFuture, long countDownInterval, long delayMillis,        @NonNull ObservableEmitter emitter) {      mCountdownInterval = countDownInterval;      // 计算出下次执行的时间      mExecuteTimeInNext = SystemClock.elapsedRealtime() + (mCountdownInterval == 0 ? 0          : millisInFuture % mCountdownInterval) + delayMillis;      mStopTimeInFuture = SystemClock.elapsedRealtime() + millisInFuture + delayMillis;      mEmitter = emitter;    }

5.5 支持延迟执行

在计算下次执行的时间时,加上了delayMillis,这样就支持了延迟执行。

private Task(long millisInFuture, long countDownInterval, long delayMillis,        @NonNull ObservableEmitter emitter) {      mCountdownInterval = countDownInterval;      // 计算出下次执行的时间      mExecuteTimeInNext = SystemClock.elapsedRealtime() + (mCountdownInterval == 0 ? 0          : millisInFuture % mCountdownInterval) + delayMillis;      mStopTimeInFuture = SystemClock.elapsedRealtime() + millisInFuture + delayMillis;      mEmitter = emitter;    }

看完上述内容,你们对如何利用Android设计一个倒计时组件有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注行业资讯频道,感谢大家的支持。

倒计时 时间 支持 休眠 问题 任务 队列 延迟 组件 设计 周期 实际 情况 是在 优先级 场景 定时器 手机 操作符 方法 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 ibm服务器断电了进不去系统 公共信息网络安全监察部 浙里办服务器和网络错误 聊天软件开发难度 软件开发的标志设计 网络安全自查整改总结报告 网络安全技术人员哪里有 网络技术专业需要电脑吗 审计sqlserver数据库 金苗宝登陆显示无法连接服务器 扬州网络安全什么价格 华科网络安全公司 描述网络安全工作 java收费管理软件开发 方舟服务器难开荒吗 王者争霸pvp服务器ip 上海富友金融网络技术6 手机网络安全教育视频藏语 腾讯会议服务器怎么检测 网络安全模式下杀毒很彻底吗 英语科技小短文关于网络安全 怎么把数据写到数据库 树型网络安全防御 ipv6全球有几个根服务器 宁波系统软件开发流程 服务器虚拟资源管理软件 科技中的通讯网络技术顺口溜视频 萤石云连接服务器异常是怎么回事 探究计算机网络技术的应用与发展 服务器管理员登录密码忘记
0