Android倒计时的三种实现方式

引言

目前,处于安全的考虑,大部分软件账户注册都需要短信验证码,而点击验证码之后倒计时获取变得习以为常,下面个人总结了三种实现倒计时的方法。供大家参考:

1 . 谷歌原生的计时器(CountDownTimer类)
首先,由于谷歌的开发者官网对国内开放了,所以我们先去看一下关于CountDownTimer的解释和应用:

Schedule a countdown until a time in the future, with regular notifications on intervals along the way. Example of showing a 30 second countdown in a text field:

//案例
 new CountDownTimer(30000, 1000) {

     public void onTick(long millisUntilFinished) {
         mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
     }

     public void onFinish() {
         mTextField.setText("done!");
     }
  }.start();

The calls to onTick(long) are synchronized to this object so that one call to onTick(long) won't ever occur before the previous callback is complete. This is only relevant when the implementation of onTick(long) takes an amount of time to execute that is significant compared to the countdown interval.

然而对于我这种四级没过的人来说理解是:同过设置构造函数中的两个参数,第一个是倒计时需要的时间,第二个是时间的间隔,在该函数中含有两个回调函数onTick(long)onFinish(),第一个回到函数回传过来剩余的时间,第二个是倒计时完成时回调的方法。下面我把我写的代码贴出来(这里我写的倒计时是六秒钟):

      countDownTimer = new CountDownTimer(6000, 1000) {
            int n = 6;

            @Override
            public void onTick(long l) {
                Log.e(TAG, "onTick: " + l);
                 tv_time.setClickable(false);
                tv_time.setText("(" + --n + ")");
            }

            @Override
            public void onFinish() {
                Log.e(TAG, "onFinish: " );
                 tv_time.setClickable(true);
                tv_time.setText("重新获取");
                n = 6;
            }
        };


     countDownTimer.start();//开始计时

下面我们看一下log:

Android倒计时的三种实现方式_第1张图片

真是不看不知道一看吓一跳,这怎么原生的每次的时间间隔不是1000毫秒整呢,每次差几毫秒,而且在最后一次就调用了onFinish()方法,导致计数0无法看到了,如果你不是处女座这个就可以了,不过对于我这个喜欢perfect的man来说,显然不行,既然别人靠不住,还是自己动手写吧:

2 . 利用Handler机制
对于Handler,想必做Android的都很熟悉了吧,但是Handler有一个坏处就是由于创建的Handler会隐式持有外部类即是MainActivity的强引用。由于计时会在单独的线程中执行,当MainActivity需要销毁时,由于handler持有MainActivity的强引用而GC无法回收内存,容易造成内存泄露,所以我写了一个内部静态类继承Handler,因为静态内部类不再持有外部类的强引用。另外,创建了一个MainActivity的弱引用,能够判断在计时过程中该Activity是否已经销毁:

 /*
     * 创建一个计时的线程,利用handler发送计时的消息
     * 
     */

    private void startCountTime() {
        new Thread() {
            @Override
            public void run() {
                super.run();
                for (int i = 6; i >= 0; i--) {
                    Message mes = myHandler.obtainMessage();
                    mes.arg1 = i;
                    mes.what = 0x00;//计时中的标志
                    myHandler.sendMessage(mes);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                Message mes = myHandler.obtainMessage();
                mes.what = 0x11;//计时完成的标志
                myHandler.sendMessage(mes);

            }
        }.start();
    }

/**
     * 自定义一个带有MainActivity弱引用的静态Handler
     * 防止内存泄漏
     */
    private static class MyHandler extends Handler {

        WeakReference wr;

        public MyHandler(MainActivity activity) {
            //实例化弱引用
            wr = new WeakReference(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.e(TAG, "handleMessage: " + msg.arg1 + ":" + msg.what);
            MainActivity activity = wr.get();
            if (activity != null) {//判断该activity是否已经销毁
                switch (msg.what) {

                    case 0x00:
                     tv_time.setClickable(false);
                        tv_time.setText("(" + msg.arg1 + ")");
                        break;
                    case 0x11:
                    tv_time.setClickable(true);
                        tv_time.setText("重新获取");
                        break;
                }
            }

        }
    }


startCountTime();//开始计时

3 . 利用RxJava、RxAndroid实现倒计时:

/**
     * RxJava实现倒计时
     * @param times 需要计时的总时间(默认间隔为1s)
     */

    private void startCount(final int times) {

        Observable.interval(0, 1, TimeUnit.SECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .map(new Func1(){
                    @Override
                    public Object call(Long aLong) {
                    //将1 2 3 4 5 6 转换成 6 5 4 3 2 1
                        return times - aLong.intValue();
                    }
                  })
                .take(times+1) //过滤掉times+1后面的数字
                .subscribe(new Subscriber() {
                    @Override
                    public void onNext(Object o) {
                        Log.e(TAG, "onNext: "+o.toString() );
                        tv_time.setClickable(false);
                        tv_time.setText("(" + o.toString() + ")");
                    }

                    @Override
                    public void onStart() {
                        super.onStart();
                        Log.e(TAG, "onStart: 开始计时" );
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e(TAG, "onError: "+e.getMessage() );
                    }

                    @Override
                    public void onCompleted() {
                        tv_time.setClickable(true);
                        tv_time.setText("重新获取");
                    }
                });

    } 
  

对于这种方式,我这里说一下RxJava的操作符interval和take的作用,interval是间隔一定时间从0开始计数,本身运行在Schedulers.computation() 线程内,其中含有三个参数,我先把这三个参数解释的源码贴出来:

 * @param initialDelay
     *            the initial delay time to wait before emitting the first value of 0L
     * @param period
     *            the period of time between emissions of the subsequent numbers
     * @param unit
     *            the time unit for both {@code initialDelay} and {@code period}
     * @param scheduler
     *            the Scheduler on which the waiting happens and items are emitted
     * @return an Observable that emits a 0L after the {@code initialDelay} and ever increasing numbers after
     *         each {@code period} of time thereafter, while running on the given Scheduler
     * @see "http://reactivex.io/documentation/operators/interval.html">ReactiveX operators documentation: Interval
     * @since 1.0.12
     */
    public static Observable interval(long initialDelay, long period, TimeUnit unit, Scheduler scheduler) {
        return create(new OnSubscribeTimerPeriodically(initialDelay, period, unit, scheduler));
    }

原来第一个参数是在第一个值发射前的时间延时,第二个是每次计数的时间间隔,第三个是增加的时间单元(分、秒、毫秒、微秒等),而take表示只保留前面的若干项数据,这里表示之保留7前面的数字(1,2,3,4,5,6)。
好了,计时器的三种实现方式就介绍完了,点击查看GitHub源码

你可能感兴趣的:(android)