Android定时器,CountDownTimer,AlarmManager

一,Timer TimerTask

参考的Java中的定时器和TimerTask的在安卓中的用法

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

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

//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();
    }
}

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

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

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

若要在TimerTask的中更新主线程UI,鉴于Android的编程模型不允许在非主线程中更新主线程UI,因此需要结合安卓的处理程序实现在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并发专题:计时器的缺陷使用ScheduledExecutorService替代
以前在项目中也经常使用定时器,比如每隔一段时间清理项目中的一些垃圾文件,每个一段时间进行数据清洗;然而计时器是存在一些缺陷的,因为定时器在执行定时任务时只会创建一个线程,所以如果存在多个任务,且任务时间过长,超过了两个任务的间隔时间,会发生一些缺陷。

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后才执行,正因为定时器内部是一个线程,而任务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,计时器会停止所有任务的运行:

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

YY才能执行,但由于CPU休眠造成线程阻塞的关系,当前系统时间超过了这个时间,即便CPU从终端中恢复了,那么由于条件不满足,定时任务在这一次自然就失效了。 解决方案是:它需要用激活锁定让CPU保持唤醒状态。那么问题就来了,这样会大量消耗手机电量(CPU唤醒和屏幕唤醒不是同一概念),大大减短手机待机时间。这种方式不能满足我们的需求。注:TimerTask的实现了Runnable接口,但它的运行方法只是简单的在定时器中封装的线程中被调用,并未将其放在其它线程中执行也就是说计时器是单线程执行的,那么问题来了。为何要这么费劲的封装一个Runnable接口又不进行多线程调用?我的答案是,老外只是想要表示它是可执行的方法。关于休眠,可以参考Android关于休眠引发的几个血案Android手机休眠后时间不准确的解决方案 AlarmManager是安卓系统封装的用于管理RTC 模块,RTC(实时时钟)是一个独立的硬件时钟,可以在CPU休眠时正常运行,在预设的时间到达时,通过中断唤醒CPU。这意味着,如果我们用AlarmManager来定时执行任务,CPU可以正常的休眠,只有在需要运行任务时醒来一段很短的时间






以下参考Android基础入门教程--10.5 AlarmManager(闹钟服务)

核心流程如下:

  • AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
    获得系统提供的AlarmManager服务的对象
  • 意向设置要启动的组件:
    Intent intent = new Intent(MainActivity.this, ClockActivity.class);
  • 的PendingIntent对象设置动作,启动的是活动还是服务,又或者是广播!
    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):
    重复性闹钟,时间不固定
  • 取消(PendingIntent pi):取消AlarmManager的定时服务
  • getNextAlarmClock():得到下一个闹钟,返回值AlarmManager.AlarmClockInfo
  • setAndAllowWhileIdle(int type,long triggerAtMillis,PendingIntent operation)
    和设置方法类似,这个闹钟运行在系统处于低电模式时有效
  • setExact(int type,long triggerAtMillis,PendingIntent operation):
    在规定的时间精确的执行闹钟,比较方法设置的精度更高
  • setTime(long millis):设置系统墙上的时间
  • setTimeZone(String timeZone):设置系统持续的默认时区
  • setWindow(int type,long windowStartMillis,long windowLengthMillis,PendingIntent操作):
    设置一个闹钟在给定的时间窗触发。类似于设置,该方法允许应用程序精确地控制操作系统调整整个闹钟触发时间的程度。

关键参数讲解:

  • 类型(闹钟类型):
    有五个可选值:
    • AlarmManager.ELAPSED_REALTIME:
      闹钟在手机睡眠状态下不可用,该状态下闹钟使用相对时间(相对于系统启动开始),状态值为3;
    • AlarmManager.ELAPSED_REALTIME_WAKEUP 
      闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟也使用相对时间,状态值为2;
    • AlarmManager.RTC 
      闹钟在睡眠状态下不可用,该状态下闹钟使用绝对时间,即当前系统时间,状态值为1;
    • AlarmManager.RTC_WAKEUP 
      表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟使用绝对时间,状态值为0;
    • AlarmManager.POWER_OFF_WAKEUP 
      表示闹钟在手机关机状态下也能正常进行提示功能,所以是5个状态中用的最多的状态之一,该状态下闹钟也是用绝对时间,状态值为4;不过本状态好像受SDK版本影响,某些版本并不支持;
  • 开始时间:闹钟的第一次执行时间,以毫秒为单位,可以自定义时间,不过一般使用当前时间。需要注意的是,本属性与第一个属性(类型)密切相关,如果第一个参数对应的时钟使用的是相对时间(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 
[Android] CountDownTimer简单用法与源码解析

在开发中会经常用到倒计时这个功能,包括给手机发送验证码等等,之前我的做法都是使用Handler + Timer + TimerTask来实现,现在发现了这个类,果断抛弃之前的做法,相信还是有很多人和我一样一开始不知道的Android已经帮我们封装好了一个叫CountDownTimer的类。CountDownTimer timer = new CountDownTimer(10000,1000);以毫秒为单位,第一个参数是指从开始调用启动()方法到倒计时完成的时候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);
      }
    }
  }
};

内部流程


看一个使用例子:

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)方法,该方法第一个参数需要传入一个可运行接口,并实现的run()方法,第二个参数就是延迟多少时间将run()的方法中的代码通过一个消息发送给消息队列,然后在主线程中执行这个消息中的代码,即是运行方法中的代码,从而实现在主线程中更新界面UI。
贴代码吧:

new Handler().postDelayed(new Runnable() {//在当前线程(也即主线程中)开启一个消息处理器,并在3秒后在主线程中执行,从而来更新UI
    @Override
    public void run() {
        //有关更新UI的代码
    }
}, 3000);//3秒后发送
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值