You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何复现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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 06:15:22