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
相关产品推荐
相关产品推荐

