欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

利用Android設計一個倒計時組件

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

1 背景

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

2 對比分析

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

2.1 是否是倒計時

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

通過源碼,我們也可以看出在ObservableInterval中實際也是進行了周期性調(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);
            // 以給定的初始時間延遲、周期時間進行周期性執(zhí)行
            worker.schedulePeriodically(is, initialDelay, period, unit);
        } else {
            // 以給定的初始時間延遲、周期時間進行周期性執(zhí)行
            Disposable d = sch.schedulePeriodicallyDirect(is, initialDelay, period, unit);
            is.setResource(d);
        }
    }

那么作為倒計時使用會有什么問題呢?

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

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

2.2 支持多任務

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

2.3 支持時間校準

CountDownTimer中每次onTick()方法回調(diào),都會重新計算下一次onTick的時間。其中主要優(yōu)化有2點,一是減去onTick執(zhí)行耗時;二是針對特殊情況(如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方法執(zhí)行耗時
        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先執(zhí)行,倒計時B后執(zhí)行,A和B的倒計時結(jié)束時間是一致的,那么我們假設倒計時時間為10秒,每1秒刷新一次,A在剩余10秒時執(zhí)行,B在剩余9.5秒執(zhí)行,當二者在同一頁面顯示時,就會刷新不一致,這個問題在我們新的倒計時組件中將得到解決,文章后面將會詳細說明。

2.5 支持延遲執(zhí)行

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

2.6 支持CPU休眠

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

3 需求目標

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

4 設計類結(jié)構(gòu)

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

5 具體實現(xiàn)

5.1 收口

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

  /**
   * 優(yōu)先級隊列,保存task,以 {@link Task#mExecuteTimeInNext} 作為基準
   */
  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); 錯誤示范
          return Long.compare(task1.mExecuteTimeInNext, task2.mExecuteTimeInNext);
        }
      });

5.2 支持與RxJava協(xié)同

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

  /**
   * 倒計時
   *
   * @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();
      }
    });
  }

  /**
   * 定時器
   *
   * @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 支持時間校準

不推薦使用RxJava中的interval,因為RxJava中的實現(xiàn)無法保障倒計時的準確執(zhí)行,如在手機CPU進入休眠之后再恢復到前臺。那么如何實現(xiàn)呢?這里借鑒了AndroidCountDownTimer的設計思路,在每次onTick后重新計算了下一次onTick的時間,比如前文提到的“CPU進入休眠”的情況,我們通過一個while循環(huán),計算出下一次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 支持同步刷新

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

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

5.5 支持延遲執(zhí)行

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

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

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

相關文章

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

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

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

    快速掌握Android屏幕的知識點

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

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

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

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

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

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

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

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

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

    Android進階從字節(jié)碼插樁技術了解美團熱修復實例詳解

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

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

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

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

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

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

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

最新評論