SimpleTouch:一個庫完全搞懂事件分發流程

該庫已經開源到github,地址github.com/AlexMahao/S…java

目標

一個用於監聽android事件分發流程的庫,兩行代碼便可在運行時期監聽事件的分發流程。android

在編寫一些複雜的佈局時,經常因爲事件分發究竟是哪一個view處理產生困擾,作法一般須要通過如下步驟:git

  • 自定義一個View,重寫disaptchTouchEvent等方法。
  • 添加log日誌。
  • 而後替換佈局文件。
  • 編譯,經過控制檯查看事件分發流程。
  • 繼續自定義View .... 若是沒有發現問題,無線循環...
  • 問題解決,刪除以前定義的View,還原佈局文件。

對於如上的流程,須要屢次的修改代碼,編譯等,並且還有還原錯誤的風險。github

那麼有沒有一種方式,可以在儘量的少編寫代碼而實現上述流程,減小對於事件分發打印的困擾呢。json

簡介

SimpleTouch爲了解決如上問題而誕生,該庫能夠在運行時期打印完整的事件分發流程。bash

  • 監聽ViewdispatchTouchEventonTouchEventonInterceptTouchEvent
  • 運行時期動態打印事件分發流程。
  • 每一次完整的事件分發記錄以json的形式寫入文件。
  • 去重功能,對相同的move事件會自動過濾。
  • 提供no-op版本,使用時可區分debugrelease
  • 提供不一樣模式顯示

對於一次完整的手指點擊,控制檯打印日誌以下:app

同時提供以 json的格式寫入到磁盤,便於細緻分析。 (因爲暫時沒找到合適的流程圖軟件,暫時以json代替)

該展現效果來源於bejson的視圖展現功能。ide

使用

添加依賴佈局

在項目的app下的build.gradle中添加依賴gradle

debugApi 'com.spearbothy:simple-touch:1.0.5'
releaseApi 'com.spearbothy:simple-touch-no-op:1.0.5'
複製代碼

初始化

在項目的ApplicationonCreate()中調用初始化方法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;
}

複製代碼

注入代理類(用於監聽事件分發)

ActivityonCreate()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版本,該版本中包含有初始化和注入方法的空實現,以達到debugrelease使用不一樣的版本,使release不包含任何注入和初始化邏輯。
  • 在注入的時候有點耗時,若是頁面過於複雜,會有種頁面卡頓的感受.

思考

對於該庫,其實核心就是怎麼可以監聽onTouchEvent()等事件分發方法。

從實現的角度,核心問題在於兩個:

  • 如何生成代理類,該代理類中包含對view中事件的hook
  • 如何將View替換爲生成的代理類對象。

生成代理類

生成代理類有如下幾種方式:

  • 靜態方式:預先編寫一些基本view的代理類,而對於自定義view,能夠在編譯期經過Processor生成。
  • 動態方式:在apk運行時期,動態的生成代理類,該方式參考java的動態代理機制。

替換代理類對象

  • 靜態方式:在程序編譯時期,監聽xml的打包流程,動態的修改佈局文件替換爲代理類對象。相似於代碼注入。
  • 動態方式:在運行時期,構造view對象的時候,替換爲構造代理類。

根據以上的兩種方式,最終所有選擇動態的方式,及運行時期動態的生成代理類以及動態的替換view對象。

實現

生成代理類

java自己提供了動態代理的機制,可是因爲動態代理的對象必須是接口的方法,而view的事件分發方法都不是某一個接口的方法,那麼java自己的動態代理機制是不行的。

cglibjava的一個動態代理庫,能夠代理類方法。可是由於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);
    }

複製代碼

引用或借鑑的三方庫

  • com.android.support:appcompat-v7
  • com.google.dexmaker:dexmaker
  • com.alibaba:fastjson
  • com.noober.background:core

關於

有任何疑問能夠經過issue或者以郵件的形式發送到zziamahao@163.com

相關文章
相關標籤/搜索