美文网首页
TouchLog:解决事件分发机制

TouchLog:解决事件分发机制

作者: 潇洒人生_cca2 | 来源:发表于2020-04-06 09:16 被阅读0次

    目标

    一个用于监听android事件分发流程的库,两行代码即可在运行时期监听事件的分发流程

    在编写一些复杂的布局时,常常由于事件分发到底是哪个view处理产生困扰,做法通常需要经过以下步骤:

    • 自定义一个View,重写disaptchTouchEvent等方法。
    • 添加log日志。
    • 然后替换布局文件。
    • 编译,通过控制台查看事件分发流程。
    • 继续自定义View … 如果没有发现问题,无线循环…
    • 问题解决,删除之前定义的View,还原布局文件。

    对于如上的流程,需要多次的修改代码,编译等,而且还有还原错误的风险。
    那么有没有一种方式,能够在尽可能的少编写代码而实现上述流程,减少对于事件分发打印的困扰呢

    简介

    TouchLog为了解决如上问题而诞生,该库可以在运行时期打印完整的事件分发流程。

    • 监听View的dispatchTouchEvent,onTouchEvent,onInterceptTouchEvent。
    • 运行时期动态打印事件分发流程。
    • 每一次完整的事件分发记录以json的形式写入文件。
    • 去重功能,对相同的move事件会自动过滤。
    • 提供no-op版本,使用时可区分debug和release。
    • 提供不同模式显示

    使用

    添加依赖

    在项目的app下的build.gradle中添加依赖

    debugApi 'com.spearbothy:simple-touch:1.0.5'
    releaseApi 'com.spearbothy:simple-touch-no-op:1.0.5'

    初始化

    在项目的Application的onCreate()中调用初始化方法Touch.inject(this);

    Touch.init(this, new Config().setSimple(false));
    

    Config对象提供一些配置选项

    public class Config {
    
        // 输出的日志以极简模式输出
        private boolean isSimple = true;
        // 是否延迟打印日志,延迟打印日志会在触摸事件结束之后打印,并且具有去重功能
        private boolean isDelay = true;
        // 是否保留重复的,默认不保留
        private boolean isRepeat = false;
        // 是否写入到文件
        private boolean isPrint2File = true;
        // 是否处理,不处理则不会监听任何方法,任何功能都无法生效
        private boolean isProcess = true;
    }
    

    注入代理类(用于监听事件分发)

    在Activity的onCreate()的super.onCreate(savedInstanceState);之前调用

     @Override
        protected void onCreate(Bundle savedInstanceState) {
            Touch.inject(this);
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mRootView = (LinearLayout) findViewById(R.id.root);
        }
    
    使用

    编译完成之后,打开app,开始触摸吧!!! 每一次手指离开到触摸请间隔大于1s,目的是对于每次触摸加以区分,暂时没想到合适的判断条件。

    备注
    • 提供了no-op版本,该版本中包含有初始化和注入方法的空实现,以达到debug和release使用不同的版本,使release不包含任何注入和初始化逻辑。
    • 在注入的时候有点耗时,如果页面过于复杂,会有种页面卡顿的感觉.

    思考

    对于该库,其实核心就是怎么能够监听onTouchEvent()等事件分发方法。
    从实现的角度,核心问题在于两个:

    • 如何生成代理类,该代理类中包含对view中事件的hook。
    • 如何将View替换为生成的代理类对象。

    生成代理类

    生成代理类有以下几种方式:

    • 静态方式:预先编写一些基本view的代理类,而对于自定义view,可以在编译期通过Processor生成。
    • 动态方式:在apk运行时期,动态的生成代理类,该方式参考java的动态代理机制。

    替换代理类对象

    • 静态方式:在程序编译时期,监听xml的打包流程,动态的修改布局文件替换为代理类对象。类似于代码注入。
    • 动态方式:在运行时期,构造view对象的时候,替换为构造代理类。
      根据以上的两种方式,最终全部选择动态的方式,及运行时期动态的生成代理类以及动态的替换view对象。

    实现

    生成代理类

    java本身提供了动态代理的机制,但是由于动态代理的对象必须是接口的方法,而view的事件分发方法都不是某一个接口的方法,那么java本身的动态代理机制是不行的。
    cglib是java的一个动态代理库,可以代理类方法。但是因为android中是以dex方式存储代码,所以无法应用于android。
    dexmaker是应用于android的动态生成代码的库。可以用该库实现动态生成代理类。
    动态生成代理类的关键点在于ViewProxyBuilder类,通过该类可以生成代理类对象。
    生成的方式如下:

    private static View proxy(final View view, AttributeSet attrs) {
            try {
                return ViewProxyBuilder.forClass(view.getClass())
                        .handler(new TouchHandler())
                        .dexCache(view.getContext().getDir(Constants.DEX_CACHE_DIR, Context.MODE_PRIVATE))
                        .constructorArgTypes(Context.class, AttributeSet.class)
                        .constructorArgValues(view.getContext(), attrs)
                        .addProxyMethod(Arrays.asList(Constants.PROXY_METHODS))
                        .build();
            } catch (IOException e) {
                return null;
            }
        }
    

    其中handler为代理方法处理类。

    public class TouchHandler implements InvocationHandler {
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            TouchMessageManager.getInstance().printBefore(proxy, method, args);
            Object result = ViewProxyBuilder.callSuper(proxy, method, args);
            TouchMessageManager.getInstance().printAfter(proxy, method, args ,result);
            return result;
        }
    }
    

    该类实际上只是在方法前和方法后都打印日志

    动态替换对象

    集成生成了代理对象,还有一个问题就是如何将生成的代理对象和原view`对象替换。

    该思路来源于support-v7,对于继承AppCompatActivity的页面,其中的TextView等运行时期都会被替换为AppCompatTextView。核心便是LayoutInfalter类,该类用于生成所有的布局对象,同时该类提供生成布局对象的hook方法,可以添加一下自定义操作。

    核心就是调用LayoutInflater.setFactory(),关键代码如下:

    public static void inject(Context context) {
            if (sConfig == null || !sConfig.isProcess()) {
                return;
            }
            LayoutInflater inflater;
            if (context instanceof Activity) {
                inflater = ((Activity) context).getLayoutInflater();
            } else {
                inflater = LayoutInflater.from(context);
            }
            ViewFactory factory = new ViewFactory();
            if (context instanceof AppCompatActivity) {
                final AppCompatDelegate delegate = ((AppCompatActivity) context).getDelegate();
                factory.setInterceptFactory(new LayoutInflater.Factory2() {
                    @Override
                    public View onCreateView(String name, Context context, AttributeSet attrs) {
                        return delegate.createView(null, name, context, attrs);
                    }
    
                    @Override
                    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
                        return delegate.createView(parent, name, context, attrs);
                    }
                });
            }
            // 设置hook
            inflater.setFactory2(factory);
        }
    

    相关文章

      网友评论

          本文标题:TouchLog:解决事件分发机制

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