Hook实例3--拦截应用通知

当应用内接入了众多的 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 之上的。

你可能感兴趣的:(Hook实例3--拦截应用通知)