Foreground Service中Notification Canvas绘制更新停止问题排查
问题分析与解决方案
你的问题是前台服务的倒计时通知运行约两分钟后停止更新,但onTick的日志还在输出,说明倒计时逻辑正常,问题出在通知更新的环节。以下是代码中的关键问题和修复方案:
1. 重复创建未回收的Bitmap导致内存压力
每次onTick都会调用createBitmapFromText生成新的Bitmap,但旧的Bitmap没有被回收,随着时间推移内存占用持续升高,系统可能会限制通知的更新操作,甚至导致Bitmap创建失败。
修复方案:
- 在设置新Bitmap前,回收旧的Bitmap资源;
- 尽量复用Bitmap的尺寸(如果倒计时文本宽度变化不大),避免每次创建新的Bitmap对象。
2. 未重新构建Notification实例
你修改了contentView的内容,但一直复用最初创建的notification对象。系统会缓存Notification实例,直接调用notify可能不会触发UI更新,因为对象引用没有变化。
修复方案:
每次更新contentView后,需要重新构建Notification(或者使用NotificationCompat.Builder重新build),再调用notify。
3. 重复加载Typeface浪费资源
createBitmapFromText中每次都从assets加载Typeface,这是IO操作,重复执行会耗时且浪费资源,可能导致主线程阻塞。
修复方案:
提前缓存Typeface实例,避免重复加载。
修复后的代码示例
缓存Typeface并优化Bitmap创建
// 类级别缓存Typeface,避免重复加载 private val cachedTypeface by lazy { Typeface.createFromAsset( context.assets, "cairo_medium_500.ttf" ) } fun createBitmapFromText(context: Context, messageText: String, textSize: Float, textColor: Int): Bitmap { val paint = Paint().apply { this.textSize = textSize typeface = cachedTypeface color = textColor textAlign = Paint.Align.LEFT isAntiAlias = true // 提升文本显示清晰度 } val baseline = -paint.ascent() val width = (paint.measureText(messageText) + 0.5f).toInt() val height = (baseline + paint.descent() + 0.5f).toInt() val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) val canvas = Canvas(bitmap) canvas.drawText(messageText, 0f, baseline, paint) return bitmap }
修复通知更新逻辑
// 假设你持有全局的NotificationCompat.Builder实例 private lateinit var notificationBuilder: NotificationCompat.Builder private fun startLiveNextPrayerTimer(futureMillisTime: Long, context: Context) { countDownTimer = object : CountDownTimer(futureMillisTime, 1000) { override fun onTick(millisUntilFinished: Long) { val timeText = secondsToHHmmss(millisUntilFinished / 1000) Log.e("onTick", timeText) // 回收旧Bitmap释放内存 val oldBitmap = contentView.getImageViewBitmap(R.id.tvCounter) oldBitmap?.recycle() // 生成新Bitmap并更新RemoteViews val newBitmap = createBitmapFromText(context, timeText, 40.0f, Color.GRAY) contentView.setImageViewBitmap(R.id.tvCounter, newBitmap) // 重新构建Notification并通知系统更新 val updatedNotification = notificationBuilder.build() notificationManager.notify(1, updatedNotification) } override fun onFinish() { setPrayerNotificationCounter() } } countDownTimer.start() }
额外注意事项
- 确保
notificationBuilder是全局维护的实例,每次build()都会生成包含最新contentView的Notification对象; - 前台服务需设置合适的启动模式(如
START_STICKY),避免被系统低内存杀死; - 可以通过Android Studio的Profiler监控内存变化,验证Bitmap回收是否生效。
内容的提问来源于stack exchange,提问作者Yasmine Raafat
相关产品推荐
相关产品推荐

