Android8.0以上启动一个前台服务需要创建一个通知,通知又需要创建一个通道并获取Channel的Id,在用NotificationCompat创建通知时绑定它,这些都属于相对新的特性,旧的包比如v4、v7这些似乎并不生效。
Notification notification = new NotificationCompat //v7
.Builder(this,channelId) //v4包的构造器可以设置channelId
.setSmallIcon(R.drawable.icon)
.setContentTitle("SampleService")
.setContentText("Service is running...")
.setOngoing(true)
.setAutoCancel(false)
// .setChannel(notificationManager.getNotificationChannel(BuildConfig.APPLICATION_ID).getId())
.build();
用NotificationCompat创建通知如何设置ChannelId?总之就两种方法,一个是构造时塞入ChannelId,另一种是用setChannel方法。
但实际上在v4或v7包无论哪种方法的最后结果只有一个,那就是Channel为null没有设置进去。
为什么会出现这种问题?
V7的NotificationCompat是继承于V4包的,V7没有重写这个方法。
所以setChannel这个方法是V4包实现的,虽然v4包的setChannel方法很正常,存储到自身的mChannelId字段上,但是mChannelId这个属性是否被成功设置到Notification里了呢?
最终通知创建方法也是在v4包实现的,从代码可见通知是借助IMPL代理创建的。
IMPL是如何被创建的?
运行时NotificationCompat会判断系统的编译版本,比如API21对应安卓5,API24对应安卓7,以此类推,看碟下菜,生成对应版本的NotificationCompatApiXXImpl(XX对应系统api版本)。
但是最高支持的版本也就是26,假设手机的版本是26以上,就调用NotificationCompatApi26Impl的工厂方法创建通知,点进Builder方法看一下。
Api26Impl的工厂构造方法最终仍然会调用Notification自身的Builder类(mB)。
setChannel方法在AndroidStudio里是报红的,说明这个方法可能不存在,这也是造成mChannelId没有设置到通知的原因。
如果Builder无法正常设置ChannelId,可以写一个方法直接给Notification设置ChannelId。
public static Notification insertChannelRet(Notification notification,String channelId){
try {
Field field = Notification.class.getDeclaredField("mChannelId"); //找到mChannelId字段
field.setAccessible(true); //设置字段的可见度
field.set(notification,channelId); //通过反射将channelId添加到通知上
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
return notification;
}
在启动前台服务的地方调用 startForeground(1,insertChannelRet(notification,channelId))就可以了。