AlarmManager(闹钟服务)
使用 AlarmManager (闹钟服务)
AlarmManager
是 Android 提供的一个系统服务类,用于调度定时任务或重复任务(如设置闹钟、定时通知)。它允许开发者在指定时间或周期性地触发 PendingIntent
,适合执行后台操作,如发送通知、启动服务或广播。结合 Android 动画合集的背景,AlarmManager
可用于触发动画效果(如定时播放动画或音效),增强用户交互体验。本文将详细介绍 AlarmManager
的功能、使用方法、权限要求及注意事项,重点展示如何设置单次和重复闹钟,并结合动画实现交互反馈。
AlarmManager 的作用与原理
- 作用:
AlarmManager
提供定时或周期性任务调度,支持在特定时间或间隔触发PendingIntent
(可启动 Activity、服务或广播)。它适用于需要精确或近似定时执行的任务,如提醒、通知或数据同步。 - 原理:
AlarmManager
与 Android 系统时钟和电源管理交互,调度任务即使应用未运行也能执行(通过唤醒设备)。它支持多种闹钟类型(如 RTC、ELAPSED_REALTIME),并与 Doze 模式和权限限制(如 API 31+)紧密相关。 - 应用场景:
- 定时触发通知(如每日提醒动画)。
- 周期性执行任务(如同步数据后更新动画)。
- 结合
Vibrator
或SoundPool
实现闹钟效果。 - 在特定时间播放动画或音效(如日程提醒)。
权限要求
使用 AlarmManager
的某些功能需要以下权限,在 AndroidManifest.xml
中声明:
<!-- 精确闹钟(API 31+) -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<!-- 接收广播(如果使用 BroadcastReceiver) -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
- 动态权限(API 31+):
SCHEDULE_EXACT_ALARM
:设置精确闹钟(API 31+)。- 使用
ActivityCompat.requestPermissions
请求。 - 普通权限(无需动态请求):
RECEIVE_BOOT_COMPLETED
:设备重启后重新注册闹钟。- 注意:API 23+(Android 6.0)引入 Doze 模式,影响非精确闹钟;API 31+(Android 12)对精确闹钟需用户授权。
获取 AlarmManager
通过 Context
获取 AlarmManager
实例:
import android.app.AlarmManager;
import android.content.Context;
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
常用功能与方法
以下是 AlarmManager
的核心方法:
方法/功能 | 描述 | API 级别 | 用法示例 |
---|---|---|---|
set(int type, long triggerAtMillis, PendingIntent operation) | 设置单次闹钟(API 19 弃用)。 | 1+ (API 19 弃用) | alarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); |
setExact(int type, long triggerAtMillis, PendingIntent operation) | 设置精确单次闹钟。 | 19+ | alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); |
setRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) | 设置重复闹钟(API 19 弃用)。 | 1+ (API 19 弃用) | alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, triggerTime, interval, pendingIntent); |
setInexactRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) | 设置非精确重复闹钟(节省电量)。 | 19+ | alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, triggerTime, interval, pendingIntent); |
setExactAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) | 设置精确闹钟,允许在 Doze 模式下触发。 | 23+ | alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); |
cancel(PendingIntent operation) | 取消指定闹钟。 | 1+ | alarmManager.cancel(pendingIntent); |
canScheduleExactAlarms() | 检查是否可设置精确闹钟(API 31+)。 | 31+ | boolean canSchedule = alarmManager.canScheduleExactAlarms(); |
- 闹钟类型:
RTC_WAKEUP
:基于实时时钟(RTC),唤醒设备。RTC
:基于 RTC,不唤醒设备。ELAPSED_REALTIME_WAKEUP
:基于开机时间,唤醒设备。ELAPSED_REALTIME
:基于开机时间,不唤醒设备。- 时间参数:
triggerAtMillis
:触发时间(System.currentTimeMillis()
或SystemClock.elapsedRealtime()
)。intervalMillis
:重复间隔(如AlarmManager.INTERVAL_DAY
)。
完整示例(定时闹钟 + 动画 + 通知)
以下是一个使用 AlarmManager
设置定时闹钟的示例,触发通知并结合按钮动画效果。
import android.app.AlarmManager;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.SystemClock;
import android.widget.Button;
import android.widget.Toast;
import android.animation.ObjectAnimator;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;
public class AlarmAnimationActivity extends AppCompatActivity {
private AlarmManager alarmManager;
private Button setAlarmButton;
private ObjectAnimator scaleAnimator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_alarm_animation);
setAlarmButton = findViewById(R.id.set_alarm_button);
alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
// 初始化动画
scaleAnimator = ObjectAnimator.ofFloat(setAlarmButton, "scaleX", 1f, 1.2f, 1f);
scaleAnimator.setDuration(200);
// 创建通知渠道(API 26+)
createNotificationChannel();
// 设置闹钟按钮
setAlarmButton.setOnClickListener(v -> {
setAlarm();
scaleAnimator.start();
Toast.makeText(this, "Alarm set for 10 seconds from now", Toast.LENGTH_SHORT).show();
});
}
private void setAlarm() {
// 创建 PendingIntent
Intent intent = new Intent(this, AlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
// 设置闹钟(10秒后触发)
long triggerTime = SystemClock.elapsedRealtime() + 10_000; // 10秒后
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
if (alarmManager.canScheduleExactAlarms()) {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTime, pendingIntent);
} else {
Toast.makeText(this, "Exact alarms not permitted", Toast.LENGTH_SHORT).show();
}
} else {
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTime, pendingIntent);
}
}
private void createNotificationChannel() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
"ALARM_CHANNEL",
"Alarm Notifications",
NotificationManager.IMPORTANCE_HIGH
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
scaleAnimator.cancel();
}
// 广播接收器处理闹钟触发
public static class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "ALARM_CHANNEL")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle("Alarm Triggered")
.setContentText("Your alarm has gone off!")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true);
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(1, builder.build());
}
}
}
- 布局 XML(res/layout/activity_alarm_animation.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/set_alarm_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Set Alarm" />
</LinearLayout>
- AndroidManifest.xml(添加权限和广播接收器):
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<application>
<receiver android:name=".AlarmAnimationActivity$AlarmReceiver"
android:exported="false" />
</application>
- 说明:
- 权限:声明
SCHEDULE_EXACT_ALARM
权限,API 31+ 检查canScheduleExactAlarms()
。 - 闹钟:设置 10 秒后触发的单次闹钟,使用
setExactAndAllowWhileIdle
(API 23+)以兼容 Doze 模式。 - 通知:闹钟触发时通过
BroadcastReceiver
发送通知。 - 动画:点击按钮时触发缩放动画(
scaleX
)。 - 生命周期:
onDestroy
取消动画。
设置重复闹钟
实现每日重复闹钟:
long triggerTime = System.currentTimeMillis() + 10_000; // 10秒后首次触发
long interval = AlarmManager.INTERVAL_DAY; // 每天重复
alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, triggerTime, interval, pendingIntent);
取消闹钟
取消指定闹钟:
alarmManager.cancel(pendingIntent);
结合 Vibrator 和 SoundPool
结合 Vibrator
和 SoundPool
(前文介绍)增强闹钟效果:
public static class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createOneShot(200, VibrationEffect.DEFAULT_AMPLITUDE));
} else {
vibrator.vibrate(200);
}
SoundPool soundPool = new SoundPool.Builder().setMaxStreams(1).build();
int soundId = soundPool.load(context, R.raw.alarm_sound, 1);
soundPool.setOnLoadCompleteListener((pool, sampleId, status) -> {
pool.play(sampleId, 1.0f, 1.0f, 1, 0, 1.0f);
});
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "ALARM_CHANNEL")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle("Alarm with Vibration")
.setContentText("Your alarm is ringing!")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true);
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(1, builder.build());
}
}
优缺点
- 优点:
- 支持精确和非精确定时任务。
- 可唤醒设备执行操作(
RTC_WAKEUP
)。 - 兼容 Doze 模式(
setExactAndAllowWhileIdle
)。 - 缺点:
- API 31+ 精确闹钟需用户授权。
- Doze 模式和电池优化可能延迟非精确闹钟。
- 依赖
PendingIntent
,需正确管理。 - 替代方案:
- WorkManager:适合周期性或延迟任务,兼容 Doze 模式。
- JobScheduler:API 21+,适合后台任务。
- Foreground Service:需持续运行的任务。
- Firebase Cloud Messaging:云端触发的通知。
注意事项
- 权限:API 31+ 需检查
canScheduleExactAlarms()
并引导用户授权。 - Doze 模式:API 23+ 的 Doze 和 App Standby 可能延迟非精确闹钟,使用 gars
使用setExactAndAllowWhileIdle
。 - 设备重启:设备重启后闹钟会丢失,需在
RECEIVE_BOOT_COMPLETED
广播中重新注册。 - PendingIntent:确保使用
FLAG_IMMUTABLE
(API 23+)或FLAG_UPDATE_CURRENT
。 - 调试:通过 Log 检查闹钟触发时间和
PendingIntent
执行情况。 - 电池优化:避免在受限应用中运行(如电池优化白名单)。
扩展:Jetpack Compose 中的实现
在 Jetpack Compose 中,AlarmManager
可结合 Composable 实现闹钟设置和动画:
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.SystemClock
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
@Composable
fun AlarmScreen() {
val context = LocalContext.current
val alarmManager = remember { context.getSystemService(Context.ALARM_SERVICE) as AlarmManager }
var isAlarmSet by remember { mutableStateOf(false) }
val scale by animateFloatAsState(
targetValue = if (isAlarmSet) 1.2f else 1f,
animationSpec = tween(200)
)
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Button(
onClick = {
val intent = Intent(context, AlarmAnimationActivity.AlarmReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val triggerTime = SystemClock.elapsedRealtime() + 10_000 // 10秒后
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) {
// Handle permission
} else {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTime, pendingIntent)
isAlarmSet = true
}
},
modifier = Modifier.scale(scale)
) {
Text("Set Alarm")
}
}
DisposableEffect(Unit) {
onDispose {
// Clean up if needed
}
}
}
- 说明:
- 使用
remember
缓存AlarmManager
。 animateFloatAsState
实现按钮缩放动画。- 设置 10 秒后触发的闹钟。
DisposableEffect
可用于清理资源(本例无需额外清理)。
示例:结合振动和通知
在 AlarmReceiver
中添加振动效果:
public static class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createOneShot(200, VibrationEffect.DEFAULT_AMPLITUDE));
} else {
vibrator.vibrate(200);
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "ALARM_CHANNEL")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle("Alarm Triggered")
.setContentText("Time's up with vibration!")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true);
NotificationManagerCompat.from(context).notify(1, builder.build());
}
}
If you need more advanced features (e.g., repeating alarms with custom intervals, integration with WorkManager
for complex scheduling, or Canvas-based alarm visualizations) or want to explore other parts of the Android animation collection (e.g., transition animations, Lottie), please let me know!