这段时间做的项目需要service保活,也就是实时监听设备的通知栏消息,并可以捕获到通知的内容,然后进行对应的操作。之前尝试过很多方式,最后感觉前台service对于服务保活相对好使(据说这个微信也用过的方案),知情者可能要问了:前台service不是有个通知栏一直显示么?这样对用户来说不是很好。我们这里可以使用两个service互调来实现不显示通知栏,原理如下:
对于 API level < 18 :调用startForeground(ID, new Notification()),发送空的Notification ,图标则不会显示。
对于 API level >= 18:在需要提优先级的service A启动一个InnerService,两个服务同时startForeground,且绑定同样的 ID。Stop 掉InnerService ,这样通知栏图标即被移除。
前台服务类:
public class ForeService extends Service {
private final int FORESERVICE_PID = android.os.Process.myPid();
private AssistServiceConnection mConnection;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
/**
* 之前的额前台service(会显示通知栏)
*/
/* //定义一个notification
Notification.Builder builder1 = new Notification.Builder(this);
builder1.setSmallIcon(R.mipmap.ic_launcher); //设置图标
// builder1.setTicker("新消息");
builder1.setContentTitle("My title"); //设置标题
builder1.setContentText("My content"); //消息内容
// builder1.setContentInfo("");//补充内容
// builder1.setWhen(System.currentTimeMillis()); //发送时间
// builder1.setDefaults(Notification.DEFAULT_ALL); //设置默认的提示音,振动方式,灯光
// builder1.setAutoCancel(true);//打开程序后图标消失
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
builder1.setContentIntent(pendingIntent);
Notification notification1 = builder1.build();
//把该service创建为前台service
startForeground(1, notification1);*/
setForeground();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
private void setForeground() {
//如果sdk<18 , 直接调用startForeground即可,不会在通知栏创建通知
if (Build.VERSION.SDK_INT < 18) {
this.startForeground(FORESERVICE_PID, getNotification());
return;
}
if (null == mConnection) {
mConnection = new AssistServiceConnection();
}
this.bindService(new Intent(this, AssistService.class), mConnection,
Service.BIND_AUTO_CREATE);
}
public Notification getNotification() {
//定义一个notification
Notification.Builder builder1 = new Notification.Builder(this);
builder1.setSmallIcon(R.mipmap.ic_launcher); //设置图标
// builder1.setTicker("新消息");
builder1.setContentTitle("My title"); //设置标题
builder1.setContentText("My content"); //消息内容
// builder1.setContentInfo("");//补充内容
// builder1.setWhen(System.currentTimeMillis()); //发送时间
// builder1.setDefaults(Notification.DEFAULT_ALL); //设置默认的提示音,振动方式,灯光
// builder1.setAutoCancel(true);//打开程序后图标消失
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
builder1.setContentIntent(pendingIntent);
Notification notification1 = builder1.build();
return notification1;
}
private class AssistServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
// sdk >=18 的,会在通知栏显示service正在运行,这里不要让用户感知,所以这里的实现方式是利用2个同进程的service,利用相同的notificationID,
// 2个service分别startForeground,然后只在1个service里stopForeground,这样即可去掉通知栏的显示
Service assistService = ((AssistService.LocalBinder) binder)
.getService();
ForeService.this.startForeground(FORESERVICE_PID, getNotification());
assistService.startForeground(FORESERVICE_PID, getNotification());
assistService.stopForeground(true);
ForeService.this.unbindService(mConnection);
mConnection = null;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
辅佐服务类:
public class AssistService extends Service {
public class LocalBinder extends Binder {
public AssistService getService() {
return AssistService.this;
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new LocalBinder();
}
}
然后开启前台service即可。
下面给介绍一种新的保活方案:NotificationListenerService。NotificationListenerService就是一个监听通知的服务,只要手机收到了通知,NotificationListenerService都能监听到,即时用户把进程杀死,也能重启。
NotificationListenerService
的使用范围也挺广的,比如我们熟知的抢红包,智能手表同步通知,通知栏去广告工具等,都是利用它来完成的。所以,我也想赶时髦地好好利用这把“利器”。最后方案也就出来了:在 Android 4.3 以下(API < 18)使用 AccessibilityService
来读取新通知,在 Android 4.3 及以上(API >= 18)使用 NotificationListenerService
来满足需求。
NotificationListenerService
在这里,我们就做一个小需求:实时检测微信的新通知,如果该通知是微信红包的话,就进入微信聊天页面。
首先创建一个 WeChatNotificationListenerService
继承 NotificationListenerService
。然后在 AndroidManifest.xml
中进行声明相关权限和 <intent-filter>
:
1 | <android:name="com.yuqirong.listenwechatnotification.WeChatNotificationListenerService" |
然后一般会重写下面这三个方法:
onNotificationPosted(StatusBarNotification sbn)
:当有新通知到来时会回调;onNotificationRemoved(StatusBarNotification sbn)
:当有通知移除时会回调;onListenerConnected()
:当NotificationListenerService
是可用的并且和通知管理器连接成功时回调。
onNotificationPosted(StatusBarNotification sbn)
下面我们来看看 NotificationListenerService
中的重点: onNotificationPosted(StatusBarNotification sbn)
方法。
1 | @Override |
从上面的代码可知,对于分析 Notification
的内容分为了两种:
- 当 API > 18 时,利用
Notification.extras
来获取通知内容。extras
是在 API 19 时被加入的; - 当 API = 18 时,利用反射获取
Notification
中的内容。具体的代码在下方。
1 | public List<String> (Notification notification) { |
凭着 onNotificationPosted(StatusBarNotification sbn)
方法就已经可以完成监听微信通知并打开的动作了。下面我们来看一下其他关于 NotificationListenerService
的二三事。
取消通知
有了监听,NotificationListenerService
自然提供了可以取消通知的方法。取消通知的方法有:
cancelNotification(String key)
:是 API >= 21 才可以使用的。利用StatusBarNotification
的getKey()
方法来获取key
并取消通知。cancelNotification(String pkg, String tag, int id)
:在 API < 21 时可以使用,在 API >= 21 时使用此方法来取消通知将无效,被废弃。
最后,取消通知的方法:
1 | public void (StatusBarNotification sbn) { |
检测通知监听服务是否被授权
1 | public boolean (Context context) { |
打开通知监听设置页面
1 | public void () { |
被杀后再次启动时,监听不生效的问题
这个问题来源于知乎问题: NotificationListenerService不能监听到通知,研究了一天不知道是什么原因?
从问题的回答中可以了解到,是因为 NotificationListenerService
被杀后再次启动时,并没有去 bindService
,所以导致监听效果无效。
最后,在回答中还给出了解决方案:利用 NotificationListenerService
先 disable 再 enable ,重新触发系统的 rebind 操作。代码如下:
1 | private void () { |
该方法使用前提是 NotificationListenerService
已经被用户授予了权限,否则无效。另外,在自己的小米手机上实测,重新完成 rebind 操作需要等待 10 多秒(我的手机测试过大概在 13 秒左右)。幸运的是,官方也已经发现了这个问题,在 API 24 中提供了 requestRebind(ComponentName componentName)
方法来支持重新绑定。
AccessibilityService
讲完了 NotificationListenerService
之后,按照前面说的那样,在 API < 18 的时候使用 AccessibilityService
。
同样,创建一个 WeChatAccessibilityService
,并且在 AndroidManifest.xml
中进行声明:
1 | <service |
声明之后,还要对 WeChatAccessibilityService
进行配置。需要在 res 目录下新建一个 xml 文件夹,在里面新建一个 accessible_service_config.xml 文件:
1 | <?xml version="1.0" encoding="utf-8"?> |
最后就是代码了:
1 | public class extends { |
看了一圈 WeChatAccessibilityService
的代码,发现和 WeChatNotificationListenerService
在 API < 18 时处理的逻辑是一样的,getText(notification)
方法就是上面那个,在这里就不复制粘贴了,基本没什么好讲的了。
有了 WeChatAccessibilityService
之后,在 API < 18 的情况下也能监听通知啦。\(^ο^)/
我们终于实现了当初许下的那个需求了。 cry …
总结
除了监听通知之外,AccessibilityService
还可以进行模拟点击、检测界面变化等功能。具体的可以在 GitHub 上搜索抢红包有关的 Repo 进行深入学习。
而 NotificationListenerService
的监听通知功能更加强大,也更加专业。在一些设备上,如果 NotificationListenerService
被授予了权限,那么可以做到该监听进程不死的效果,也算是另类的进程保活。