You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity中能否全局Hook所有按钮点击事件?

全局Hook UI点击事件实现操作轨迹记录

针对你想避免逐个按钮添加日志、通过全局方式捕获UI点击事件的需求,以下是不同平台下的具体实现方案,类似C++重载new的全局拦截思路:

1. Unity 平台方案

利用Unity的EventSystem全局事件机制,替换默认的输入模块来捕获所有点击操作:

using UnityEngine;
using UnityEngine.EventSystems;

public class GlobalClickTracker : StandaloneInputModule
{
    private static GlobalClickTracker _instance;

    void Awake()
    {
        if (_instance != null && _instance != this)
        {
            Destroy(gameObject);
            return;
        }
        _instance = this;
        DontDestroyOnLoad(gameObject);
    }

    public override void Process()
    {
        base.Process();
        
        // 捕获左键点击事件
        if (Input.GetMouseButtonDown(0))
        {
            PointerEventData eventData = GetPointerEventData(Input.mousePosition);
            if (eventData.pointerEnter != null)
            {
                string uiTrace = $"点击控件: {eventData.pointerEnter.name} | 层级路径: {GetFullTransformPath(eventData.pointerEnter.transform)}";
                MyDebug.LogAction(uiTrace);
            }
        }
    }

    private string GetFullTransformPath(Transform transform)
    {
        string path = transform.name;
        while (transform.parent != null)
        {
            transform = transform.parent;
            path = $"{transform.name}/{path}";
        }
        return path;
    }
}

使用方式:删除场景中原有的StandaloneInputModule,将此脚本挂载到EventSystem对象上即可全局捕获所有UI点击。

2. Android 平台方案

通过反射Hook View的点击监听器,或利用LayoutInflater拦截View创建流程,统一添加日志逻辑:

public class GlobalClickHook {
    public static void init(Context appContext) {
        try {
            // 获取View的点击监听器字段
            Class<?> viewClass = Class.forName("android.view.View");
            Field listenerField = viewClass.getDeclaredField("mOnClickListener");
            listenerField.setAccessible(true);

            // 创建代理监听器
            View.OnClickListener proxyListener = v -> {
                // 记录点击信息
                String uiTrace = String.format("点击控件ID: %d | 类型: %s", v.getId(), v.getClass().getSimpleName());
                if (v instanceof TextView) {
                    uiTrace += " | 文本: " + ((TextView) v).getText();
                }
                MyDebug.LogAction(uiTrace);

                // 执行原点击逻辑
                try {
                    View.OnClickListener original = (View.OnClickListener) listenerField.get(v);
                    if (original != null) original.onClick(v);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            };

            // 拦截LayoutInflater的View创建流程,为所有View设置代理监听器
            LayoutInflater inflater = LayoutInflater.from(appContext);
            inflater.setFactory2((parent, name, context, attrs) -> {
                View view = inflater.createView(parent, name, context, attrs);
                if (view != null) {
                    try {
                        listenerField.set(view, proxyListener);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                return view;
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

使用方式:在Application的onCreate方法中调用GlobalClickHook.init(this)即可全局生效。

3. iOS 平台方案

利用OC的Method Swizzling特性,交换UIButton的点击方法实现,添加日志逻辑:

#import <objc/runtime.h>

@implementation UIButton (ClickTracker)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL originalSel = @selector(sendAction:to:forEvent:);
        SEL swizzledSel = @selector(swizzled_sendAction:to:forEvent:);

        Method originalMethod = class_getInstanceMethod(self, originalSel);
        Method swizzledMethod = class_getInstanceMethod(self, swizzledSel);

        // 交换方法实现
        if (class_addMethod(self, originalSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
            class_replaceMethod(self, swizzledSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)swizzled_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    // 记录点击信息
    NSString *uiTrace = [NSString stringWithFormat:@"点击按钮: %@ | 标题: %@", self.accessibilityIdentifier ?: self.class.superclass, self.currentTitle];
    [MyDebug logAction:uiTrace];

    // 执行原点击逻辑
    [self swizzled_sendAction:action to:target forEvent:event];
}

@end

使用方式:无需额外调用,只要引入该分类文件,App启动后所有UIButton的点击都会被自动捕获。

关键注意事项

  • 控件标识:需记录足够区分的信息(如层级路径、控件ID、文本),避免日志模糊。
  • 性能优化:日志逻辑要轻量化,避免阻塞主线程;可对高频点击做防抖处理。
  • 覆盖范围:若有自定义可交互控件(如Toggle、Slider),需扩展Hook逻辑覆盖对应控件的事件方法。
  • Crash上报:将操作轨迹存储在内存或本地临时文件,崩溃时通过Crashlytics的自定义日志接口上传,注意不要包含敏感信息。

内容的提问来源于stack exchange,提问作者Amit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 06:08:29