如何复现Android系统闹钟应用的亮屏跳转Activity及弹窗行为?
实现系统时钟风格的计时器提醒行为
要复现系统时钟的两种计时器结束行为,需解决权限限制、后台触发逻辑、状态判断三个核心问题,以下是可直接落地的实现方案:
一、先配置必要权限与组件
1. 清单文件(AndroidManifest.xml)权限声明
<!-- 唤醒屏幕、保持亮屏 --> <uses-permission android:name="android.permission.WAKE_LOCK" /> <!-- Android 13+ 通知权限(可选,若需辅助通知) --> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- 解锁状态下显示悬浮弹窗 --> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <!-- 锁屏下全屏启动Activity的权限 --> <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
2. 配置专属停止Activity
在Manifest中注册处理锁屏场景的Activity,需添加锁屏显示、亮屏相关属性:
<activity android:name=".TimerStopActivity" android:showOnLockScreen="true" android:showWhenLocked="true" android:turnScreenOn="true" android:excludeFromRecents="true" android:launchMode="singleInstance"> <intent-filter> <action android:name="com.your.package.ACTION_STOP_TIMER" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>
3. 注册广播接收器
用于接收AlarmManager的触发信号,避免直接后台启动Activity被系统限制:
<receiver android:name=".TimerTriggerReceiver" />
二、核心逻辑实现
1. 设置计时器(AlarmManager正确调用)
使用setExactAndAllowWhileIdle确保休眠状态下也能精准触发,适配Android 12+的PendingIntent权限:
fun startTimer(context: Context, delayMillis: Long) { val alarmManager = context.getSystemService(AlarmManager::class.java) val triggerIntent = Intent(context, TimerTriggerReceiver::class.java) val pendingIntent = PendingIntent.getBroadcast( context, 0, // 可自定义请求码区分多个计时器 triggerIntent, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE else PendingIntent.FLAG_UPDATE_CURRENT ) // RTC_WAKEUP:基于系统时间,触发时唤醒设备 alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + delayMillis, pendingIntent ) }
2. 广播接收器中判断设备状态并处理
在接收器中判断当前是否锁屏/熄屏,分别执行弹窗或启动Activity逻辑:
class TimerTriggerReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val keyguardManager = context.getSystemService(KeyguardManager::class.java) val powerManager = context.getSystemService(PowerManager::class.java) val isLocked = keyguardManager.isKeyguardLocked || keyguardManager.isDeviceLocked val isScreenOn = powerManager.isInteractive if (isLocked || !isScreenOn) { // 锁屏/熄屏:启动专属停止Activity val activityIntent = Intent(context, TimerStopActivity::class.java) activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) context.startActivity(activityIntent) } else { // 解锁状态:显示系统级悬浮弹窗 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(context)) { // 若未获取悬浮窗权限,跳转至权限设置页 val permissionIntent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:${context.packageName}")) permissionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(permissionIntent) return } val dialog = AlertDialog.Builder(context, R.style.Theme_AppCompat_Dialog_Alert) .setTitle("计时器结束") .setMessage("时间到了!") .setPositiveButton("停止") { _, _ -> // 执行停止计时器的逻辑 } .setNegativeButton("返回应用") { _, _ -> // 跳转回应用主界面 val appIntent = context.packageManager.getLaunchIntentForPackage(context.packageName) appIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(appIntent) } .create() dialog.window?.apply { // 设置弹窗类型为系统悬浮窗 setType( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY else WindowManager.LayoutParams.TYPE_SYSTEM_ALERT ) setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT) } dialog.show() } } }
3. 锁屏Activity的亮屏与解锁处理
在TimerStopActivity中保持屏幕亮着,可选解锁锁屏:
class TimerStopActivity : AppCompatActivity() { private lateinit var wakeLock: PowerManager.WakeLock override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_timer_stop) // 保持屏幕亮着,最多10分钟 val powerManager = getSystemService(PowerManager::class.java) wakeLock = powerManager.newWakeLock( PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP, "Timer:WakeLock" ) wakeLock.acquire(10 * 60 * 1000L) // 可选:请求解锁锁屏(需用户授权) val keyguardManager = getSystemService(KeyguardManager::class.java) keyguardManager.requestDismissKeyguard(this, null) // 停止按钮逻辑 findViewById<Button>(R.id.btn_stop).setOnClickListener { if (wakeLock.isHeld) wakeLock.release() finish() } } override fun onDestroy() { super.onDestroy() if (::wakeLock.isInitialized && wakeLock.isHeld) { wakeLock.release() } } }
三、关键注意事项
- Android 6+动态权限:
SYSTEM_ALERT_WINDOW是特殊权限,需主动引导用户到设置页开启 - 后台启动限制:Android 10+禁止直接后台启动Activity,必须通过广播接收器中转判断状态
- WakeLock释放:务必在Activity销毁时释放WakeLock,避免耗电
- PendingIntent适配:Android 12+必须指定
FLAG_MUTABLE或FLAG_IMMUTABLE,这里用MUTABLE因为需要修改Intent内容
内容的提问来源于stack exchange,提问作者Jack M
相关产品推荐
相关产品推荐

