1:需求
常常有这样的需求,在某个时候手机自动执行某一动作,或者周期性的做一些事情。
在程序的声明周期中,我们可以使用Schedule+Timer+TimerTask运行在service中,也可以使用handler的延时post进行处理。都比较方便,尤其是采用handler方式。但是当不在程序的生命周期的时候,上述两种方式就显得力不从心了。
这个时候就到了Alarm大显身手了。当然Alarm也是可以在程序的生命周期内使用。
注意一下:handler在程序切到后后台的时候会出现计时延缓,我发现这个问题是在程序发短信倒计时的时候。
2:什么是Alarm
Alarm是在预定的时间触发Intent的,独立于应用程序的提醒用户的方式。当这个Alarm触发后,就会广播这个Intent,如果应用程序没有起启,就会启动这个应用程序,而不需要就用程序被打开或者处于活动状态(这里说的只是其中一种较为特殊的情况,具体的启动方式可以根据参数的不同而配置)。
在android中通过AlarmManager来管理所有的Alarm。
AlarmManage有一个AlarmManagerServie服务程序,该服务程序才是正真提供闹铃服务的,它主要维护应用程序注册下来的各类闹铃并适时的设置即将触发的闹铃给闹铃设备(在系统中,linux实现的设备名为”/dev/alarm”),并且一直监听闹铃设备,一旦有闹铃触发或者是闹铃事件发生,AlarmManagerServie服务程序就会遍历闹铃列表找到相应的注册闹铃并发出广播。该服务程序在系统启动时被系统服务程序system_service启动并初始化闹铃设备(/dev/alarm)。当然,在JAVA层的AlarmManagerService与Linux Alarm驱动程序接口之间还有一层封装,那就是JNI。
AlarmManager将应用与服务分割开来后,使得应用程序开发者不用关心具体的服务,而是直接通过AlarmManager来使用这种服务。这也许就是客户/服务模式的好处吧。AlarmManager与 AlarmManagerServie之间是通过Binder来通信的,他们之间是多对一的关系。
3:Alarm如何使用
这里只介绍BroadcastReceiver中的使用,因为核心代码类似顾不在详解。
在应用中进行Alarm的注册(这种方式更令我认同)
布局的code
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<Button
android:id="@+id/cancle"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="cancle"
/>
<Button
android:id="@+id/restart"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="restart"
android:layout_marginTop="20dp"
/>
</LinearLayout>
activity中的code
public class MainActivity extends Activity implements OnClickListener{
private AlarmManager alarmManager;
private PendingIntent pendingIntent;
private Button cancle,restart;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
cancle = (Button) findViewById(R.id.cancle);
restart = (Button) findViewById(R.id.restart);
cancle.setOnClickListener(this);
restart.setOnClickListener(this);
Intent intent;
intent = new Intent(this, AlarmBroadcastReceiver.class);
intent.setAction("Action.Alarm");
pendingIntent = PendingIntent.getBroadcast(this, 0, intent,PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
//alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+5000, pendingIntent)
alarmManager.setRepeating(AlarmManager.RTC,System.currentTimeMillis(), 1000, pendingIntent);
//api19以后使用
//alarmManager.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+5000, pendingIntent);
//alarmManager.setWindow(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 15*1000, pendingIntent);
}
@Override
public void onClick(View v) {
int id = v.getId();
switch (id) {
case R.id.cancle:
alarmManager.cancel(pendingIntent);
break;
case R.id.restart:
alarmManager.setRepeating(AlarmManager.RTC,System.currentTimeMillis(), 1000, pendingIntent);
break;
default:
break;
}
}
}
广播接收的code,在这里可以进行一些启动服务或者activity的操作。(这里不能进行耗时操作)
public class AlarmBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if ("Action.Alarm".equals(intent.getAction())) {
System.out.println("闹钟事件发生了!");
Toast.makeText(context, "闹钟事件发生了!", Toast.LENGTH_SHORT).show();
}
}
}
4:Alarm知识点
alarm 需要使用pendingIntent进行注册。pendingIntent对Intent进行了包装,可以根据需求的不同得到不同的pendingIntent。
PendingIntent.getBroadcast
PendingIntent.getActivity
PendingIntent.getService
就3中的PendingIntent.getBroadcast(this, 0, intent,PendingIntent.FLAG_UPDATE_CURRENT);进行参数的讲解
参数1:上下文
参数2:int requestCode 类似于startActivityForResult中的requestCode(这个到底有什么作用呢)
参数3:intent可以携带数据
参数4:
FLAG_CANCEL_CURRENT:如果描述的PendingIntent对象已经存在时,会先取消当前的PendingIntent对象再生成新的。
FLAG_NO_CREATE:如果描述的PendingIntent对象不存在,它会返回null而不是去创建它。
FLAG_ONE_SHOT:创建的PendingIntent对象只使用一次。
FLAG_UPDATE_CURRENT:如果描述的PendingIntent对象存在,则保留它,并将新的PendingIntent对象的数据替换进去。
在从提取PendingIntent时,通过FLAG_CANCEL_CURRENT参数,让这个老PendingIntent的先cancel()掉,这样得到的pendingInten和其token的就是新的了。可以让新的Intent会更新之前PendingIntent中的Intent对象数据
拿到alarmManager,就可以对alarm进行注册了。
注册的方式有下面几种
//一次
set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+5000, pendingIntent)
参数1:后面讲解
参数2:alarm触发的的时间
参数3:上文已经讲解
//多次
alarmManager.setRepeating(AlarmManager.RTC,System.currentTimeMillis(), 1000, pendingIntent)
setInexactRepeating(AlarmManager.RTC,System.currentTimeMillis(), 1000, pendingIntent)
参数1:后面讲解
参数2:alarm触发的的时间
参数3:两次触发之间的时间间隔参数4:上文已经讲解
二者都是周期性的触发,区别在于setRepeating将安排第一个报警准确的触发,而setInexactRepeating
将会偏离你所设定的具体值
setInexactRepeating
中第三个参数可做选择
AlarmManager.INTERVAL_DAY
AlarmManager.INTERVAL_FIFTEEN_MINUTES
AlarmManager.INTERVAL_HALF_DAY
AlarmManager.INTERVAL_HALF_HOUR
AlarmManager.INTERVAL_HOUR
//Api19之后可以使用
//alarmManager.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+5000, pendingIntent);
//alarmManager.setWindow(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 15*1000, pendingIntent);
第一个参数
FLAG_CANCEL_CURRENT:如果描述的PendingIntent对象已经存在时,会先取消当前的PendingIntent对象再生成新的。
FLAG_NO_CREATE:如果描述的PendingIntent对象不存在,它会返回null而不是去创建它。
FLAG_ONE_SHOT:创建的PendingIntent对象只使用一次。
FLAG_UPDATE_CURRENT:如果描述的PendingIntent对象存在,则保留它,并将新的PendingIntent对象的数据替换进去。
5:Alarm出现的问题
1:Alarm不能滥用,否则会使得程序成为待机电量杀手。
2:小米手机
定时器,15秒发一个广播,HTC测试都是立马就发出第一个广播,
就MI2测试时,第一个等了好5分钟才发出来
原因:这是由于MIUI的对齐唤醒机制导致的,这个用什么都不太好使,小米上如果要使用AlarmManager 智能使用RTC 模式 其他的都有限制。
3:另当定时不准确的时候,看看是不是因为没有使用最新的API19以后推荐使用的接口。
参考:
http://www.cnblogs.com/jk1001/archive/2010/08/02/1790167.html
http://www.cnblogs.com/linzheng/archive/2011/01/22/1942059.html
http://blog.youkuaiyun.com/hudashi/article/details/7060837
http://blog.youkuaiyun.com/dianyueneo/article/details/19157289
http://blog.youkuaiyun.com/maosidiaoxian/article/details/21776697