最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

利用Android設(shè)計(jì)一個(gè)倒計(jì)時(shí)組件

 更新時(shí)間:2021年09月25日 14:24:54   投稿:zhanglei  
在很多電商工作項(xiàng)目中經(jīng)常有倒計(jì)時(shí)的場景,比如活動倒計(jì)時(shí)、搶紅包倒計(jì)時(shí)等等,今天小編就帶大家來學(xué)習(xí)如何利用Android設(shè)計(jì)倒計(jì)時(shí)組件,感興趣的小伙伴一起奧

1 背景

我們在項(xiàng)目中經(jīng)常有倒計(jì)時(shí)的場景,比如活動倒計(jì)時(shí)、搶紅包倒計(jì)時(shí)等等。通常情況下,我們實(shí)現(xiàn)倒計(jì)時(shí)的方案有Android中的CountDownTimerJava中自帶的TimerScheduleExcutorService、RxJava中的interval操作符。 在實(shí)際項(xiàng)目中存在2個(gè)典型的問題,一是倒計(jì)時(shí)的實(shí)現(xiàn)形式不統(tǒng)一,不統(tǒng)一的原因分為認(rèn)知不一致、每種倒計(jì)時(shí)方案各有優(yōu)勢;二是存在大量倒計(jì)時(shí)同時(shí)執(zhí)行。

2 對比分析

關(guān)于幾種方案的用法不是本文要討論的重點(diǎn),在此我們通過表格的方式列出來各自的特性,表格底部的CountDownTimerManager就是本文要為大家介紹的新鮮出爐的中心化倒計(jì)時(shí)組件。

2.1 是否是倒計(jì)時(shí)

Rx中的interval操作符是每隔一段時(shí)間會發(fā)送一個(gè)事件,可以說是一個(gè)計(jì)數(shù)器,而不是倒計(jì)時(shí),在實(shí)際項(xiàng)目中會發(fā)現(xiàn)很多同學(xué)都把它當(dāng)做倒計(jì)時(shí)在使用。下圖是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.(簡單理解就是固定間隔時(shí)間進(jìn)行回調(diào))

通過源碼,我們也可以看出在ObservableInterval中實(shí)際也是進(jìn)行了周期性調(diào)度。

public final class ObservableInterval extends Observable<Long> {

    @Override
    public void subscribeActual(Observer<? super Long> observer) {
        IntervalObserver is = new IntervalObserver(observer);
        observer.onSubscribe(is);

        Scheduler sch = scheduler;

        if (sch instanceof TrampolineScheduler) {
            Worker worker = sch.createWorker();
            is.setResource(worker);
            // 以給定的初始時(shí)間延遲、周期時(shí)間進(jìn)行周期性執(zhí)行
            worker.schedulePeriodically(is, initialDelay, period, unit);
        } else {
            // 以給定的初始時(shí)間延遲、周期時(shí)間進(jìn)行周期性執(zhí)行
            Disposable d = sch.schedulePeriodicallyDirect(is, initialDelay, period, unit);
            is.setResource(d);
        }
    }

那么作為倒計(jì)時(shí)使用會有什么問題呢?

問題一是回調(diào)可能不準(zhǔn)確,假設(shè)倒計(jì)時(shí)9.5秒,每1秒刷新一次view,該怎么設(shè)置回調(diào)間隔時(shí)間呢?

問題二是在手機(jī)長時(shí)間息屏后,某些廠商會將CPU休眠,RxJavainterval操作符此時(shí)將被按下暫停鍵,當(dāng)APP再次回到前臺,interval會繼續(xù)執(zhí)行,假設(shè)暫停時(shí)倒計(jì)時(shí)剩余100秒,回到前臺后實(shí)際只有10秒了,但是interval還是從100繼續(xù)執(zhí)行。

2.2 支持多任務(wù)

Timer是單線程串行執(zhí)行多任務(wù),假設(shè)taskA設(shè)定1秒后執(zhí)行,taskB設(shè)定2秒后執(zhí)行,實(shí)際上taskB是在taskA執(zhí)行結(jié)束后才執(zhí)行taskB,所以taskB的執(zhí)行時(shí)間是在第3秒,所以Timer只算是偽支持多任務(wù)。ScheduledExecutorService是利用線程池支持了多任務(wù)調(diào)度的。

2.3 支持時(shí)間校準(zhǔn)

CountDownTimer中每次onTick()方法回調(diào),都會重新計(jì)算下一次onTick的時(shí)間。其中主要優(yōu)化有2點(diǎn),一是減去onTick執(zhí)行耗時(shí);二是針對特殊情況(如1.2.1中提到的手機(jī)息屏后CPU休眠場景),對比delay是否小于0,如果小于0則需要累加mCountdownInterval。

    long lastTickStart = SystemClock.elapsedRealtime();
    onTick(millisLeft);
    long lastTickDuration = SystemClock.elapsedRealtime() - lastTickStart;
    long delay;
    if (millisLeft < mCountdownInterval) {
        // 減去上面onTick方法執(zhí)行耗時(shí)
        delay = millisLeft - lastTickDuration;
        if (delay < 0) {
            delay = 0;
        } else {
            delay = mCountdownInterval - lastTickDuration;
            // 針對特殊情況(如1.2.1中提到的手機(jī)息屏后CPU休眠場景)
            // 對比delay是否小于0,如果小于0則需要累加mCountdownInterval
            while (delay < 0) {
               delay += mCountdownInterval;
            }
        }
        sendMessageDelayed(obtainMessage(MSG), delay);
     }

2.4 支持同幀刷新

我們項(xiàng)目中有很多場景是這樣的:

倒計(jì)時(shí)A先執(zhí)行,倒計(jì)時(shí)B后執(zhí)行,A和B的倒計(jì)時(shí)結(jié)束時(shí)間是一致的,那么我們假設(shè)倒計(jì)時(shí)時(shí)間為10秒,每1秒刷新一次,A在剩余10秒時(shí)執(zhí)行,B在剩余9.5秒執(zhí)行,當(dāng)二者在同一頁面顯示時(shí),就會刷新不一致,這個(gè)問題在我們新的倒計(jì)時(shí)組件中將得到解決,文章后面將會詳細(xì)說明。

2.5 支持延遲執(zhí)行

延遲1分鐘再執(zhí)行10秒的倒計(jì)時(shí)?Android中提供的CountDownTimer是做不到的,只能額外寫一個(gè)1分鐘的定時(shí)器,到時(shí)間后再啟動倒計(jì)時(shí)。

2.6 支持CPU休眠

我們這里提到的支持CPU休眠,并不是指CPU休眠期間倒計(jì)時(shí)仍能得到執(zhí)行,而是在CPU休眠后能夠恢復(fù)正常執(zhí)行。和1.2.3中提到的時(shí)間校準(zhǔn)類似,解決了時(shí)間校準(zhǔn)的問題也就支持了CPU休眠的特性。

3 需求目標(biāo)

  • 設(shè)計(jì)一個(gè)中心化的倒計(jì)時(shí)組件,同時(shí)支持上述提到的一系列特性。
  •  接口易于調(diào)用,使用者只需關(guān)注計(jì)時(shí)回調(diào)的邏輯。

4 設(shè)計(jì)類結(jié)構(gòu)

CountDownTimer采用靜態(tài)內(nèi)部類形式實(shí)現(xiàn)單例,暴露countdown() 、timer()方法供業(yè)務(wù)方ClientA/ClientB/ClientC等調(diào)用,Task是抽象任務(wù),每次調(diào)用countdown() 、timer()后都生成一個(gè)task,交給優(yōu)先級隊(duì)列管理,內(nèi)部通過handler不斷從隊(duì)列中取task執(zhí)行。

5 具體實(shí)現(xiàn)

5.1 收口

收口可以理解為進(jìn)行統(tǒng)一管理,這里我們通過一個(gè)優(yōu)先級隊(duì)列管理所有倒計(jì)時(shí)、定時(shí)器,優(yōu)先級隊(duì)列可以直接采用Java中已有的數(shù)據(jù)結(jié)構(gòu)PriorityQueue,設(shè)置隊(duì)列大小默認(rèn)為5,根據(jù)task中的mExecuteTimeInNext進(jìn)行正序排序。這里有一個(gè)特別需要注意的點(diǎn),PriorityQueue需要傳入實(shí)現(xiàn)Comparator接口的對象,在實(shí)現(xiàn)Comparator時(shí),因?yàn)?code>mExecuteTimeInNext的數(shù)據(jù)類型是long類型,而compare()方法返回的是int類型,如果直接使用二者相減再強(qiáng)制轉(zhuǎn)換為int,會有溢出的風(fēng)險(xiǎn),所以可以使用Long.compare()來實(shí)現(xiàn)大小比較。

  /**
   * 優(yōu)先級隊(duì)列,保存task,以 {@link Task#mExecuteTimeInNext} 作為基準(zhǔn)
   */
  private final Queue<Task> mTaskQueue = new PriorityQueue<>(DEFAULT_INITIAL_CAPACITY,
      new Comparator<Task>() {
        @Override
        public int compare(Task task1, Task task2) {
          // return (int) (task1.mExecuteTimeInNext - task2.mExecuteTimeInNext); 錯(cuò)誤示范
          return Long.compare(task1.mExecuteTimeInNext, task2.mExecuteTimeInNext);
        }
      });

5.2 支持與RxJava協(xié)同

提供倒計(jì)時(shí)countdown、定時(shí)器timer操作符,直接返回Observable,方便與RxJava框架協(xié)同。

  /**
   * 倒計(jì)時(shí)
   *
   * @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<Long> countdown(long millisInFuture, long countDownInterval, long delayMillis) {
    AtomicReference<Task> taskAtomicReference = new AtomicReference<>();
    return Observable.create((ObservableOnSubscribe<Long>) 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();
      }
    });
  }

  /**
   * 定時(shí)器
   *
   * @param millisInFuture   Millis since epoch when alarm should stop.
   * @return Observable
   */
  public synchronized Observable<Long> timer(long millisInFuture) {
    return countdown(0, 0, millisInFuture);
  }

  private synchronized void remove(Task task) {
    mTaskQueue.remove(task);
    if (mTaskQueue.size() == 0) {
      cancel();
    }
  }

5.3 支持時(shí)間校準(zhǔn)

不推薦使用RxJava中的interval,因?yàn)镽xJava中的實(shí)現(xiàn)無法保障倒計(jì)時(shí)的準(zhǔn)確執(zhí)行,如在手機(jī)CPU進(jìn)入休眠之后再恢復(fù)到前臺。那么如何實(shí)現(xiàn)呢?這里借鑒了AndroidCountDownTimer的設(shè)計(jì)思路,在每次onTick后重新計(jì)算了下一次onTick的時(shí)間,比如前文提到的“CPU進(jìn)入休眠”的情況,我們通過一個(gè)while循環(huán),計(jì)算出下一次onTick的時(shí)間(其條件是大于當(dāng)前時(shí)間)。

          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);
              // 時(shí)間校準(zhǔn) 
              // 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 支持同步刷新

針對多個(gè)倒計(jì)時(shí)在同一時(shí)刻結(jié)束的情況,優(yōu)化了刷新不同步的問題。 mExecuteTimeInNext是下一次任務(wù)執(zhí)行時(shí)間,假設(shè)倒計(jì)時(shí)剩余時(shí)間為9.5秒,每1秒刷新,那么下一次的執(zhí)行時(shí)間則是在0.5秒之后。

    private Task(long millisInFuture, long countDownInterval, long delayMillis,
        @NonNull ObservableEmitter<Long> emitter) {
      mCountdownInterval = countDownInterval;
      // 計(jì)算出下次執(zhí)行的時(shí)間
      mExecuteTimeInNext = SystemClock.elapsedRealtime() + (mCountdownInterval == 0 ? 0
          : millisInFuture % mCountdownInterval) + delayMillis;
      mStopTimeInFuture = SystemClock.elapsedRealtime() + millisInFuture + delayMillis;
      mEmitter = emitter;
    }

5.5 支持延遲執(zhí)行

在計(jì)算下次執(zhí)行的時(shí)間時(shí),加上了delayMillis,這樣就支持了延遲執(zhí)行。

    private Task(long millisInFuture, long countDownInterval, long delayMillis,
        @NonNull ObservableEmitter<Long> emitter) {
      mCountdownInterval = countDownInterval;
      // 計(jì)算出下次執(zhí)行的時(shí)間
      mExecuteTimeInNext = SystemClock.elapsedRealtime() + (mCountdownInterval == 0 ? 0
          : millisInFuture % mCountdownInterval) + delayMillis;
      mStopTimeInFuture = SystemClock.elapsedRealtime() + millisInFuture + delayMillis;
      mEmitter = emitter;
    }

到此這篇關(guān)于利用Android設(shè)計(jì)一個(gè)倒計(jì)時(shí)組件的文章就介紹到這了,更多相關(guān)利用Android設(shè)計(jì)倒計(jì)時(shí)組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論

昭通市| 宕昌县| 龙口市| 香港 | 黑水县| 新龙县| 加查县| 克什克腾旗| 古丈县| 鄂州市| 福建省| 厦门市| 正定县| 镇坪县| 茂名市| 惠安县| 尼玛县| 分宜县| 怀宁县| 资讯 | 丰镇市| 阿拉尔市| 定结县| 建始县| 东丰县| 申扎县| 博兴县| 肃北| 大理市| 溧水县| 丁青县| 台安县| 浦北县| 卓资县| 威海市| 读书| 佛山市| 印江| 赤壁市| 潼关县| 乡宁县|