Android通知中Chronometer与ProgressBar应用关闭后异常问题求助
解决Android自定义通知中Chronometer和ProgressBar在应用被杀后行为异常的问题
问题原因
自定义通知里的Chronometer和ProgressBar由SystemUI进程托管,但它们的更新逻辑默认依赖你的应用进程。当应用被关闭或杀死后,进程终止,系统无法继续维护这两个控件的计时/进度更新,导致行为异常。
解决方案
使用**前台服务(Foreground Service)**托管通知,前台服务拥有更高的系统优先级,不易被杀死,可在后台持续运行并定时更新通知的RemoteView内容,确保Chronometer和ProgressBar正常工作。
步骤1:创建前台服务
创建Foreground Service,负责初始化通知、定时更新Chronometer剩余时间和ProgressBar进度:
class NotificationUpdateService : Service() { private lateinit var notificationManager: NotificationManager private lateinit var collapsedView: RemoteViews private lateinit var expandedNotifView: RemoteViews private var notificationId = 123 private var remainingTime: Long = 0L private val handler = Handler(Looper.getMainLooper()) private val updateRunnable = object : Runnable { override fun run() { remainingTime -= 1000 if (remainingTime <= 0) { // 倒计时结束,清理通知并停止服务 stopForeground(STOP_FOREGROUND_REMOVE) stopSelf() return } // 手动格式化剩余时间并更新Chronometer文本 val timeStr = String.format("%tH:%tM:%tS", Date(remainingTime), Date(remainingTime), Date(remainingTime)) collapsedView.setTextViewText(R.id.myChronometere, timeStr) expandedNotifView.setTextViewText(R.id.myChronometere, timeStr) // 更新ProgressBar(示例总时长为1小时,需根据实际需求修改) val totalDuration = 3600000L val progress = ((totalDuration - remainingTime) * 100 / totalDuration).toInt() collapsedView.setProgressBar(R.id.myProgressBar, 100, progress, false) expandedNotifView.setProgressBar(R.id.myProgressBar, 100, progress, false) // 更新通知 notificationManager.notify(notificationId, buildNotification()) handler.postDelayed(this, 1000) } } override fun onCreate() { super.onCreate() notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // 初始化折叠和展开状态的RemoteView collapsedView = RemoteViews(packageName, R.layout.custom_notification_id27_collapsed) expandedNotifView = RemoteViews(packageName, R.layout.custom_notification_id27) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { // 接收传递的参数 remainingTime = intent?.getLongExtra("remaining_time", 0L) ?: 0L val heading = intent?.getStringExtra("heading") ?: "" val content = intent?.getStringExtra("content") ?: "" val img = intent?.getParcelableExtra<Bitmap>("img") // 初始化RemoteView的文本、图片和点击事件 initRemoteViewContent(heading, content, img, intent) // 启动前台服务 startForeground(notificationId, buildNotification()) // 开始定时更新 handler.post(updateRunnable) return START_STICKY } private fun initRemoteViewContent(heading: String, content: String, img: Bitmap?, intent: Intent?) { // 设置展开视图内容 expandedNotifView.setTextViewText(R.id.notification_content_title3, heading) expandedNotifView.setTextViewText(R.id.notification_content_text3, content) img?.let { expandedNotifView.setImageViewBitmap(R.id.productImageView_large, it) } expandedNotifView.setViewVisibility(R.id.productImageView_large, View.VISIBLE) expandedNotifView.setViewVisibility(R.id.rlFlipper, View.GONE) expandedNotifView.setViewVisibility(R.id.viewFlipperLarge, View.GONE) // 设置图片点击事件 val imgIntent = GcmIntentService.setSlotIntentTemplate( this, intent?.getStringExtra("uuid") ?: "", intent?.getStringExtra("link") ?: "", GcmIntentService.CTA_ACTION_TEMPLATE_27, intent?.getStringExtra("tId") ?: "", "", "0", intent?.getStringExtra("fullImg") ?: "" ) imgIntent.putExtra("notificationId", notificationId) val contentIntentImg = PendingIntent.getActivity(this, notificationId, imgIntent, PendingIntent.FLAG_IMMUTABLE) expandedNotifView.setOnClickPendingIntent(R.id.productImageView_large, contentIntentImg) // 设置前后导航点击事件 val clickIntentNext = intent?.getParcelableExtra<Intent>("clickIntentNext") ?: Intent() val contentIntentNext = PendingIntent.getBroadcast(this, notificationId, clickIntentNext, PendingIntent.FLAG_IMMUTABLE) expandedNotifView.setOnClickPendingIntent(R.id.imgNext, contentIntentNext) expandedNotifView.setViewVisibility(R.id.imgNext, View.VISIBLE) collapsedView.setViewVisibility(R.id.imgNext, View.VISIBLE) val clickIntentPrev = intent?.getParcelableExtra<Intent>("clickIntentPrev") ?: Intent() val contentIntentPrev = PendingIntent.getBroadcast(this, notificationId, clickIntentPrev, PendingIntent.FLAG_IMMUTABLE) expandedNotifView.setOnClickPendingIntent(R.id.imgPrev, contentIntentPrev) expandedNotifView.setViewVisibility(R.id.imgPrev, View.VISIBLE) collapsedView.setViewVisibility(R.id.imgPrev, View.VISIBLE) // 设置折叠视图内容 collapsedView.setTextViewText(R.id.notification_content_title3, heading) collapsedView.setTextViewText(R.id.notification_content_text3, content) collapsedView.setViewVisibility(R.id.downArrowImg, View.VISIBLE) } private fun buildNotification(): Notification { return NotificationCompat.Builder(this, getString(R.string.default_notification_channel_id)) .setSmallIcon(R.drawable.material_sd_notificationicon) .setColor(resources.getColor(R.color.notificationb_bg_color)) .setCustomContentView(collapsedView) .setCustomBigContentView(expandedNotifView) .setPriority(NotificationCompat.PRIORITY_MAX) .setOnlyAlertOnce(true) .setOngoing(true) .build() } override fun onDestroy() { super.onDestroy() handler.removeCallbacks(updateRunnable) } override fun onBind(intent: Intent?): IBinder? = null }
步骤2:启动前台服务
在原创建通知的位置,替换为启动前台服务并传递必要参数:
// 保留原有剩余时间计算逻辑 val date = format.parse(exptime) val elapsed = now.time - date.time val remainingTime = startTime - elapsed // 构造启动服务的Intent val serviceIntent = Intent(context, NotificationUpdateService::class.java).apply { putExtra("remaining_time", remainingTime) putExtra("heading", heading) putExtra("content", content) putExtra("img", img) putExtra("uuid", uuid) putExtra("link", link) putExtra("tId", tId) putExtra("fullImg", fullImg) putExtra("clickIntentNext", clickIntentNext) putExtra("clickIntentPrev", clickIntentPrev) } // 适配Android版本启动前台服务 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(serviceIntent) } else { context.startService(serviceIntent) }
步骤3:声明权限和服务
在AndroidManifest.xml中添加权限并注册服务:
<!-- 前台服务权限 --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- Android 12及以上需要通知权限 --> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- 注册服务 --> <service android:name=".NotificationUpdateService" android:exported="false" />
关键说明
- 放弃依赖RemoteView中Chronometer的自动计时,改为手动计算剩余时间并更新文本,避免系统进程无法维护计时的问题。
- 前台服务持续在后台运行,定时更新通知内容,确保应用被杀后控件仍能正常工作。
START_STICKY参数确保服务被系统杀死后会尝试重启,提升稳定性。
内容的提问来源于stack exchange,提问作者Discom 9
相关产品推荐
相关产品推荐

