$Android AlarmManager的用法详解
在Android的Alarm机制中,使用AlarmManager可以实现类似闹钟这样的定时任务。在毕业设计项目中要实现定时任务的功能,所以在这里先进行一下梳理。
(一)AlarmManager与Broadcast结合实现定时任务
AlarmManager主要可以发送定时广播,然后在广播接收器中执行任务的具体逻辑;还可以取消已经创建的定时任务、创建可以周期重复执行的定时任务等,将这几个功能进行封装,封装成AlarmManagerUtil类如下:
1 import android.app.AlarmManager; 2 import android.app.PendingIntent; 3 import android.content.Context; 4 import android.content.Intent; 5 import android.util.Log; 6 7 /** 8 * AlarmManager工具类 9 * 10 */ 11 12 public class AlarmManagerUtil { 13 // 获取AlarmManager实例 14 public static AlarmManager getAlarmManager(Context context) { 15 return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 16 } 17 18 // 发送定时广播(执行广播中的定时任务) 19 // 参数: 20 // context:上下文 21 // requestCode:请求码,用于区分不同的任务 22 // type:alarm启动类型 23 // triggerAtTime:定时任务开启的时间,毫秒为单位 24 // cls:广播接收器的class 25 public static void sendAlarmBroadcast(Context context, int requestCode, 26 int type, long triggerAtTime, Class cls) { 27 AlarmManager mgr = getAlarmManager(context); 28 29 Intent intent = new Intent(context, cls); 30 PendingIntent pi = PendingIntent.getBroadcast(context, requestCode, 31 intent, 0); 32 33 mgr.set(type, triggerAtTime, pi); 34 } 35 36 // 取消指定requestCode的定时任务 37 // 参数: 38 // context:上下文 39 // requestCode:请求码,用于区分不同的任务 40 // cls:广播接收器的class 41 public static void cancelAlarmBroadcast(Context context, int requestCode, 42 Class cls) { 43 AlarmManager mgr = getAlarmManager(context); 44 45 Intent intent = new Intent(context, cls); 46 PendingIntent pi = PendingIntent.getBroadcast(context, requestCode, 47 intent, 0); 48 49 mgr.cancel(pi); 50 ToastUtil 51 .showShort(context, "取消定时服务成功" + " @requestCode:" + requestCode); 52 Log.d("取消定时服务成功", "@requestCode:" + requestCode); 53 } 54 55 // 周期性执行定时任务 56 // 参数: 57 // context:上下文 58 // requestCode:请求码,用于区分不同的任务 59 // type:alarm启动类型 60 // startTime:开始的时间,毫秒为单位 61 // cycleTime:定时任务的重复周期,毫秒为单位 62 // cls:广播接收器的class 63 public static void sendRepeatAlarmBroadcast(Context context, 64 int requestCode, int type, long startTime, long cycleTime, Class cls) { 65 AlarmManager mgr = getAlarmManager(context); 66 67 Intent intent = new Intent(context, cls); 68 PendingIntent pi = PendingIntent.getBroadcast(context, requestCode, 69 intent, 0); 70 71 mgr.setRepeating(type, startTime, cycleTime, pi); 72 } 73 }
其中使用到的其他两个工具类都是项目中经常用到的,代码如下:
ToastUtil:
1 /** 2 * Toast提示显示工具类 3 * 4 */ 5 6 import android.content.Context; 7 import android.widget.Toast; 8 9 public class ToastUtil { 10 11 // 短时间显示Toast信息 12 public static void showShort(Context context, String info) { 13 Toast.makeText(context, info, Toast.LENGTH_SHORT).show(); 14 } 15 16 // 长时间显示Toast信息 17 public static void showLong(Context context, String info) { 18 Toast.makeText(context, info, Toast.LENGTH_LONG).show(); 19 } 20 21 }
DateTimeUtil:
1 import java.text.DateFormat; 2 import java.text.DecimalFormat; 3 import java.text.ParseException; 4 import java.text.SimpleDateFormat; 5 import java.util.Arrays; 6 import java.util.Calendar; 7 import java.util.Date; 8 import java.util.GregorianCalendar; 9 10 public class DateTimeUtil { 11 // 获取当前时间n[]之后的时间的日期时间字符串(N的单位为Calendar的那些表示时间的常量) 12 public static String getNLaterDateTimeString(int nType, int n) { 13 Date date = new Date(); 14 Calendar c = new GregorianCalendar(); 15 c.setTime(date); 16 c.add(nType, n); 17 18 return CalendarToString(c); 19 } 20 21 // millis to datetime 22 public static String getDateTimeStringFromMillis(long millis) { 23 Date date = new Date(millis); 24 return (DateToString(date)); 25 } 26 27 // 把日期时间字符串的时间转换成毫秒值(RTC) 28 public static long stringToMillis(String dateTime) { 29 Calendar c = StringToGregorianCalendar(dateTime); 30 31 return c.getTimeInMillis(); 32 } 33 34 // 获取两个日期时间字符串表示的时间之间的毫秒差值 35 public static long getDifMillis(String dateTime1, String dateTime2) { 36 return (stringToMillis(dateTime1) - stringToMillis(dateTime2)); 37 } 38 39 // 输入一个表示日期或时间的整型数,输出"01"或"23"这样格式的字符串 40 public static String getDoubleNumString(int n) { 41 int num = n % 60; 42 43 if (num < 10) { 44 return "0" + num; 45 } else { 46 return num + ""; 47 } 48 } 49 50 // 获取标准日期时间字符串的整型的日期值,如:"2015-01-21 00:00:00",返回:21 51 public static int getDayOfMonth(String dateTime) { 52 Calendar c = StringToGregorianCalendar(dateTime); 53 int day = c.get(Calendar.DAY_OF_MONTH); 54 55 return day; 56 } 57 58 // 获取当前时间的日期时间字符串,格式:"yyyy-MM-dd HH:mm:ss" 59 public static String getCurrentDateTimeString() { 60 Date date = new Date(); 61 return DateToString(date); 62 } 63 64 // 获取当前的"yyyy-MM-dd"日期格式字符串 65 public static String getCurrentDateString() { 66 Date date = new Date(); 67 return DateToString(date).substring(0, 10); 68 } 69 70 // 获取当前的"yyyy-MM"日期格式字符串 71 public static String getCurrentMonthString() { 72 Date date = new Date(); 73 return DateToString(date).substring(0, 7); 74 } 75 76 // 获取当前的"HH:mm"时间格式字符串 77 public static String getCurrentTimeString() { 78 Date date = new Date(); 79 return DateToString(date).substring(11, 16); 80 } 81 82 // 获取当前的"HH:mm:ss"时间格式字符串 83 public static String getCurrentTimeLongString() { 84 Date date = new Date(); 85 return DateToString(date).substring(11, 19); 86 } 87 88 // 由日期时间字符串生成“11月1日 星期一”这样格式的字符串 89 public static String getShortDateTimeOfWeek(String dateTime) { 90 Calendar c = StringToGregorianCalendar(dateTime); 91 92 int month = c.get(Calendar.MONTH) + 1; 93 int day = c.get(Calendar.DAY_OF_MONTH); 94 95 String[] weekStr = new String[] { "星期日", "星期一", "星期二", "星期三", "星期四", 96 "星期五", "星期六" }; 97 String week = weekStr[c.get(Calendar.DAY_OF_WEEK) - 1]; 98 99 String result = month + "月" + day + "日" + " " + week; 100 101 return result; 102 } 103 104 // 由日期时间字符串生成“2015年11月1日 星期一”这样格式的字符串 105 public static String getDateTimeOfWeek(String dateTime) { 106 Calendar c = StringToGregorianCalendar(dateTime); 107 108 int year = c.get(Calendar.YEAR); 109 int month = c.get(Calendar.MONTH) + 1; 110 int day = c.get(Calendar.DAY_OF_MONTH); 111 112 String[] weekStr = new String[] { "星期日", "星期一", "星期二", "星期三", "星期四", 113 "星期五", "星期六" }; 114 String week = weekStr[c.get(Calendar.DAY_OF_WEEK) - 1]; 115 116 String result = year + "年" + month + "月" + day + "日" + " " + week; 117 118 return result; 119 } 120 121 // 由日期时间字符串生成“2015年11月1日 05:43”这样格式的字符串 122 public static String getDateTimeOfHourMinute(String dateTime) { 123 String result = ""; 124 String date = dateTime.split(" ")[0]; 125 String time = dateTime.split(" ")[1]; 126 String[] dateArr = date.split("-"); 127 String[] timeArr = time.split(":"); 128 129 int year = Integer.parseInt(dateArr[0]); 130 int month = Integer.parseInt(dateArr[1]); 131 int day = Integer.parseInt(dateArr[2]); 132 133 result = year + "年" + month + "月" + day + "日" + " " + timeArr[0] + ":" 134 + timeArr[1]; 135 136 return result; 137 } 138 139 // 用年月日生成日期字符串,month取值范围:[0,13] 140 public static String getDateString(int year, int month, int day) { 141 String m; 142 String d; 143 144 if (month >= 9) { 145 m = (month + 1) + ""; 146 } else { 147 m = "0" + (month + 1); 148 } 149 150 if (day >= 10) { 151 d = day + ""; 152 } else { 153 d = "0" + day; 154 } 155 156 String dateString = year + "-" + m + "-" + d; 157 return dateString; 158 } 159 160 // 用年月生成年月日期字符串,month取值范围:[0,13] 161 public static String getDateString(int year, int month) { 162 String m; 163 String d; 164 165 if (month >= 9) { 166 m = (month + 1) + ""; 167 } else { 168 m = "0" + (month + 1); 169 } 170 171 String dateString = year + "-" + m; 172 return dateString; 173 } 174 175 // 用时、分生成时间字符串 176 public static String getTimeString(int hour, int minute) { 177 String h; 178 String m; 179 180 if (hour >= 10) { 181 h = hour + ""; 182 } else { 183 h = "0" + hour; 184 } 185 186 if (minute >= 10) { 187 m = minute + ""; 188 } else { 189 m = "0" + minute; 190 } 191 192 return h + ":" + m; 193 } 194 195 // 用时、分、秒生成时间字符串 196 public static String getTimeString(int hour, int minute, int second) { 197 String h; 198 String m; 199 String c; 200 201 if (hour >= 10) { 202 h = hour + ""; 203 } else { 204 h = "0" + hour; 205 } 206 207 if (minute >= 10) { 208 m = minute + ""; 209 } else { 210 m = "0" + minute; 211 } 212 213 if (second >= 10) { 214 c = second + ""; 215 } else { 216 c = "0" + second; 217 } 218 219 return h + ":" + m + ":" + c; 220 } 221 222 // 该内部类是用于对日期时间字符串数组进行排序的 223 public class DateTimeString implements Comparable<DateTimeString> { 224 private String mDateTimeStr; 225 226 public DateTimeString(String dateTimeStr) { 227 mDateTimeStr = dateTimeStr; 228 } 229 230 @Override 231 public int compareTo(DateTimeString another) { 232 return compareDateTimeString(mDateTimeStr.toString(), 233 another.toString()); 234 } 235 236 @Override 237 public String toString() { 238 return mDateTimeStr; 239 } 240 241 } 242 243 // 对日期时间字符串数组进行排序,返回排序后的数组(排序后的顺序是从小到大) 244 public static String[] sortDateTimeStringArray(String[] dateTimeStringArray) { 245 // 将日期时间字符串数组转换成DateTimeString类型数组,DateTimeString实现了Comparable接口,可以进行排序 246 DateTimeString[] tmpArray = new DateTimeString[dateTimeStringArray.length]; 247 248 // 生成tmpArray数组 249 int i = 0; 250 DateTimeUtil tmpUtil = new DateTimeUtil(); 251 for (String str : dateTimeStringArray) { 252 tmpArray[i++] = tmpUtil.new DateTimeString(str); 253 } 254 255 // 对tmpArray进行排序 256 Arrays.sort(tmpArray); 257 258 String[] result = new String[tmpArray.length]; 259 i = 0; 260 for (DateTimeString str : tmpArray) { 261 result[i++] = str.toString(); 262 } 263 return result; 264 } 265 266 // 比较两个日期时间字符串的大小,如果str1比str2早,则返回-1,如果相等返回0,否则返回1 267 public static int compareDateTimeString(String str1, String str2) { 268 Date d1 = StringToDate(str1); 269 Date d2 = StringToDate(str2); 270 if (d1.getTime() - d2.getTime() < 0) { 271 return -1; 272 } else if (d1.getTime() - d2.getTime() > 0) { 273 return 1; 274 } else { 275 return 0; 276 } 277 278 } 279 280 // 时间日期字符串转换成Date对象 281 // 注:dateTimeStr带不带前导0都是可以的,比如"2011-01-01 01:02:03"和"2011-1-1 1:2:3"都是合法的 282 public static Date StringToDate(String dateTimeStr) { 283 Date date = new Date(); 284 // DateFormat fmt = DateFormat.getDateTimeInstance(); 285 DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 286 try { 287 date = fmt.parse(dateTimeStr); 288 return date; 289 } catch (ParseException e) { 290 e.printStackTrace(); 291 } 292 return date; 293 } 294 295 // Date对象转换成日期时间字符串 296 public static String DateToString(Date date) { 297 String dateTimeStr = null; 298 // DateFormat fmt = DateFormat.getDateTimeInstance(); 299 DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 300 dateTimeStr = fmt.format(date); 301 return dateTimeStr; 302 } 303 304 // 字符串转换成Calendar 305 public static Calendar StringToGregorianCalendar(String dateTimeStr) { 306 Date date = StringToDate(dateTimeStr); 307 Calendar calendar = new GregorianCalendar(); 308 309 calendar.setTime(date); 310 return calendar; 311 } 312 313 // Calendar转换成String 314 public static String CalendarToString(Calendar calendar) { 315 Date date = ((GregorianCalendar) calendar).getTime(); 316 return DateToString(date); 317 } 318 319 // 获取日期时间格式字符串表示的两日期时间之间相隔的天数(天数可为浮点型) AC 320 public static double getDayNumDif(String str1, String str2) { 321 // DateFormat fmt = DateFormat.getDateTimeInstance(); 322 DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 323 try { 324 Date d1 = fmt.parse(str1); 325 Date d2 = fmt.parse(str2); 326 long dif = Math.abs(d1.getTime() - d2.getTime()); 327 double dayDif = (double) (dif) / 1000 / (24 * 60 * 60); 328 329 // 保留两位小数 330 DecimalFormat df = new DecimalFormat("0.00"); 331 return Double.parseDouble(df.format(dayDif)); 332 } catch (ParseException e) { 333 e.printStackTrace(); 334 } 335 return -1; 336 } 337 338 // 求算术平均值函数,保留2位小数 339 public static double getAverage(double[] data) { 340 double sum = 0; 341 for (int i = 0; i < data.length; i++) { 342 sum += data[i]; 343 } 344 345 DecimalFormat df = new DecimalFormat("0.00"); 346 return Double.parseDouble(df.format(sum / data.length)); 347 } 348 349 // 输入一个时间日期字符串(格式:“yyyy-MM-dd HH:mm:ss”),输出num天后的时间日期字符串(num可为浮点数) 350 public static String getNDayLatterDateTime(String str, double num) { 351 // 创建日期时间格式对象fmt 352 // DateFormat fmt = DateFormat.getDateTimeInstance(); 353 DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 354 try { 355 Date curDate = fmt.parse(str); 356 GregorianCalendar calendar = new GregorianCalendar(); 357 calendar.setTime(curDate); 358 359 calendar.add(Calendar.SECOND, (int) (num * (24 * 60 * 60))); 360 361 Date newDate = calendar.getTime(); 362 String newDateStr = fmt.format(newDate); 363 return newDateStr; 364 } catch (ParseException e) { 365 e.printStackTrace(); 366 } 367 return ""; 368 } 369 370 }
(二)Demo
根据上面AlarmManagerUtil中的方法,下面要编写一个Demo,主要的功能就是能创建和取消可以周期执行的定时任务,定时任务的创建是在服务中执行的,而任务的具体内容是在广播接收器中执行的,并且在手机重启后定时任务还能正常运作。
1、布局文件activity_main.xml:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:layout_width="match_parent" 3 android:layout_height="match_parent" 4 android:orientation="vertical" > 5 6 <Button 7 android:id="@+id/start_service_btn" 8 android:layout_width="match_parent" 9 android:layout_height="wrap_content" 10 android:text="开启定时服务" /> 11 12 <Button 13 android:id="@+id/cancel_service_btn" 14 android:layout_width="match_parent" 15 android:layout_height="wrap_content" 16 android:text="取消定时服务" /> 17 18 </LinearLayout>
2、MainActivity:
1 import java.util.Calendar; 2 3 import android.app.Activity; 4 import android.content.Intent; 5 import android.os.Bundle; 6 import android.view.View; 7 import android.view.View.OnClickListener; 8 import android.widget.Button; 9 10 public class MainActivity extends Activity implements OnClickListener { 11 12 // 开启服务按钮 13 private Button startServiceBtn; 14 // 取消服务按钮 15 private Button cancelServiceBtn; 16 17 // 模拟的task id 18 private static int mTaskId = 0; 19 20 @Override 21 protected void onCreate(Bundle savedInstanceState) { 22 super.onCreate(savedInstanceState); 23 setContentView(R.layout.activity_main); 24 25 startServiceBtn = (Button) findViewById(R.id.start_service_btn); 26 cancelServiceBtn = (Button) findViewById(R.id.cancel_service_btn); 27 28 startServiceBtn.setOnClickListener(this); 29 cancelServiceBtn.setOnClickListener(this); 30 } 31 32 @Override 33 public void onClick(View v) { 34 switch (v.getId()) { 35 case R.id.start_service_btn: 36 Intent i = new Intent(this, AlarmService.class); 37 // 获取20秒之后的日期时间字符串 38 i.putExtra("alarm_time", 39 DateTimeUtil.getNLaterDateTimeString(Calendar.SECOND, 20)); 40 i.putExtra("task_id", mTaskId); 41 startService(i); 42 break; 43 case R.id.cancel_service_btn: 44 AlarmManagerUtil.cancelAlarmBroadcast(this, mTaskId, 45 AlarmReceiver.class); 46 break; 47 default: 48 break; 49 } 50 } 51 }
3、创建一个服务AlarmService,为了让不同的定时任务运行在不同的线程中,让AlarmService继承支持线程的IntentService类:
1 import java.util.Date; 2 3 import android.app.AlarmManager; 4 import android.app.IntentService; 5 import android.app.PendingIntent; 6 import android.app.Service; 7 import android.content.Intent; 8 import android.os.IBinder; 9 import android.os.SystemClock; 10 import android.util.Log; 11 12 public class AlarmService extends IntentService { 13 14 // 从其他地方通过Intent传递过来的提醒时间 15 private String alarmDateTime; 16 17 public AlarmService() { 18 super("AlarmService"); 19 } 20 21 @Override 22 protected void onHandleIntent(Intent intent) { 23 alarmDateTime = intent.getStringExtra("alarm_time"); 24 // taskId用于区分不同的任务 25 int taskId = intent.getIntExtra("task_id", 0); 26 27 Log.d("AlarmService", "executed at " + new Date().toString() 28 + " @Thread id:" + Thread.currentThread().getId()); 29 30 long alarmDateTimeMillis = DateTimeUtil.stringToMillis(alarmDateTime); 31 32 AlarmManagerUtil.sendRepeatAlarmBroadcast(this, taskId, 33 AlarmManager.RTC_WAKEUP, alarmDateTimeMillis, 10 * 1000, 34 AlarmReceiver.class); 35 36 } 37 38 @Override 39 public void onDestroy() { 40 super.onDestroy(); 41 Log.d("Destroy", "Alarm Service Destroy"); 42 } 43 44 }
4、创建广播接收器AlarmReceiver:
1 import android.content.BroadcastReceiver; 2 import android.content.Context; 3 import android.content.Intent; 4 import android.text.method.DateTimeKeyListener; 5 import android.util.Log; 6 import android.widget.Toast; 7 8 public class AlarmReceiver extends BroadcastReceiver { 9 10 @Override 11 public void onReceive(Context context, Intent intent) { 12 ToastUtil.showShort(context, 13 "从服务启动广播:at " + DateTimeUtil.getCurrentDateTimeString()); 14 Log.d("Alarm", "从服务启动广播:at " + DateTimeUtil.getCurrentDateTimeString()); 15 } 16 17 }
5、为了让定时服务在手机重启后也能正常运行,再创建一个系统广播接收器BootCompleteReceiver,监听手机一旦开机完成就启动服务AlarmService:
1 /** 2 * 开机重新启动服务AlarmService 3 * 4 */ 5 6 public class BootCompleteReceiver extends BroadcastReceiver { 7 // 模拟的task id 8 private static int mTaskId = 0; 9 10 @Override 11 public void onReceive(Context context, Intent intent) { 12 Log.d("定时服务", "开机启动"); 13 ToastUtil.showShort(context, "定时服务开机启动"); 14 Intent i = new Intent(context, AlarmService.class); 15 // 获取3分钟之后的日期时间字符串 16 i.putExtra("alarm_time", 17 DateTimeUtil.getNLaterDateTimeString(Calendar.MINUTE, 3)); 18 i.putExtra("task_id", mTaskId); 19 context.startService(i); 20 } 21 }
6、最后是在AndroidManifest.xml文件中注册服务、广播接收器,申请相应的权限:
1 <?xml version="1.0" encoding="utf-8"?> 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 3 package="com.example.servicebestpractice" 4 android:versionCode="1" 5 android:versionName="1.0" > 6 7 <uses-sdk 8 android:minSdkVersion="14" 9 android:targetSdkVersion="14" /> 10 11 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> 12 13 <application 14 android:allowBackup="true" 15 android:icon="@drawable/ic_launcher" 16 android:label="@string/app_name" 17 android:theme="@style/AppTheme" > 18 <activity 19 android:name=".MainActivity" 20 android:label="@string/app_name" > 21 <intent-filter> 22 <action android:name="android.intent.action.MAIN" /> 23 24 <category android:name="android.intent.category.LAUNCHER" /> 25 </intent-filter> 26 </activity> 27 28 <service android:name=".AlarmService" > 29 </service> 30 31 <receiver android:name=".AlarmReceiver" > 32 </receiver> 33 <receiver android:name=".BootCompleteReceiver" > 34 <intent-filter> 35 <action android:name="android.intent.action.BOOT_COMPLETED" /> 36 </intent-filter> 37 </receiver> 38 </application> 39 40 </manifest>
7、点击“开启定时服务”按钮后一段时间,最后点击“取消定时服务”按钮后的运行结果截图:
8、存在的问题:定时任务在手机上测试时,计时不精确,但在模拟器上是精确的。这个问题现在还没有找到很好的解决方案。