亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

1 背景

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

2 對(duì)比分析

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

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

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

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

問題二是在手機(jī)長時(shí)間息屏后,某些廠商會(huì)將CPU休眠,RxJavainterval操作符此時(shí)將被按下暫停鍵,當(dāng)APP再次回到前臺(tái),interval會(huì)繼續(xù)執(zhí)行,假設(shè)暫停時(shí)倒計(jì)時(shí)剩余100秒,回到前臺(tái)后實(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),都會(huì)重新計(jì)算下一次onTick的時(shí)間。其中主要優(yōu)化有2點(diǎn),一是減去onTick執(zhí)行耗時(shí);二是針對(duì)特殊情況(如1.2.1中提到的手機(jī)息屏后CPU休眠場(chǎng)景),對(duì)比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;
            // 針對(duì)特殊情況(如1.2.1中提到的手機(jī)息屏后CPU休眠場(chǎng)景)
            // 對(duì)比delay是否小于0,如果小于0則需要累加mCountdownInterval
            while (delay < 0) {
               delay += mCountdownInterval;
            }
        }
        sendMessageDelayed(obtainMessage(MSG), delay);
     }

2.4 支持同幀刷新

我們項(xiàng)目中有很多場(chǎ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í),就會(huì)刷新不一致,這個(gè)問題在我們新的倒計(jì)時(shí)組件中將得到解決,文章后面將會(huì)詳細(xì)說明。

2.5 支持延遲執(zhí)行

延遲1分鐘再執(zhí)行10秒的倒計(jì)時(shí)?Android中提供的CountDownTimer是做不到的,只能額外寫一個(gè)1分鐘的定時(shí)器,到時(shí)間后再啟動(dòng)倒計(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)先級(jí)隊(duì)列管理,內(nèi)部通過handler不斷從隊(duì)列中取task執(zhí)行。

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

5.1 收口

收口可以理解為進(jìn)行統(tǒng)一管理,這里我們通過一個(gè)優(yōu)先級(jí)隊(duì)列管理所有倒計(jì)時(shí)、定時(shí)器,優(yōu)先級(jí)隊(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接口的對(duì)象,在實(shí)現(xiàn)Comparator時(shí),因?yàn)?code>mExecuteTimeInNext的數(shù)據(jù)類型是long類型,而compare()方法返回的是int類型,如果直接使用二者相減再強(qiáng)制轉(zhuǎn)換為int,會(huì)有溢出的風(fēng)險(xiǎn),所以可以使用Long.compare()來實(shí)現(xiàn)大小比較。

  /**
   * 優(yōu)先級(jí)隊(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ù)到前臺(tái)。那么如何實(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 支持同步刷新

針對(duì)多個(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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Android自定義View實(shí)現(xiàn)隨機(jī)數(shù)驗(yàn)證碼

    Android自定義View實(shí)現(xiàn)隨機(jī)數(shù)驗(yàn)證碼

    這篇文章主要為大家詳細(xì)介紹了Android如何利用自定義View實(shí)現(xiàn)隨機(jī)數(shù)驗(yàn)證碼效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下
    2022-06-06
  • 快速掌握Android屏幕的知識(shí)點(diǎn)

    快速掌握Android屏幕的知識(shí)點(diǎn)

    相信不少設(shè)計(jì)師和工程師都被安卓設(shè)備紛繁的屏幕搞得暈頭轉(zhuǎn)向,我既做UI設(shè)計(jì),也做過一點(diǎn)安卓界面布局,剛好對(duì)這塊內(nèi)容比較熟悉,所以在此我將此部分知識(shí)重新梳理出來分享給大家!有需要的朋友們可以參考借鑒,下面來一起學(xué)習(xí)學(xué)習(xí)吧。
    2016-11-11
  • android全屏去掉title欄的多種實(shí)現(xiàn)方法

    android全屏去掉title欄的多種實(shí)現(xiàn)方法

    android全屏去掉title欄包括以下幾個(gè)部分:實(shí)現(xiàn)應(yīng)用中的所有activity都全屏/實(shí)現(xiàn)單個(gè)activity全屏/實(shí)現(xiàn)單個(gè)activity去掉title欄/自定義標(biāo)題內(nèi)容/自定義標(biāo)題布局等等感興趣的可參考下啊
    2013-02-02
  • 詳解Android全局異常的捕獲處理

    詳解Android全局異常的捕獲處理

    這篇文章主要為大家介紹了Android全局異常的捕獲處理,為什么要進(jìn)行捕獲處理,如何進(jìn)行捕獲處理,想要了解的朋友可以參考一下
    2016-01-01
  • android fm單體聲和立體聲的切換示例代碼

    android fm單體聲和立體聲的切換示例代碼

    切換是需要在一定的條件下滿足才會(huì)進(jìn)行切換,切換的條件和電臺(tái)的信號(hào)強(qiáng)度RSSI、信號(hào)穩(wěn)定性CQI等等都有關(guān)系
    2013-06-06
  • Android實(shí)現(xiàn)圖片輪播切換實(shí)例代碼

    Android實(shí)現(xiàn)圖片輪播切換實(shí)例代碼

    利用Android的ViewFlipper和AnimationUtils實(shí)現(xiàn)圖片帶有動(dòng)畫的輪播切換,其中當(dāng)點(diǎn)擊“上一張”圖片時(shí),切換到上一張圖片;當(dāng)點(diǎn)擊“下一張”圖片時(shí),切換到下一張圖片,本文給大家介紹Android實(shí)現(xiàn)圖片輪播切換實(shí)例代碼,需要的朋友參考下
    2015-12-12
  • Android進(jìn)階從字節(jié)碼插樁技術(shù)了解美團(tuán)熱修復(fù)實(shí)例詳解

    Android進(jìn)階從字節(jié)碼插樁技術(shù)了解美團(tuán)熱修復(fù)實(shí)例詳解

    這篇文章主要為大家介紹了Android進(jìn)階從字節(jié)碼插樁技術(shù)了解美團(tuán)熱修復(fù)實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • Android開發(fā)之TextView使用intent傳遞信息,實(shí)現(xiàn)注冊(cè)界面功能示例

    Android開發(fā)之TextView使用intent傳遞信息,實(shí)現(xiàn)注冊(cè)界面功能示例

    這篇文章主要介紹了Android開發(fā)之TextView使用intent傳遞信息,實(shí)現(xiàn)注冊(cè)界面功能,涉及Android使用intent傳值及界面布局等相關(guān)操作技巧,需要的朋友可以參考下
    2019-04-04
  • android編程之下拉刷新實(shí)現(xiàn)方法分析

    android編程之下拉刷新實(shí)現(xiàn)方法分析

    這篇文章主要介紹了android編程之下拉刷新實(shí)現(xiàn)方法,以實(shí)例形式詳細(xì)分析了Android編程中針對(duì)ListView下拉刷新的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-11-11
  • android控件Spinner(下拉列表)的使用例子

    android控件Spinner(下拉列表)的使用例子

    這篇文章主要給大家介紹了關(guān)于android控件Spinner(下拉列表)的使用例子,在Android開發(fā)中下拉框(Spinner)是常用的UI控件之一,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-11-11

最新評(píng)論