Android中几种定时器的总结和比较

在android中,一般有下面几种方式实现定时任务:

 

  • 采用Handler与线程的sleep(long)方法
  • 采用Handler的postDelayed(Runnable, long)方法
  • 采用Handler与timer及TimerTask结合的方法
  • 采用采用AlarmManager和BroadcastReceiver结合的方式

 

下面进行分别介绍和比较:

一、采用Handler与线程的sleep(long)方法

  首先定义一个handler来处理定时主要处理要执行的业务

Handler handler = new Handler() 
{ 
   public void handleMessage(Message msg)
 { 
        // 业务 
        super.handleMessage(msg); 
    } 
};

然后实现Runnable接口,在run()方法中循环发送消息

public class MyThread implements Runnable
 { 
    @Override
public void run()
 { 
        // TODO Auto-generated method stub 
        while (true)
 				 { 
            try 
{ 
                Thread.sleep(10000);            // 线程暂停10秒,单位毫秒 
                Message message = new Message(); 
                message.what = 0x0001; 
                handler.sendMessage(message);   // 发送消息 
            }
 							catch (InterruptedException e)
 { 
                // TODO Auto-generated catch block 
                e.printStackTrace(); 
            } 
        } 
    } 
}

最后在需要启动线程的地方开启线程

new Thread(new MyThread()).start();

注:1.Thread.sleep()使当前线程的执行暂停一段指定的时间,但并不保证这些睡眠时间的精确性,因为他们受到系统计时器和调度程序精度和准确性的影响。另外中断(interrupt)可以终止睡眠时间,所以,在任何情况下,都不能假设调用 sleep就会按照指定的时间精确的挂起线程 或开启任务。特别是在当毫秒数过小时,Sleep往往不能按照给定的时间睡眠,而是睡了长了一点的时间,这是由于Sleep函数的精度导致的,一般的系统默认的精度是10毫秒,也就是说,如果参数小于10,那Sleep的时间可能是10,或者更多,系统尽量以10毫秒为精度保证时间准确。

        2.在后台服务中使用Thread.sleep()方法,特别是长时间的睡眠,系统很容就会降低该进程的优先级从而杀掉     进程。

        3.此方法若想停止周期性任务,还需要另行编写控制代码,比较麻烦,不推荐使用

 

二、采用Handler的postDelayed(Runnable, long)方法

 

  同样,首先实现一个Runnable接口处理业务

Handler handler=new Handler(); 
Runnable runnable=new Runnable() { 
    @Override
    public void run() { 
        // TODO Auto-generated method stub 
        //要做的事情 
        handler.postDelayed(this, 2000); 
    } 
};

然后在要启动计时器的地方填写如下代码

handler.postDelayed(runnable, 2000);                //每两秒执行一次任务线程runnable

最后在要停止的地方添加

handler.removeCallbacks(runnable);

注:1.此方法比较简单。

        2.注意有时候removeCallbacks有时候会失效,不能从消息队列中移除。见如下代码:

package com.example.demoactivity;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class TimerActivity extends Activity{
	Handler handler = new Handler();
	Runnable runnable = new Runnable() {
		
		@Override
		public void run() {
			System.out.println("update...");
			handler.postDelayed(runnable, 1000);
		}
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.timer);
		
		Button mButtonStart = (Button) findViewById(R.id.button1);
		Button mButtonStop = (Button) findViewById(R.id.button2);
		
		mButtonStart.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				handler.post(runnable);
			}
		});
		
		mButtonStop.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				handler.removeCallbacks(runnable);
			}
		});
	}
}

结果:
(1)start –>  输出 –> stop–> 停止输出
(2)start –> 输出 –> Background –> Front –> stop->继续输出

 

可以看出当Activity进入后台运行后再转入前台运行,removeCallbacks无法将updateThread从message queue中移除。

原因是在Activity由前台转后台过程中,线程是一直在运行的,但是当Activity转入前台时会重新定义Runnable runnable;也就是说此时从message queue移除的runnable与原先加入message queue中的runnable并非是同一个对象。如果 runnable 定义为静态的则removeCallbacks不会失效,对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配。我们做如下修改就能解决上面的这个问题:

static Handler handler = new Handler();
static Runnable runnable = new Runnable() {
	@Override
	public void run() {
		System.out.println("update...");
		handler.postDelayed(runnable, 1000);
	}
};
       3.下面列出Handler相关的方法
post(Runnable r);
立即执行Runnable对象 

postAtTime(Runnable r, long uptimeMillis);
在指定的时间(uptimeMillis)执行Runnable对象

postDelayed(Runnable r, long delayMillis);
推迟指定时间(delayMillis)执行Runnable对象

最后要注意Handler的post系列方法并不可靠,要较精确的定时发送建议用下面总结的第4种方法。

、采用Handler与timer及TimerTask结合的方法

1. 定义定时器定时器任务及Handler句柄

private final Timer timer = new Timer();  
private TimerTask task;  
Handler handler = new Handler()
{  
  	  	@Override 
    		public void handleMessage(Message msg) 
{  
        		// TODO Auto-generated method stub  
        		// 要做的事情  
        		super.handleMessage(msg);  
    		}	 
};

2. 初始化计时器任务

task = new TimerTask() 
{  
    	@Override 
public void run() 
{  
        // TODO Auto-generated method stub  
        Message message = new Message();  
        message.what = 1;  
        handler.sendMessage(message);  
    	}  
};

3. 启动定时器

timer.schedule(task, 5000, 5000);

4. 撤销定时器

timer.cancel();

注:

1).定时器任务(TimerTask)顾名思义,就是说当定时器到达指定的时间时要做的工作,这里是向Handler发送一个消息,由Handler类进行处理。

2).java.util.Timer类中的方法

//dalay/1000秒后执行task且只执行一次。
schedule(TimerTask task, long delay)
//delay/1000秒后执行task,然后进过period/1000秒再次执行task,这个用于循环任务,执行无数次。
schedule(TimerTask task, long delay, long period) 
//在指定时间执行某任务
schedule(TimerTask task, Date when) 
//在指定时间开始重复执行某任务,然后进过period/1000秒再次执行task,这个用于循环任务,执行无数次。  
schedule(TimerTask task, Date when, long period)
//delay/1000秒后执行task,然后进过period/1000秒再次执行task,这个用于循环任务,执行无数次,注重频率的稳定性。
scheduleAtFixedRate (TimerTask task, long delay, long period)
//在指定时间开始重复执行某任务,然后进过period/1000秒再次执行task,这个用于循环任务,执行无数次,注重频率的稳定性。
scheduleAtFixedRate (TimerTask task, Date when, long period)

在指定时间开始重复执行某任务,然后进过period/1000秒再次执行task,这个用于循环任务,执行无数次,注重频率的稳定性。

3)Timer有两种调度模式fixed-rate(固定的调度周期),fixed-period(完整的执行周期)。

这里通过比较schedule和scheduleAtfixedRate说明两种调度模式,scheduleAtfixedRate是fixed-rate模式,schedule属于fixed-period模式,主要区别在于在有延迟的情况下执行TimerTask 频率是否稳定。比如:目前时间为0:15分,设定启动第一次TimerTask 时间为0:00,每隔10分钟执行次TimerTask ,此时,对于schedule,就会在0:15分运行代码时执行一TimerTask ,以后若没有延迟的情况下执行形式为:0:25,0:35,0:45……,而对于scheduleAtfixedRate来说0:15分运行代码时,固定的执行TimerTask 的时间间隔已经设定完毕,无论有无延迟,执行形式均为:先执行两次TimerTask ,分别代表0:00和0:10,以后执行的形式为0:20,0:30,0:40……。另外注意schedule若出现延迟,以后对应的顺序回一次按10分钟向下延迟,而scheduleAtfixedRate不会。

4)一些注意的问题 

<1>每一个Timer仅对应唯一一个线程。

<2>Timer和第二种方法一样不保证任务执行的十分精确。

<3>Timer类的线程安全的。

<4>一个Timer开始对应一个cancel();不可以重复cancel()。

四、采用AlarmManager和BroadcastReceiver结合的方式

   

此方式可以提供比上边较精确的的定时任务操作。

1. 通过系统服务获得AlarmManager并设定参数

AlarmManager alarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, new Intent(this, MyBroadCastReceiver.class), Intent.FLAG_ACTIVITY_NEW_TASK);

2. 开始计时,在指定时间发出Broadcast

long now = System.currentTimeMillis();
alarmManager.setInexactRepeating(AlarmManager.RTC, now, 60000, pendingIntent);

3. 在MyBroadCastReceiver类中处理任务。

public class MyBroadCastReceiver extends BroadcastReceiver {
	
				@Override
	public void onReceive(Context context, Intent intent) {
		// TODO Auto-generated method stub
		//做要处理的事情
	}
}

4. 撤销闹钟

alarmManager.cancel(pendingIntent);

5. 别忘了在AndroidManifest.xml注册或动态注册广播。

注:

1)       如果某个AlarmManager已经启动, 程序又再次去启动它,只要PendingIntent是一样,那么之前那个AlarmManager会被release掉。

2)      不可以重复调用cancel(),否则会报错

3)      开启的AlarmManager只有”强制停止”或重启手机才能取消计时。

4)      注意BroadcastReceiver的onReceive()中不可以进行耗时操作。若必须有耗时操作可以另起线程。或通过AlarmManager和service结合的方式在后台进行。通过此方式还可以编写杀掉后可重启的服务。

5)      亲测该方法比前面3中方法更好一点,较为精准,但也不代表不会有消息传递的延迟。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值