Android状态栏通知中动态显示数字的更优实现方案咨询
Android状态栏通知中动态显示数字的更优实现方案咨询
你当前通过生成Bitmap再转成通知小图标的方案确实能实现需求,但每秒重复执行创建Bitmap、Canvas、绘制文本的流程,会带来不必要的性能开销——尤其是在长时间运行的计时器场景下,可能会增加CPU和内存的占用。下面给你几个更高效的替代方案:
方案1:使用系统自带的数字通知图标(推荐,单数字场景)
Android原生系统提供了一套内置的数字小图标,从android.R.drawable.ic_notification_0到android.R.drawable.ic_notification_9(不同厂商可能有细微差异,但原生系统完全支持)。如果你的计时器只需要显示0-9的单个数字,直接用这些内置图标是最省性能的选择,完全不需要手动绘制Bitmap。
代码示例
private fun updateTimerNotification(seconds: Int) { // 根据需求提取要显示的数字,这里取秒数的个位数 val displayDigit = seconds % 10 // 匹配系统内置数字图标 val iconResId = when(displayDigit) { 0 -> android.R.drawable.ic_notification_0 1 -> android.R.drawable.ic_notification_1 2 -> android.R.drawable.ic_notification_2 3 -> android.R.drawable.ic_notification_3 4 -> android.R.drawable.ic_notification_4 5 -> android.R.drawable.ic_notification_5 6 -> android.R.drawable.ic_notification_6 7 -> android.R.drawable.ic_notification_7 8 -> android.R.drawable.ic_notification_8 9 -> android.R.drawable.ic_notification_9 else -> android.R.drawable.ic_notification_0 // 兜底默认图标 } val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { Notification.Builder(this, CHANNEL_ID) } else { Notification.Builder(this) } builder.setSmallIcon(iconResId) .setContentTitle("计时器运行中") .setContentText("剩余时间: $seconds 秒") .setOngoing(true) // 设置为常驻通知,防止误取消 .setAutoCancel(false) val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.notify(NOTIFICATION_ID, builder.build()) }
方案2:使用RemoteViews自定义通知布局(支持多位数/复杂文本)
如果需要显示多位数(比如“45”“120”)或者自定义样式的文本,使用RemoteViews直接在通知布局中放置TextView是更高效的选择。这种方式由系统直接渲染文本,性能比手动绘制Bitmap好很多,还能灵活调整文本样式。
步骤1:创建自定义通知布局
在res/layout/notification_timer.xml中定义布局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="12dp" android:background="@android:color/transparent"> <TextView android:id="@+id/tv_timer_display" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20sp" android:textColor="@android:color/white" android:fontFamily="sans-serif-black" android:text="00"/> <TextView android:id="@+id/tv_timer_desc" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="14sp" android:textColor="@android:color/darker_gray" android:text="计时器运行中"/> </LinearLayout>
步骤2:代码中更新通知
private fun updateTimerNotificationWithRemoteViews(seconds: Int) { val remoteViews = RemoteViews(packageName, R.layout.notification_timer) // 更新文本内容 remoteViews.setTextViewText(R.id.tv_timer_display, seconds.toString()) remoteViews.setTextViewText(R.id.tv_timer_desc, "剩余时间: $seconds 秒") val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { Notification.Builder(this, CHANNEL_ID) } else { Notification.Builder(this) } // 注意:系统要求必须设置小图标,即使使用自定义布局 builder.setSmallIcon(android.R.drawable.ic_dialog_time) .setCustomContentView(remoteViews) .setOngoing(true) .setAutoCancel(false) val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.notify(NOTIFICATION_ID, builder.build()) }
方案3:优化原有Bitmap方案(迫不得已时使用)
如果因为特殊限制必须保留Bitmap方案,你可以通过以下优化减少性能开销:
- 缓存已绘制的Bitmap:用
HashMap<String, Bitmap>缓存生成过的文本对应的Bitmap,避免重复绘制相同文本 - 复用Paint对象:将Paint作为类成员变量初始化,不要每次创建新对象
- 固定Bitmap尺寸:使用合适的固定尺寸,避免不必要的绘制计算
优化后的代码示例
// 类成员变量:缓存和复用对象 private val textBitmapCache = HashMap<String, Bitmap>() private val textPaint: Paint by lazy { Paint(Paint.ANTI_ALIAS_FLAG).apply { color = Color.WHITE typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD) textAlign = Paint.Align.CENTER } } private fun createBitmapFromStringCached(text: String): Bitmap { // 先查询缓存,存在则直接返回 textBitmapCache[text]?.let { return it } val density = resources.displayMetrics.density val sizeDp = 24f val sizePx = (sizeDp * density).toInt().coerceAtLeast(24) val bitmap = Bitmap.createBitmap(sizePx, sizePx, Bitmap.Config.ARGB_8888) val canvas = Canvas(bitmap) canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR) // 复用全局Paint,仅更新文本尺寸 textPaint.textSize = sizePx * 0.6f val bounds = Rect() textPaint.getTextBounds(text, 0, text.length, bounds) val x = sizePx / 2f val y = sizePx / 2f - bounds.exactCenterY() canvas.drawText(text, x, y, textPaint) // 存入缓存 textBitmapCache[text] = bitmap return bitmap }
总结
- 单数字场景优先用方案1:零绘制开销,系统原生风格,兼容性最好
- 多位数/自定义文本场景用方案2:性能高效,样式灵活
- 只有在特殊限制下才考虑优化后的方案3
内容来源于stack exchange
相关产品推荐
相关产品推荐

