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; } }
关键注意事项
- 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" />
- 角标兼容:不同Launcher对标的读取逻辑有差异,除了自定义布局中的计数文本,也可以尝试用
NotificationCompat.Builder.setNumber()来设置计数,提升兼容性 - 调试代码清理:原代码中的
waitForDebugger()是调试用的,正式发布时必须移除,避免影响应用性能 - 前台服务限制:Oreo中前台服务必须在启动后5秒内调用
startForeground(),否则会抛出ANR异常,代码中已经在onCreate方法中处理了这一点
内容的提问来源于stack exchange,提问作者Raj Kumar Singh
相关产品推荐
相关产品推荐

