Android 开发 AlarmManager 定时器
介绍
AlarmManager是Android中常用的一种系统级别的提示服务,在特定的时刻为我们广播一个指定的Intent。简单的说就是我们设定一个时间,然后在该时间到来时,AlarmManager为我们广播一个我们设定的Intent,通常我们使用 PendingIntent,PendingIntent可以理解为Intent的封装包,简单的说就是在Intent上在加个指定的动作。在使用Intent的时候,我们还需要在执行startActivity、startService或sendBroadcast才能使Intent有用。而PendingIntent的话就是将这个动作包含在内了。
注意!因为在较高的android版本(大概应该是5.0,其他厂商的机器都会有所不同)里,为了节省电源使用了一种在一段时间的所有定时器都会自动合并到一个时间点上一起发出的机制。所以,现在的定时器启动的时间并不是十分精确,它只是一个大概时间。就算你在后续设置了精确版本重复模式也是如此,它只是稍微好一点点。
创建流程
- 创建Intent用来告诉定时器触发后它要做什么,Intent可以是启动activity、启动Service、发送广播。
- 创建时间值用来告诉定时器什么时候触发。
- 创建PendingIntent(等待Intent)用来包裹创建好的Intent。
- 取得AlarmManager系统服务,强制转型并且实例化
- 用AlarmManager实例以set方法,添加类型、时间值、PendingIntent参数
简单Demo了解流程
关于更详解的了解在后续说明,我们先简单大概的了解一下使用流程。帮助理解后续更详细的参数说明。
我们创建一个定时器启动Activity的简单demo:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_time); mBtnStart = (Button)findViewById(R.id.btn_start);
mBtnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) { //正常的创建intent,TimeDemoActivity是我要启动的活动
Intent intent = new Intent(TimeActivity.this,TimeDemoActivity.class);
long time = System.currentTimeMillis()+5*1000;//得到当前时间并且增加5秒,表示我们在5秒后触发定时器
//创建PendingIntent封装了intent
PendingIntent pi = PendingIntent.getActivity(TimeActivity.this,0,intent,0);
AlarmManager manager = (AlarmManager)getSystemService(ALARM_SERVICE);//得到系统AlarmManager服务
//设置了AlarmManager类型参数,时间值,PendingIntent
manager.set(AlarmManager.RTC_WAKEUP,time,pi); }
});
}
这样就完成了,下面我们来看看效果:
PendingIntent对象
//这是启动activity的PendingIntent
PendingIntent pi = PendingIntent.getActivity(TimeActivity.this,0,intent,0);
- 启动Activity: PendingIntent对象的获取就应该采用 PendingIntent.getActivity(Context c,int i,Intent intent,int j)方法。
- 启动服务: PendingIntent对象的获取就应该采用Pending.getService(Context c,int i,Intent intent,int j)方法;
- 广播: PendingIntent对象的获取就应该采用 PendingIntent.getBroadcast(Context c,int i,Intent intent,int j)方法;
介绍一下参数:
- 第一个参数是当前activity或者服务的上下文。
- 第二个参数是这个PendingIntent的key代码,是识别这个PendingIntent的关键,在重复添加的定时器的时候,如果有Intent的内容一致(识别不同的PendingIntent也靠Intent是不是有一样的内容(setClass、setAction、setData内容决定Intent是否相同)),但是你还是需要有多个定时器,就需要更新这个参数。
- 第三个参数是添加一个需要延迟执行的Intent
- 第四个参数是,这个参数很重要这里我需要举一个坑来帮你理解一下为什么很重要:
如果你有一个PendingIntent创建后并且添加到AlarmManager里,在触发时间之前,如果你需要更新这个AlarmManager里的PendingIntent包裹的Intent的putExtra内容。如果这个时候你把第四个参数设置为0.你就会发现你就算重新把AlarmManager重写set了PendingIntent,它依然不会将Intent里的putExtra传值更新到最新。因为它会觉得你Intent是一样的。所以我们就需要使用2个常设定的参数来更新Intent或者更新PendingIntent。分别如下:
- PendingIntent.FLAG_CANCEL_CURRENT 如果AlarmManager管理的PendingIntent已经存在,那么将会取消当前的PendingIntent,从而创建一个新的PendingIntent
- PendingIntent.FLAG_UPDATE_CURRENT 如果PendingIntent第二个参数一样不变,则所有对应的Intent里面的数据被更新为最新的, 就是全部为最后一次更新的Intent。 如果PendingIntent第二个参数不一样,就不会更新Intent
AlarmManager详解
常用set方法
- set(int type,long startTime,PendingIntent pi);该方法用于设置一次性闹钟,第一个参数表示闹钟类型,第二个参数表示闹钟执行时间,第三个参数表示闹钟响应动作。
- setRepeating(int type,long startTime,long intervalTime,PendingIntent pi);该方法用于设置重复闹钟,第一个参数表示闹钟类型,第二个参数表示闹钟首次执行时间,第三个参数表示闹钟两次执行的间隔时间,第三个参数表示闹钟响应动作。
- setInexactRepeating(int type,long startTime,long intervalTime,PendingIntent pi);该方法也用于设置重复闹钟,重复闹钟的不精确版本,它相对而言更节能(power-efficient)一些,因为系统可能会将几个差不多的闹钟合并为一个来执行,减少设备的唤醒次数。
- setTimeZone(String timeZone) ;设置系统的默认时区。需要android.permission.SET_TIME_ZONE权限
注意!
API 19(Android4.4)开始,AlarmManager的机制是非准确激发的,操作系统会偏移(shift)闹钟来最小化唤醒和电池消耗。不过AlarManager新增了如下两个方法来支持精确激发。
setExact(int type long triggerAtMillis,PendingIntent operation)
设置闹钟闹钟将在精确的时间被激发。 setWindow(int type,long windowStartMillis,long windowLengthMillis,PendingIntent operation)
设置闹钟将在精确的时间段内被激发。
很显示API19以后无法使用setInexactRepeating()和setRepeating(),也就是无法设置重复闹钟,唯一解决的方式,也只有启动闹钟的时候再设置一次闹钟,也就变相地实现了重复闹钟了。
API19以下使用setExact()和setWindow()将会报没有匹配的方法
java.lang.NoSuchMethodError: android.app.AlarmManager.setExact
在Android6.0后google又更新了最新的设置方式,如果想继续保持Alarm在手机处于所谓Doze模式(6.0后新的电量控制模式)时仍然能够被即时响应,则需要使用AlarmManager新提供的两个方法(但是需要将app添加到电量白名单中):
setAndAllowWhileIdle() 这个是日历级别的定时器,在添加了电量白名单后,依然会被Doze模式影响,在Doze模式下的5分钟窗口期会被触发,并且窗口的触发期会越来越久。(可能会被其他定时器影响触发时间)
setExactAndAllowWhileIdle() 这个是通知信息级别的定时器,在添加了电量白名单后,依然会被Doze模式影响,在Doze模式下的5分钟窗口期会被触发,并且窗口的触发期会越来越久。。
- setAlarmClock() 这个是闹钟级别的定时器,添加电量白名单后,会有最高的触发精度。(另外怀疑这个定时器最高级别独立的,不会因为其他定时器而影响触发时间)
所以最后你可能需要写出这样的三次判断来兼容全部版本的定时器:
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.M) {
alarmMgr.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendIntent);
}else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
alarmMgr.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendIntent);
}else{
alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendIntent); }
set方法各参数详解
不介绍setTimeZone方法
int type 参数
闹钟的类型,常用的有5个值:AlarmManager.ELAPSED_REALTIME、 AlarmManager.ELAPSED_REALTIME_WAKEUP、AlarmManager.RTC、 AlarmManager.RTC_WAKEUP、AlarmManager.POWER_OFF_WAKEUP。
- AlarmManager.ELAPSED_REALTIME表示闹钟在手机睡眠状态下不可用,该状态下闹钟使用相对时间(相对于系统启动开始),状态值为3;
- AlarmManager.ELAPSED_REALTIME_WAKEUP表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟也使用相对时间,状态值为2;
- AlarmManager.RTC表示闹钟在睡眠状态下不可用,该状态下闹钟使用绝对时间,即当前系统时间,状态值为1;
- AlarmManager.RTC_WAKEUP表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟使用绝对时间,状态值为0;
- AlarmManager.POWER_OFF_WAKEUP表示闹钟在手机关机状态下也能正常进行提示功能,所以是5个状态中用的最多的状态之一,该状态下闹钟也是用绝对时间,状态值为4;不过本状态好像受SDK版本影响,某些版本并不支持;
long startTime 参数
闹钟的第一次执行时间,以毫秒为单位,可以自定义时间,不过一般使用当前时间。需要注意的是,本属性与第一个属性(type)密切相关,如果第一个参数对 应的闹钟使用的是相对时间(ELAPSED_REALTIME和ELAPSED_REALTIME_WAKEUP),那么本属性就得使用相对时间(相对于 系统启动时间来说),比如当前时间就表示为:SystemClock.elapsedRealtime();如果第一个参数对应的闹钟使用的是绝对时间 (RTC、RTC_WAKEUP、POWER_OFF_WAKEUP),那么本属性就得使用绝对时间,比如当前时间就表示 为:System.currentTimeMillis()。
long intervalTime 参数
对于后两个方法来说,存在本属性,表示两次闹钟执行的间隔时间,也是以毫秒为单位。这里系统代码上已经提供了一些快捷的间隔时间,让你可以不需要在另外计算一天的毫秒级时间:
- INTERVAL_DAY = 86400000L;//设置闹钟,间隔一天
- INTERVAL_FIFTEEN_MINUTES = 900000L; //设置闹钟,间隔15分钟
- INTERVAL_HALF_DAY = 43200000L; //设置闹钟,间隔半天
- INTERVAL_HALF_HOUR = 1800000L; //设置闹钟,间隔半个小时
- INTERVAL_HOUR = 3600000L; //设置闹钟,间隔一个小时
使用方法:
manager.setRepeating(AlarmManager.RTC_WAKEUP,time+10*1000,AlarmManager.INTERVAL_DAY,pi);
PendingIntent pi 参数
绑定了闹钟的执行动作,比如发送一个广播、给出提示等等。PendingIntent是Intent的封装类。
取消已经注册的AlarmManager
AlarmManager manager = (AlarmManager)getSystemService(ALARM_SERVICE);
Intent intent = new Intent(AlarmTest.this, AlarmActivity.class);
intent.setAction("111111");
PendingIntent pendingIntent = PendingIntent.getActivity( AlarmTest.this, 0, intent, 0);
manager.cancel(pendingIntent);
说明一下为什么需要setAction,因为有一种情况就是多个 AlarmManager 都在不同时间段要启动相同activity的intent,所以为了区分不同时段的intent,我们需要添加setAction。这样取消的时候只会取消指定的intent。注意!如果你添加了2个一样的Intent到定时器里,那么第一个被添加到定时器的Intent会自动被取消。
了解时间值的创建
请移步到本人另一篇博客了解Java的时间值操作与获取:https://blog.csdn.net/qq_37217804/article/details/81741684取消
Demo演示
定时器广播Demo_1
注意!必需静态广播!
在AndroidManifest.xml静态广播注册与action接收器注册:
<receiver
android:name=".ui.MyReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.xxx.xxx.MyReceiver"/>
</intent-filter>
</receiver>
创建接收器class:
public class MyReceiver extends BroadcastReceiver { @Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("com.yt.owl.MyReceiver")) {
Toast.makeText(context, "定时器广播成功接收", Toast.LENGTH_SHORT).show();
}
}
}
创建定时器:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_time); mBtnStart = (Button)findViewById(R.id.btn_start);
mBtnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mFormat = new SimpleDateFormat("yyyy年MM月dd日 : HH时mm分ss秒");
long time = System.currentTimeMillis();
Log.e(TAG, "TIME1: "+time);
Intent intent = new Intent(TimeActivity.this,MyReceiver.class);
intent.setAction("com.xxx.xxx.MyReceiver");
PendingIntent pt = PendingIntent.getBroadcast(TimeActivity.this,0,intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager manager = (AlarmManager)getSystemService(ALARM_SERVICE);
manager.set(AlarmManager.RTC_WAKEUP,time+5*1000,pt);
Toast.makeText(TimeActivity.this,"定时器启动 触发时间"+mFormat.format(time+5*1000),Toast.LENGTH_SHORT).show(); }
});
}
效果图:
定时器广播Demo_2
当然我们可以不添加action
AndroidManifest.xml注册静态广播
<receiver
android:name=".ui.MyReceiver"
android:enabled="true"
android:exported="true">
</receiver>
创建广播class
public class MyReceiver extends BroadcastReceiver { @Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "定时器广播成功接收", Toast.LENGTH_SHORT).show();
}
}
创建定时器:
mBtnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) { mFormat = new SimpleDateFormat("yyyy年MM月dd日 : HH时mm分ss秒");
long time = System.currentTimeMillis();
Log.e(TAG, "TIME1: "+time);
Intent intent = new Intent(TimeActivity.this,MyReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(TimeActivity.this,0,intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager manager = (AlarmManager)getSystemService(ALARM_SERVICE);
manager.set(AlarmManager.RTC_WAKEUP,time+5*1000,pi);
Toast.makeText(TimeActivity.this,"定时器启动 触发时间"+mFormat.format(time+5*1000),Toast.LENGTH_SHORT).show(); }
});
Android 开发 AlarmManager 定时器的更多相关文章
- Android 之 AlarmManager(定时器) 的介绍和使用
AlarmManager 包含的主要方法: // 取消已经注册的与参数匹配的定时器 void cancel(PendingIntent operation) //注册一个新的延迟定时器void set ...
- 在Android开发中,定时器一般有以下3种实现方法
在Android开发中,定时器一般有以下3种实现方法: 原文地址http://www.360doc.com/content/12/0619/13/87000_219180978.shtml 一.采用H ...
- android开发 系统时间与定时器之间有关系嘛?
如题: android开发 系统时间与定时器之间有关系嘛? 答案:有. 看定时器源码: /* * Schedule a task. */ private void scheduleImpl(Timer ...
- 我的Android进阶之旅------>Android使用AlarmManager全局定时器实现定时更换壁纸
该DEMO将会通过AlarmManager来周期的调用ChangeService,从而让系统实现定时更换壁纸的功能. 更换壁纸的API为android.app.WallpaperManager,它提供 ...
- Android开发权威指南(第2版)新书发布
<Android 开发权威指南(第二版)>是畅销书<Android开发权威指南>的升级版,内容更新超过80%,是一本全面介绍Android应用开发的专著,拥有45 章精彩内容供 ...
- Android开发之闹钟
闹钟开发: 1.需要时间选择器TimePicker 2.需要Calendar类对日期时间进行操作 3.需要AlarmManager//闹钟管理实质是一个全局定时器, 是Android中常用的一种系统级 ...
- 【转】Android开发笔记(序)写在前面的目录
原文:http://blog.csdn.net/aqi00/article/details/50012511 知识点分类 一方面写写自己走过的弯路掉进去的坑,避免以后再犯:另一方面希望通过分享自己的经 ...
- Android开发网上的一些重要知识点[经验分享]
1. android单实例运行方法 我们都知道Android平台没有任务管理器,而内部App维护者一个Activity history stack来实现窗口显示和销毁,对于常规从快捷方式运行来看都是s ...
- Android开发 定时任务清理数据
原文地址:Android开发 定时任务清理数据 | Stars-One的杂货小窝 公司项目,需要整定时任务,对数据进行清理,需要在每天凌晨0:00进行数据的清理,使用了Alarm和广播的方式来实现 P ...
随机推荐
- 汇编语言debug入门
进入windows操作系统,因为我的虚拟机用的是win7 64位,所以装了一个Dos Box 的软件来执行这些指令. 输入debug回车,这样就进入了debug模式. 1: 输入 -r 查看或者修改寄 ...
- C++ 抽象类与接口
1. 抽象类 在面向对象编程中,抽象类是一种只能定义类型,不能生成对象的类,它是对一系列看上去不同,但是本质相同的具体概念的抽象.最典型的的抽象类就是”图形”,三角形.矩形.梯形都是图形,它们都具有 ...
- Python编程--类的分析
一.类的概念 python是面向对象的编程语言,详细来说,我们把一类相同的事物叫做类,其中用相同的属性(其实就是变量描述),里面封装了相同的方法,比如:汽车是一个类,它包括价格.品牌等属性.那么我们如 ...
- 前端开发【第一篇: HTML】
HTML初识 1.什么是HTML? HTML是英文Hyper Text Mark-up Language(超文本标记语言)的缩写,他是一种制作万维网页面标准语言(标记). 2.网页的组成 我们平时 ...
- 第一周例行报告psp
此作业要求参见 https://edu.cnblogs.com/campus/nenu/2018fall/homework/2100 (1)psp表
- ceph存储集群测试方案
--测试目的 测试ceph集群的读写性能,根据测试数据了解整个ceph集群的性能情况. --测试环境 1.8节点ceph集群环境,1台虚拟机(cpu 8核,内存8G),8k的块大小,时长2小时 2.8 ...
- 自动化测试-13.selenium执行JS处理滚动条
前言 selenium并不是万能的,有时候页面上操作无法实现的,这时候就需要借助JS来完成了. 常见场景: 当页面上的元素超过一屏后,想操作屏幕下方的元素,是不能直接定位到,会报元素不可见的. 这时候 ...
- opencv读取摄像头实时流代码
opencv读取摄像头实时流代码: #include <opencv2/opencv.hpp> #include <iostream> using namespace cv; ...
- IIS发布错误及解决
HTTP 错误 403.14 - Forbidden 解决办法: 打开iis管理器,找到对应网站,并找到目录浏览,双击打开. 点击启用即可. HTTP 错误 500.19 - Internal Se ...
- API Gateway : Kong
what problems 多个服务要写自己的log,auth,对于比较耗时的,有时还要高流量限制. solution intro 单点部署的情况: why not just haproxy log ...