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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 07:33:03