Hook实例 - 拦截应用通知

作者: Android架构 | 来源:发表于2019-06-20 10:17 被阅读16次

当应用内接入了众多的 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等架构技术资料】,希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

资料获取方式:加入Android架构交流QQ群聊:513088520 ,进群即领取资料!!!

点击链接加入群聊【Android移动架构总群】:加入群聊

资料大全

相关文章

网友评论

    本文标题:Hook实例 - 拦截应用通知

    本文链接:https://www.haomeiwen.com/subject/jfykqctx.html