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

Android Oreo系统下应用完全关闭时如何显示应用角标?

在Android Oreo(API 26及以上)版本里,当应用完全关闭(没有任何运行状态)时,要显示应用角标确实有点棘手——因为Oreo对后台应用的限制非常严格,普通的广播触发通知很可能失效。不过我们可以通过前台服务+通知渠道配置的组合方案来解决这个问题,下面是完整的实现细节和代码示例:

核心实现思路

  • 用BroadcastReceiver接收推送事件(这里以GCM推送为例)
  • 创建开启了角标权限的通知渠道,这是Oreo及以上版本显示角标的前提
  • 通过前台服务IntentService触发通知:Oreo中应用完全关闭后,只有前台服务能稳定运行并触发通知显示
  • 自定义通知布局,同时设置未读计数作为角标显示的数字

1. 推送广播接收器(处理通知构建与触发)

这个类负责接收推送事件,构建带角标计数的自定义通知,然后调用前台服务来触发通知显示:

public class GcmBroadcastReceiver extends BroadcastReceiver { 
    @Override 
    public void onReceive(Context context, Intent intent) {
        // 收到新推送时进入该方法
        final Notification notification = getNotification(threadId, jobTitle, body, subTitle, title, imageURL); 
        EventLoggerService.showAppBadge(context, notification, new EventLoggerService.EventLoggerServiceStartedListner(){ 
            @Override 
            public void eventLoggerServiceStarted() { 
                NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
                notificationManager.notify(101, notification); 
            } 
        }); 
    } 

    public Notification getNotification(final String threadId, String jobTitle, final String body ,String subTitle, String title, final String imageURL) { 
        final Context context = CareSDKApplication.singleton().getApplicationContext(); 
        mThreadId = threadId; 
        mImageURL = imageURL; 
        mBody = body; 
        mTitle = title; 
        mSubTitle = subTitle; 

        // 初始化通知构建器,绑定指定的通知渠道
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NotificationsUtils.NOTIFICATION_CHANNEL_ID) 
                .setSmallIcon(R.drawable.message_notification) 
                .setAutoCancel(true) 
                .setContentIntent(getContentIntent(threadId)) 
                .setTicker(getTicker(threadId)); 

        // 设置自定义通知布局
        RemoteViews customNotification = new RemoteViews(context.getPackageName(), R.layout.image_notification_custom_view); 
        builder.setCustomContentView(customNotification); 

        // 填充通知文本内容
        if (!TextUtils.isEmpty(title)) { 
            customNotification.setTextViewText(R.id.title, StringEscapeUtils.unescapeJava(title)); 
            customNotification.setTextViewText(R.id.sub_title, StringEscapeUtils.unescapeJava(subTitle)); 
            customNotification.setTextViewText(R.id.content, StringEscapeUtils.unescapeJava(body)); 
        } else { 
            customNotification.setTextViewText(R.id.title, ""); 
            customNotification.setTextViewText(R.id.sub_title, StringEscapeUtils.unescapeJava(body)); 
            customNotification.setTextViewText(R.id.content, ""); 
        } 

        // 设置通知时间和未读计数(对应角标显示数字)
        customNotification.setTextViewText(R.id.time, new SimpleDateFormat("HH:mm").format(new Date())); 
        customNotification.setTextViewText(R.id.number_of_notifications, getUnreadCount(threadId) + ""); 

        // 适配高版本系统的通知样式
        if (Build.VERSION.SDK_INT >= 16) { 
            builder.setStyle(getStyle(threadId)); 
        } 

        // 处理通知大图(可选)
        if (!TextUtils.isEmpty(imageURL)) { 
            mBigPicture = Utils.getImage(context, imageURL, this); 
            if (mBigPicture != null) { 
                setImageNotification(builder, context, threadId, body, title, subTitle); 
            } 
        } 

        // 设置通知栏颜色(Lollipop及以上)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 
            builder.setColor(0x00bce4); 
        } 

        return builder.build(); 
    } 

    // 请自行实现getContentIntent、getTicker、getStyle、setImageNotification等辅助方法
}

2. 前台服务(保证应用关闭后仍能触发通知)

Oreo中应用完全关闭后,普通服务无法启动,必须使用前台服务才能稳定运行并显示通知:

public class EventLoggerService extends IntentService { 
    private static EventLoggerServiceStartedListner mEventLoggerServiceStartedListner; 
    private static Notification mNotification; 

    // 必须提供构造方法
    public EventLoggerService() {
        super("EventLoggerService");
    }

    @Override 
    public void onCreate() { 
        super.onCreate(); 
        // Android O及以上必须启动前台服务
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 
            NotificationsUtils notificationsUtils = new NotificationsUtils(this); 
            startForeground(1, mNotification); 
            Log.d("EventLoggerService", "EventLoggerService Created"); 
        } 
    } 

    // 回调接口,用于通知服务启动完成后触发通知显示
    public interface EventLoggerServiceStartedListner{ 
        void eventLoggerServiceStarted(); 
    } 

    @Override 
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) { 
        super.onStartCommand(intent, flags, startId); 
        Log.d("EventLoggerService", "EventLoggerService onStartCommand"); 
        if (mEventLoggerServiceStartedListner != null) {
            mEventLoggerServiceStartedListner.eventLoggerServiceStarted(); 
        }
        return START_STICKY; 
    } 

    // 对外暴露的启动方法,用于触发前台服务并显示通知
    public static void showAppBadge(Context context, Notification notification, EventLoggerServiceStartedListner eventLoggerServiceStartedListner) { 
        mNotification = notification; 
        mEventLoggerServiceStartedListner = eventLoggerServiceStartedListner; 
        Intent service = new Intent(context, EventLoggerService.class); 
        // 根据系统版本选择启动方式
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.startForegroundService(service);
        } else {
            context.startService(service);
        }
    } 

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        // 这里可以处理后台任务,比如日志上报等(可选)
    }
}

3. 通知渠道工具类(配置角标权限)

Oreo及以上版本必须创建通知渠道,且必须开启setShowBadge(true)才能显示角标:

public class NotificationsUtils extends ContextWrapper{ 
    public static final String NOTIFICATION_CHANNEL_ID = "Care_push_note"; 
    public static final String NOTIFICATION_CHANNEL_NAME = "Care.com"; 
    private NotificationManager mNotificationManager; 

    public NotificationsUtils(Context context) { 
        super(context); 
        createChannels(); 
    } 

    private void createChannels() { 
        // 仅在Oreo及以上版本创建通知渠道
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH); 
            channel.setShowBadge(true); // 关键:开启角标显示权限
            channel.enableLights(true); 
            channel.enableVibration(true); 
            channel.setLightColor(Color.GREEN); 
            channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); 
            getManager().createNotificationChannel(channel); 
        }
    } 

    public NotificationManager getManager() { 
        if (mNotificationManager == null) { 
            mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 
        } 
        return mNotificationManager; 
    } 
}

关键注意事项

  1. Manifest配置:需要注册组件并声明权限:
<!-- 前台服务权限(API 29及以上需要) -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

<!-- 注册推送广播接收器 -->
<receiver android:name=".GcmBroadcastReceiver">
    <intent-filter>
        <action android:name="com.google.android.c2dm.intent.RECEIVE" />
        <!-- 添加其他推送相关的action -->
    </intent-filter>
</receiver>

<!-- 注册前台服务 -->
<service android:name=".EventLoggerService" />
  1. 角标兼容:不同Launcher对标的读取逻辑有差异,除了自定义布局中的计数文本,也可以尝试用NotificationCompat.Builder.setNumber()来设置计数,提升兼容性
  2. 调试代码清理:原代码中的waitForDebugger()是调试用的,正式发布时必须移除,避免影响应用性能
  3. 前台服务限制:Oreo中前台服务必须在启动后5秒内调用startForeground(),否则会抛出ANR异常,代码中已经在onCreate方法中处理了这一点

内容的提问来源于stack exchange,提问作者Raj Kumar Singh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:19:50