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

如何在Glance Widget中使用Timer实现秒级计时并长期保活?

用Glance Widget实现秒级计时的详细方案

核心思路

Glance Widget基于Android AppWidget机制,本身无法直接维持长周期的计时逻辑——因为Widget的Provider是广播接收器,生命周期极短。要实现秒级计时,必须依赖**前台服务(Foreground Service)**承载Timer逻辑,通过定时更新Widget的UI来展示实时计时数据,同时用持久化存储保存计时状态,避免进程重启后数据丢失。

Timer的编写位置

绝对不能将Timer写在Glance Widget的Provider类中,必须放在前台服务里:

  • 前台服务拥有独立的生命周期,不会随Widget的更新回调结束而销毁;
  • Android 8.0+对后台服务限制严格,前台服务通过显示持续通知获得更高优先级,不易被系统回收。

长期保持运行的关键措施

  1. 使用Foreground Service:强制显示前台通知,规避后台服务的系统限制;
  2. 状态持久化:用SharedPreferences保存累计秒数,服务重启后可恢复计时;
  3. 引导用户开启忽略电池优化:低电量场景下,系统会主动回收非白名单进程,需用户手动授权将应用加入电池优化白名单;
  4. 避免内存泄漏:服务销毁时必须取消Timer,释放资源。

完整实现步骤

1. 配置Glance Widget基础结构

创建GlanceWidget子类,负责渲染计时UI和处理启动/停止交互:

class TimerGlanceWidget : GlanceAppWidget() {
    override suspend fun provideGlance(context: Context, id: GlanceId) {
        val prefs = context.getSharedPreferences("timer_prefs", Context.MODE_PRIVATE)
        val elapsedSeconds = prefs.getLong("elapsed_seconds", 0L)
        
        content {
            Column(
                modifier = GlanceModifier.fillMaxSize().padding(16.dp),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalAlignment = Alignment.CenterVertically
            ) {
                Text(
                    text = String.format("%02d:%02d", elapsedSeconds / 60, elapsedSeconds % 60),
                    style = TextStyle(fontSize = 24.sp, fontWeight = FontWeight.Bold)
                )
                Row(modifier = GlanceModifier.padding(top = 16.dp)) {
                    Button(text = "启动", onClick = actionStartService<TimerService>())
                    Button(text = "停止", onClick = actionStopService<TimerService>(), modifier = GlanceModifier.padding(start = 8.dp))
                }
            }
        }
    }
}

2. 实现前台计时服务

创建承载Timer逻辑的Foreground Service,每秒更新计时数据并同步到Widget:

class TimerService : Service() {
    private lateinit var timer: Timer
    private var elapsedSeconds = 0L
    private val prefs by lazy { getSharedPreferences("timer_prefs", MODE_PRIVATE) }

    override fun onCreate() {
        super.onCreate()
        // 恢复上次计时状态
        elapsedSeconds = prefs.getLong("elapsed_seconds", 0L)
        // 启动前台服务,显示通知
        val notification = NotificationCompat.Builder(this, "timer_channel")
            .setContentTitle("秒级计时器")
            .setContentText("当前计时: ${String.format("%02d:%02d", elapsedSeconds/60, elapsedSeconds%60)}")
            .setSmallIcon(R.drawable.ic_timer)
            .build()
        startForeground(1, notification)
        // 初始化Timer,每秒执行一次
        timer = Timer()
        timer.scheduleAtFixedRate(object : TimerTask() {
            override fun run() {
                elapsedSeconds++
                // 保存计时状态
                prefs.edit().putLong("elapsed_seconds", elapsedSeconds).apply()
                // 更新Widget UI
                updateWidget()
                // 更新前台通知内容
                updateNotification()
            }
        }, 0, 1000)
    }

    private fun updateWidget() {
        val glanceId = GlanceAppWidgetManager(this).getGlanceIds(TimerGlanceWidget::class.java).firstOrNull()
        glanceId?.let { TimerGlanceWidget().update(this, it) }
    }

    private fun updateNotification() {
        val notification = NotificationCompat.Builder(this, "timer_channel")
            .setContentTitle("秒级计时器")
            .setContentText("当前计时: ${String.format("%02d:%02d", elapsedSeconds/60, elapsedSeconds%60)}")
            .setSmallIcon(R.drawable.ic_timer)
            .build()
        (getSystemService(NOTIFICATION_SERVICE) as NotificationManager).notify(1, notification)
    }

    override fun onDestroy() {
        super.onDestroy()
        timer.cancel()
        // 保存最终计时状态
        prefs.edit().putLong("elapsed_seconds", elapsedSeconds).apply()
    }

    override fun onBind(intent: Intent): IBinder? = null
}

3. 配置清单文件

在AndroidManifest.xml中注册Widget和服务,并声明必要权限:

<manifest ...>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <!-- 可选:申请忽略电池优化权限,需用户手动授权 -->
    <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />

    <application ...>
        <!-- 注册Widget接收器 -->
        <receiver
            android:name=".TimerGlanceWidgetReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/timer_widget_info" />
        </receiver>

        <!-- 注册计时服务 -->
        <service
            android:name=".TimerService"
            android:foregroundServiceType="dataSync" />
    </application>
</manifest>

4. Widget配置文件

在res/xml/timer_widget_info.xml中定义Widget的基础属性:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="180dp"
    android:minHeight="120dp"
    android:updatePeriodMillis="0" <!-- 禁用系统自动更新,由服务手动控制 -->
    android:widgetCategory="home_screen" />

注意事项

  • 首次启动前需创建通知渠道:Android 8.0+要求前台通知必须关联渠道,可在应用启动时初始化;
  • 服务重启处理:若系统因资源紧张杀死服务,可通过onStartCommand返回START_STICKY尝试自动重启,但无法保证100%生效;
  • 性能优化:每秒更新Widget会消耗少量系统资源,若无需严格秒级,可适当延长更新间隔。

内容的提问来源于stack exchange,提问作者wzk

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 21:18:27