美文网首页AndroidBinder机制
Android Timer、CountDownTimer、Ala

Android Timer、CountDownTimer、Ala

作者: 合肥黑 | 来源:发表于2016-09-01 17:17 被阅读3696次
    一、Timer TimerTask

    参考Java中的Timer和TimerTask在Android中的用法

    在开发中我们有时会有这样的需求,即在固定的每隔一段时间执行某一个任务。比如UI上的控件需要随着时间改变,我们可以使用Java为我们提供的计时器的工具类,即Timer和TimerTask。

    Timer是一个普通的类,其中有几个重要的方法;而TimerTask则是一个抽象类,其中有一个抽象方法run(),类似线程中的run()方法,我们使用Timer创建一个他的对象,然后使用这对象的schedule方法来完成这种间隔的操作。

    //true 说明这个timer以daemon方式运行(优先级低,程序结束timer也自动结束) 
    java.util.Timer timer = new java.util.Timer(true);
    
    TimerTask task = new TimerTask() {
       public void run() {
       //每次需要执行的代码放到这里面。   
       }   
    };
    
    //以下是几种调度task的方法:
    
    //time为Date类型:在指定时间执行一次。
    timer.schedule(task, time);
    
    //firstTime为Date类型,period为long,表示从firstTime时刻开始,每隔period毫秒执行一次。
    timer.schedule(task, firstTime, period);   
    
    //delay 为long类型:从现在起过delay毫秒执行一次。
    timer.schedule(task, delay);
    
    //delay为long,period为long:从现在起过delay毫秒以后,每隔period毫秒执行一次。
    timer.schedule(task, delay, period);
    
    //该任务每隔2秒更新主线程的UI(在主线程的TextView显示最新的系统时间System.currentTimeMillis())。
    package zhangphil.timertask;
    
    import java.util.Timer;
    import java.util.TimerTask;
    
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.app.Activity;
    import android.widget.TextView;
    
    public class MainActivity extends Activity {
    
        private Timer timer;
        private TimerTask task;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            final TextView tv = (TextView) findViewById(R.id.textView);
    
            final int WHAT = 102;
            final Handler handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                    case WHAT:
                        tv.setText(msg.obj + "");
                        break;
                    }
                }
            };
    
            task = new TimerTask() {
                @Override
                public void run() {
                    Message message = new Message();
                    message.what = WHAT;
                    message.obj = System.currentTimeMillis();
                    handler.sendMessage(message);
                }
            };
    
            timer = new Timer();
            // 参数:
            // 1000,延时1秒后执行。
            // 2000,每隔2秒执行1次task。
            timer.schedule(task, 1000, 2000);
        }
    
        @Override
        protected void onStop() {
            super.onStop();
    
            // 暂停
            // timer.cancel();
            // task.cancel();
        }
    }
    
    

    schedule方法有三个参数
    第一个参数就是TimerTask类型的对象,我们实现TimerTask的run()方法就是要周期执行的一个任务;
    第二个参数有两种类型,第一种是long类型,表示多长时间后开始执行,另一种是Date类型,表示从那个时间后开始执行;
    第三个参数就是执行的周期,为long类型。

    schedule方法还有一种两个参数的执行重载,第一个参数仍然是TimerTask,第二个表示为long的形式表示多长时间后执行一次,为Date就表示某个时间后执行一次。

    Timer就是一个线程,使用schedule方法完成对TimerTask的调度,多个TimerTask可以共用一个Timer,也就是说Timer对象调用一次schedule方法就是创建了一个线程,并且调用一次schedule后TimerTask是无限制的循环下去的,使用Timer的cancel()停止操作。当然同一个Timer执行一次cancel()方法后,所有Timer线程都被终止。

    若要在TimerTask中更新主线程UI,鉴于Android编程模型不允许在非主线程中更新主线程UI,因此需要结合Android的Handler实现在Java的TimerTask中更新主线程UI。

    二、ScheduledThreadPoolExecutor

    public static ExecutorService newScheduledThreadPool(int corePoolSize){
    return new ThreadPoolExecutor(corePoolSize,Integer.MAX_VALUE,0L,TimeUnit.SECONDS,new DelayedWorkQueue<Runnable>());
    }

    ScheduledThreadPoolExecutor核心线程数量固定,非核心线程数没有限制。主要用于执行定时任务和具有固定周期的重复任务。
    
    参考[Java 并发专题 : Timer的缺陷 用ScheduledExecutorService替代](http://blog.csdn.net/lmj623565791/article/details/27109467)
    **以前在项目中也经常使用定时器,比如每隔一段时间清理项目中的一些垃圾文件,每个一段时间进行数据清洗;然而Timer是存在一些缺陷的,因为Timer在执行定时任务时只会创建一个线程,所以如果存在多个任务,且任务时间过长,超过了两个任务的间隔时间,会发生一些缺陷.**
    
    1.定义了两个任务,预计是第一个任务1s后执行,第二个任务3s后执行
    

    package com.zhy.concurrency.timer;

    import java.util.Timer;
    import java.util.TimerTask;

    public class TimerTest
    {
    private static long start;

    public static void main(String[] args) throws Exception
    {
    
        TimerTask task1 = new TimerTask()
        {
            @Override
            public void run()
            {
    
                System.out.println("task1 invoked ! "
                        + (System.currentTimeMillis() - start));
                try
                {
                    Thread.sleep(3000);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
    
            }
        };
        TimerTask task2 = new TimerTask()
        {
            @Override
            public void run()
            {
                System.out.println("task2 invoked ! "
                        + (System.currentTimeMillis() - start));
            }
        };
        Timer timer = new Timer();
        start = System.currentTimeMillis();
        timer.schedule(task1, 1000);
        timer.schedule(task2, 3000);
    
    }
    

    }

    运行结果:
    

    task1 invoked ! 1000
    task2 invoked ! 4000

    task2实际上是4后才执行,正因为Timer内部是一个线程,而任务1所需的时间超过了两个任务间的间隔导致。下面使用ScheduledThreadPool解决这个问题:
    

    package com.zhy.concurrency.timer;

    import java.util.TimerTask;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;

    public class ScheduledThreadPoolExecutorTest
    {
    private static long start;

    public static void main(String[] args)
    {
        /**
         * 使用工厂方法初始化一个ScheduledThreadPool
         */
        ScheduledExecutorService newScheduledThreadPool = Executors
                .newScheduledThreadPool(2);
        
        TimerTask task1 = new TimerTask()
        {
            @Override
            public void run()
            {
                try
                {
    
                    System.out.println("task1 invoked ! "
                            + (System.currentTimeMillis() - start));
                    Thread.sleep(3000);
                } catch (Exception e)
                {
                    e.printStackTrace();
                }
    
            }
        };
    
        TimerTask task2 = new TimerTask()
        {
            @Override
            public void run()
            {
                System.out.println("task2 invoked ! "
                        + (System.currentTimeMillis() - start));
            }
        };
        start = System.currentTimeMillis();
        newScheduledThreadPool.schedule(task1, 1000, TimeUnit.MILLISECONDS);
        newScheduledThreadPool.schedule(task2, 3000, TimeUnit.MILLISECONDS);
    }
    

    }

    2.如果TimerTask抛出RuntimeException,Timer会停止所有任务的运行:
    

    package com.zhy.concurrency.timer;

    import java.util.Date;
    import java.util.Timer;
    import java.util.TimerTask;

    public class ScheduledThreadPoolDemo01
    {

    public static void main(String[] args) throws InterruptedException
    {
    
        final TimerTask task1 = new TimerTask()
        {
    
            @Override
            public void run()
            {
                throw new RuntimeException();
            }
        };
    
        final TimerTask task2 = new TimerTask()
        {
    
            @Override
            public void run()
            {
                System.out.println("task2 invoked!");
            }
        };
        
        Timer timer = new Timer();
        timer.schedule(task1, 100);
        timer.scheduleAtFixedRate(task2, new Date(), 1000);
        
        
    
    }
    

    }

    上面有两个任务,任务1抛出一个运行时的异常,任务2周期性的执行某个操作,输出结果:
    

    task2 invoked!
    Exception in thread "Timer-0" java.lang.RuntimeException
    at com.zhy.concurrency.timer.ScheduledThreadPoolDemo01$1.run(ScheduledThreadPoolDemo01.java:24)
    at java.util.TimerThread.mainLoop(Timer.java:512)
    at java.util.TimerThread.run(Timer.java:462)

    由于任务1的一次,任务2也停止运行了。。。下面使用ScheduledExecutorService解决这个问题:
    

    package com.zhy.concurrency.timer;

    import java.util.Date;
    import java.util.Timer;
    import java.util.TimerTask;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;

    public class ScheduledThreadPoolDemo01
    {

    public static void main(String[] args) throws InterruptedException
    {
    
        final TimerTask task1 = new TimerTask()
        {
    
            @Override
            public void run()
            {
                throw new RuntimeException();
            }
        };
    
        final TimerTask task2 = new TimerTask()
        {
    
            @Override
            public void run()
            {
                System.out.println("task2 invoked!");
            }
        };
        
        
        
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        pool.schedule(task1, 100, TimeUnit.MILLISECONDS);
        pool.scheduleAtFixedRate(task2, 0 , 1000, TimeUnit.MILLISECONDS);
    
    }
    

    }

    代码基本一致,但是ScheduledExecutorService可以保证,task1出现异常时,不影响task2的运行:
    

    task2 invoked!
    task2 invoked!
    task2 invoked!
    task2 invoked!
    task2 invoked!

    #####三、AlarmManager
    >Java的Timer类可以用来计划需要循环执行的任务。简单的说,一个Timer内部封装装了“一个Thread”和“一个TimerTask队列”,这个队列按照一定的方式将任务排队处理。封装的Thread在Timer的构造方法调用时被启动,这个Thread的run方法按照条件去循环这个TimerTask队列,然后调用TimerTask的run方法。
    但是,**如果CPU进入了休眠状态,那么这个thread将会因为失去CPU时间片而阻塞,从而造成我们需要的定时任务失效**。上述定时任务失效的场景分析:假设定时任务的条件是到了时间xx:yy才能执行,但由于cpu休眠造成线程阻塞的关系,当前系统时间超过了这个时间,即便CPU从终端中恢复了,那么由于条件不满足,定时任务在这一次自然就失效了。
    解决方案是:它**需要用WakeLock让CPU 保持唤醒状态。那么问题就来了,这样会大量消耗手机电量(CPU唤醒和屏幕唤醒不是同一概念)**,大大减短手机待机时间。这种方式不能满足我们的需求。
    注:TimerTask实现Runnable接口,但它的run方法只是简单的在Timer中封装的Thread中被调用,并未将其放在其它线程中执行。也就是说timer是单线程执行的,那么问题来了,为何要这么费劲的封装一个Runnable接口又不进行多线程调用?我的答案是,老外只是想要表示它是可执行的方法。
    关于休眠,可以参考
    [Android 关于休眠引发的几个血案](http://www.ithtw.com/437.html)
    [Android手机休眠后时间不准确的解决方案](http://blog.csdn.net/t12x3456/article/details/7826811)
    AlarmManager是Android 系统封装的用于管理RTC的模块,RTC(Real Time Clock) 是一个独立的硬件时钟,可以在 CPU 休眠时正常运行,在预设的时间到达时,通过中断唤醒CPU。这意味着,如果我们用 AlarmManager 来定时执行任务,CPU 可以正常的休眠,只有在需要运行任务时醒来一段很短的时间。
    
    以下参考[Android基础入门教程——10.5 AlarmManager(闹钟服务)](http://blog.csdn.net/coder_pig/article/details/49423531)
    
    **核心流程如下:**
    * `AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); `
    获得系统提供的AlarmManager服务的对象
    * Intent设置要启动的组件: 
    `Intent intent = new Intent(MainActivity.this, ClockActivity.class);`
    * PendingIntent对象设置动作,启动的是Activity还是Service,又或者是广播! 
    `PendingIntent pi = PendingIntent.getActivity(MainActivity.this, 0, intent, 0);`
    * 调用AlarmManager的set( )方法设置单次闹钟的闹钟类型,启动时间以及PendingIntent对象! 
    `alarmManager.set(AlarmManager.RTC_WAKEUP,c.getTimeInMillis(), pi);`
    
    

    //10秒钟后执行一个任务
    AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
    long triggerAtTime = SystemClock.elapsedRealtime() + 10 * 1000;
    manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendingIntent);

    **相关方法:**
    * set(int type,long startTime,PendingIntent pi):一次性闹钟
    * setRepeating(int type,long startTime,long intervalTime,PendingIntent pi): 
    重复性闹钟,和3有区别,3闹钟间隔时间不固定
    * setInexactRepeating(int type,long startTime,long intervalTime,PendingIntent pi): 
    重复性闹钟,时间不固定
    * cancel(PendingIntent pi):取消AlarmManager的定时服务
    * getNextAlarmClock():得到下一个闹钟,返回值AlarmManager.AlarmClockInfo
    * setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) 
    和set方法类似,这个闹钟运行在系统处于低电模式时有效
    * setExact(int type, long triggerAtMillis, PendingIntent operation): 
    在规定的时间精确的执行闹钟,比set方法设置的精度更高
    * setTime(long millis):设置系统墙上的时间
    * setTimeZone(String timeZone):设置系统持续的默认时区
    * setWindow(int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation): 
    设置一个闹钟在给定的时间窗触发。类似于set,该方法允许应用程序精确地控制操作系统调 整闹钟触发时间的程度。
    
    **关键参数讲解:**
    * Type(闹钟类型): 
    有五个可选值: 
      * AlarmManager.ELAPSED_REALTIME: 
    闹钟在手机睡眠状态下不可用,该状态下闹钟使用相对时间(相对于系统启动开始),状态值为3; 
      * AlarmManager.ELAPSED_REALTIME_WAKEUP 
    闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟也使用相对时间,状态值为2; 
      * AlarmManager.RTC 
    闹钟在睡眠状态下不可用,该状态下闹钟使用绝对时间,即当前系统时间,状态值为1; 
      * AlarmManager.RTC_WAKEUP 
    表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟使用绝对时间,状态值为0; 
      * AlarmManager.POWER_OFF_WAKEUP 
    表示闹钟在手机关机状态下也能正常进行提示功能,所以是5个状态中用的最多的状态之一,该状态下闹钟也是用绝对时间,状态值为4;不过本状态好像受SDK版本影响,某些版本并不支持;
    * startTime:闹钟的第一次执行时间,以毫秒为单位,可以自定义时间,不过一般使用当前时间。 需要注意的是,本属性与第一个属性(type)密切相关,如果第一个参数对应的闹钟使用的是相对时间 (ELAPSED_REALTIME和ELAPSED_REALTIME_WAKEUP),那么本属性就得使用相对时间相对于系统启动时间来说),比如当前时间就表示为:SystemClock.elapsedRealtime(); 如果第一个参数对应的闹钟使用的是绝对时间(RTC、RTC_WAKEUP、POWER_OFF_WAKEUP), 那么本属性就得使用绝对时间,比如当前时间就表示 为:System.currentTimeMillis()。
    * intervalTime:表示两次闹钟执行的间隔时间,也是以毫秒为单位.
    * PendingIntent:绑定了闹钟的执行动作,比如发送一个广播、给出提示等等。 
    PendingIntent是Intent的封装类。需要注意的是,
      * 如果是通过启动服务来实现闹钟提示的话,PendingIntent对象的获取就应该采用Pending.getService (Context c,int i,Intent intent,int j)方法;
      * 如果是通过广播来实现闹钟提示的话,PendingIntent对象的获取就应该采用 PendingIntent.getBroadcast (Context c,int i,Intent intent,int j)方法;
      * 如果是采用Activity的方式来实现闹钟提示的话,PendingIntent对象的获取就应该采用 
    PendingIntent.getActivity(Context c,int i,Intent intent,int j)方法。
    
    如果这三种方法错用了的话,虽然不会报错,但是看不到闹钟提示效果。
    
    下面是一个每隔一小时就会在后台执行定时任务的服务。
    

    public class LongRunningService extends Service{
    public IBinder onBind(Intent intent){
    return null;
    }

    public int onStartCommand(Intent intent, int flags, int startId){
    new Thread(new Runnable(){
    public void run(){
    Log.d("LongRunningService","executed at"+new Date().toString());
    }
    }).start();
    }
    AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
    int anHour = 60 * 60 * 1000;
    long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
    Intent i = new Intent(this,AlarmReceiver.class);
    PendingIntent pi = PendingIntent.getBroadcast(this,0,i,0);
    manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pi);
    return super.onStartCommand(intent,flags,startId);
    }

    public class AlarmReceiver extends BroadcastReceiver{
    public void onReceive(Context context, Intent intent){
    Intent i = new Intent(context, LongRunningService.class);
    context.startService(i);
    }
    }

    public class MainActivity extends Activity{
    protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    Intent intent = new Intent(this,LongRunningService);
    startService(intent);
    }
    }

    #####四、CountDownTimer
    参考
    [Android实现倒计时之使用CountDownTimer](http://blog.csdn.net/qq_20785431/article/details/51571300)
    [[Android] CountDownTimer 简单用法与源码解析](http://blog.qiji.tech/archives/7485)
    >在开发中会经常用到倒计时这个功能,包括给手机发送验证码等等,之前我的做法都是使用Handler + Timer +TimerTask来实现,现在发现了这个类,果断抛弃之前的做法,相信还是有很多人和我一样一开始不知道Android已经帮我们封装好了一个叫CountDownTimer的类。`CountDownTimer timer = new CountDownTimer(10000,1000);`以毫秒为单位,第一个参数是指从开始调用start()方法到倒计时完成的时候onFinish()方法被调用这段时间的毫秒数,也就是倒计时总的时间;第二个参数表示间隔多少毫秒调用一次 onTick方法,例如间隔1000毫秒。在调用的时候直接使用`timer.start();`
    
    

    //共有4个方法,前两个抽象方法需要重写
    public abstract void onTick(long millisUntilFinished);//固定间隔调用
    public abstract void onFinish();//倒计时完成时被调用
    public synchronized final void cancel();//取消倒计时,当再次启动会重新开始倒计时
    public synchronized final CountDownTimer start();//启动倒计时
    //该类的成员变量与成员函数均较少,功能实现的关键部分在于 mHandler,下面看 mHandler 的源码:
    private Handler mHandler = new Handler() {

    @Override public void handleMessage(Message msg) {

    synchronized (CountDownTimer.this) {
      if (mCancelled) {
        return;
      }
    
      final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
    
      if (millisLeft <= 0) {
        onFinish();
      } else if (millisLeft < mCountdownInterval) {
        // no tick, just delay until done
        sendMessageDelayed(obtainMessage(MSG), millisLeft);
      } else {
        long lastTickStart = SystemClock.elapsedRealtime();
        onTick(millisLeft);
    
        // take into account user's onTick taking time to execute
        long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();
    
        // special case: user's onTick took more than interval to
        // complete, skip to next interval
        while (delay < 0) delay += mCountdownInterval;
    
        sendMessageDelayed(obtainMessage(MSG), delay);
      }
    }
    

    }
    };

    
    ![内部流程](http:https://img.haomeiwen.com/i2354823/46005a2dd429b052.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    看一个使用例子:
    

    package com.per.countdowntimer;

    import android.app.Activity;
    import android.os.Bundle;
    import android.os.CountDownTimer;
    import android.view.View;
    import android.widget.TextView;

    public class MainActivity extends Activity {
    private TextView mTvShow;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTvShow = (TextView) findViewById(R.id.show);
    }
    
    /**
     * 取消倒计时
     * @param v
     */
    public void oncancel(View v) {
        timer.cancel();
    }
    
    /**
     * 开始倒计时
     * @param v
     */
    public void restart(View v) {
        timer.start();
    }
    
    private CountDownTimer timer = new CountDownTimer(10000, 1000) {
    
        @Override
        public void onTick(long millisUntilFinished) {
            mTvShow.setText((millisUntilFinished / 1000) + "秒后可重发");
        }
    
        @Override
        public void onFinish() {
            mTvShow.setEnabled(true);
            mTvShow.setText("获取验证码");
        }
    };
    

    }

    #####五、new Handler().postDelayed()
    该方法就是利用我们常说的消息处理器。该方法原理就是在主线程中创建一个Handler消息处理器,然后利用其中的一个postDelayed(Runnable r, long delayMillis)方法,该方法第一个参数需要传入一个Runnable接口,并实现run()方法,第二个参数就是延迟多少时间将run()方法中的代码通过一个消息发送给消息队列,然后在主线程中执行这个消息中的代码,即是run方法中的代码,从而实现在主线程中更新界面UI。
    贴代码吧:
    

    new Handler().postDelayed(new Runnable() {//在当前线程(也即主线程中)开启一个消息处理器,并在3秒后在主线程中执行,从而来更新UI
    @Override
    public void run() {
    //有关更新UI的代码
    }
    }, 3000);//3秒后发送

    相关文章

      网友评论

      • kaixinzzy:逻辑太清晰了,多谢分享。
      • 丶Mdzz:为啥我设置一分钟重复,总是两分钟才调用
      • 勤知:总结的很好。
        合肥黑:@勤知 谢谢:-P

      本文标题:Android Timer、CountDownTimer、Ala

      本文链接:https://www.haomeiwen.com/subject/fqocettx.html