当应用内接入了众多的 SDK,SDK 内部会使用系统服务 NotificationManager 发送通知,这就导致通知难以管理和控制。现在我们就用 Hook 技术拦截部分通知,限制应用内的通知发送操作。
发送通知使用的是 NotificationManager 的 notify 方法,我们跟随 API 进去看看。它会使用 INotificationManager 类型的对象,并调用其 enqueueNotificationWithTag 方法完成通知的发送。
public void notify(String tag, int id, Notification notification)
{
INotificationManager service = getService();
…… // 省略部分代码
try {
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
stripped, idOut, UserHandle.myUserId());
if (id != idOut[0]) {
Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
}
} catch (RemoteException e) {
}
}
private static INotificationManager sService;
/** @hide */
static public INotificationManager getService()
{
if (sService != null) {
return sService;
}
IBinder b = ServiceManager.getService("notification");
sService = INotificationManager.Stub.asInterface(b);
return sService;
}
INotificationManager 是跨进程通信的 Binder 类,sService 是 NMS(NotificationManagerService) 在客户端的代理,发送通知要委托给 sService,由它传递给 NMS,具体的原理在这里不再细究,感兴趣的可以了解系统服务和应用的通信过程。
我们发现 sService 是个静态成员变量,而且只会初始化一次。只要把 sService 替换成自定义的不就行了么,确实如此。下面用到大量的 Java 反射和动态代理,特别要注意代码的书写。
private void hookNotificationManager(Context context) {
try {
NotificationManager notificationManager = (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
// 得到系统的 sService
Method getService = NotificationManager.class.getDeclaredMethod("getService");
getService.setAccessible(true);
final Object sService = getService.invoke(notificationManager);
Class iNotiMngClz = Class.forName("android.app.INotificationManager");
// 动态代理 INotificationManager
Object proxyNotiMng = Proxy.newProxyInstance(getClass().getClassLoader(),
new Class[]{iNotiMngClz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.debug("invoke(). method:{}", method);
if (args != null && args.length > 0) {
for (Object arg : args) {
log.debug("type:{}, arg:{}", arg != null ? arg.getClass() : null, arg);
}
}
// 操作交由 sService 处理,不拦截通知
// return method.invoke(sService, args);
// 拦截通知,什么也不做
return null;
// 或者是根据通知的 Tag 和 ID 进行筛选
}
});
// 替换 sService
Field sServiceField = NotificationManager.class.getDeclaredField("sService");
sServiceField.setAccessible(true);
sServiceField.set(notificationManager, proxyNotiMng);
} catch (Exception e) {
log.warn("Hook NotificationManager failed!", e);
}
}
Hook 的时机还是尽量要早,我们在 attachBaseContext 里面操作。
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
hookNotificationManager(newBase);
}
这样我们就完成了对通知的拦截,可见 Hook 技术真的是非常强大,好多插件化的原理都是建立在 Hook 之上的。
自己是从事了七年开发的Android工程师,不少人私下问我,2019年Android进阶该怎么学,方法有没有?
没错,年初我花了一个多月的时间整理出来的学习资料,希望能帮助那些想进阶提升Android开发,却又不知道怎么进阶学习的朋友。【包括高级UI、性能优化、架构师课程、NDK、Kotlin、混合式开发(ReactNative+Weex)、Flutter等架构技术资料】,希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。
网友评论