无埋点操作,是通过gradle的Transform API在编译期扫描整个项目生成的class文件,再利用ASM API对class文件插入我们的埋点方法来实现的。

在各种事件方法里插桩埋点,基本上满足我们的大部分埋点需求,但是产品会有这样的需求,想看看用户在某个界面里哪些区域点击比较频繁,就需要知道用户点击的坐标。获取点击事件可以办到,还可以拿到点击事件里的View,但是无法获取点击的屏幕坐标。那点击屏幕坐标在哪里获取呢?在View的dispatchTouchEvent里,如果我们在此处插桩,可以拿到它的参数MotionEvent,通过它我们就可以拿到控件的点击坐标。
但是dispatchTouchEvent是系统类View的方法,项目编译的时候是没有android.jar供Transform扫描的。办法是可以通过其他方式实现,比如每打开一个界面都创建一个透明层在屏幕上,通过自定义透明层View来重写dispatchTouchEvent获取MotionEvent。或者所有的控件都自定义View的方式重写dispatchTouchEvent获取MotionEvent。这两种方式开销都比较大,难以维护,不可取。

这里介绍通过另一种方式来实现。大家都知道android系统出的support包和现在的androidx包都是为了向下兼容,使得老控件,如textview、edittext等等,可以像Appcompattextview、Appcompatedittext一样有更好的着色和主题样式。但是怎么实现呢?答案在Activity启动过程里。Activity的启动过程大家自行百度,这里先讲oncreate方法

在AppcompatActivity的onCreate方法里,有一个getDelegate()方法,返回AppCompatDelegate,
进去后,可以看到new了一个AppCompatDelegateImpl实现类,这个实现类又继承自AppCompatDelegate。

到这里还没看到关键信息,我们先看下xml布局文件是怎么映射成控件view的,
在AppCompatDelegateImpl找createView方法,在createView里有一个AppCompatViewInflater类,默认类名或设置为null就new一个AppCompatViewInflater,否则就反射获取AppCompatViewInflater对象。

到这里,AppCompatActivity的getDelegate()方法,只是new了一个AppCompatDelegateImpl类,并未看到实质的东西。在里面找一下可以看到createView这个方法,可以猜到肯定跟这个createView方法有关系。那它是什么时候被调用的呢,我们回到AppCompatActivity,getDelegate()方法的下一行delegate.installViewFactory();,进去后可以看到实例化了系统服务LAYOUT_INFLATER_SERVICE,并把它设置进setFactory2方法里,


可以看到factory赋值给mFactory2接口类

回到setContentView()方法,我们可以看到LayoutInflater.from(mContext).inflate(resId, contentParent);,步骤是在LayoutInflater里先用xmlpullparser解析xml,根据tag来生成对应的控件

最终还是通过一系列调用createViewFromTag->tryCreateView->mFactory2.onCreateView(接口回调到AppCompatDelegateImpl)->onCreateView->createView->mAppCompatViewInflater.createView,最后进入AppCompatViewInflater类里,可以看到

final View createView(View parent, final String name, @NonNull Context context,@NonNull AttributeSet attrs, boolean inheritContext,boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {final Context originalContext = context;// We can emulate Lollipop's android:theme attribute propagating down the view hierarchy// by using the parent's contextif (inheritContext && parent != null) {context = parent.getContext();}if (readAndroidTheme || readAppTheme) {// We then apply the theme on the context, if specifiedcontext = themifyContext(context, attrs, readAndroidTheme, readAppTheme);}if (wrapContext) {context = TintContextWrapper.wrap(context);}View view = null;// We need to 'inject' our tint aware Views in place of the standard framework versionsswitch (name) {case "TextView":view = createTextView(context, attrs);verifyNotNull(view, name);break;case "ImageView":view = createImageView(context, attrs);verifyNotNull(view, name);break;case "Button":view = createButton(context, attrs);verifyNotNull(view, name);break;case "EditText":view = createEditText(context, attrs);verifyNotNull(view, name);break;case "Spinner":view = createSpinner(context, attrs);verifyNotNull(view, name);break;case "ImageButton":view = createImageButton(context, attrs);verifyNotNull(view, name);break;case "CheckBox":view = createCheckBox(context, attrs);verifyNotNull(view, name);break;case "RadioButton":view = createRadioButton(context, attrs);verifyNotNull(view, name);break;case "CheckedTextView":view = createCheckedTextView(context, attrs);verifyNotNull(view, name);break;case "AutoCompleteTextView":view = createAutoCompleteTextView(context, attrs);verifyNotNull(view, name);break;case "MultiAutoCompleteTextView":view = createMultiAutoCompleteTextView(context, attrs);verifyNotNull(view, name);break;case "RatingBar":view = createRatingBar(context, attrs);verifyNotNull(view, name);break;case "SeekBar":view = createSeekBar(context, attrs);verifyNotNull(view, name);break;case "ToggleButton":view = createToggleButton(context, attrs);verifyNotNull(view, name);break;default:// The fallback that allows extending class to take over view inflation// for other tags. Note that we don't check that the result is not-null.// That allows the custom inflater path to fall back on the default one// later in this method.view = createView(context, name, attrs);}if (view == null && originalContext != context) {// If the original context does not equal our themed context, then we need to manually// inflate it using the name so that android:theme takes effect.view = createViewFromTag(context, name, attrs);}if (view != null) {// If we have created a view, check its android:onClickcheckOnClickListener(view, attrs);}return view;}

google在这里做了个偷梁换柱的switch,把老控件替换成新控件,如果有点击事件,也再重新给绑定上。这里简直就是个奇幻喵喵屋,大家可以进去尽情发挥自己的潜能。

我们要做的是hook dispatchTouchEvent方法,到这只说了google是怎么兼容老控件的,google把老控件用switch重新new一个新控件,如果我们可以让它去new一个我们自定义的view,一切的view都由我们创建,那岂不快哉。那样我们就可以在自己的自定义view里去重写dispatchTouchEvent方法,这样就可以往重写的方法里插桩拿MotionEvent。但实现呢,简单。

重写一个CustomAppCompatViewInflater类,在里面加上新控件

switch (name) {case "androidx.appcompat.widget.AppCompatTextView":case "TextView":view = createTextView(context, attrs);verifyNotNull(view, name);break;case "androidx.appcompat.widget.AppCompatImageView":case "ImageView":view = createImageView(context, attrs);verifyNotNull(view, name);break;case "androidx.appcompat.widget.AppCompatButton":case "Button":view = createButton(context, attrs);verifyNotNull(view, name);break;case "androidx.appcompat.widget.AppCompatEditText":case "EditText":view = createEditText(context, attrs);verifyNotNull(view, name);break;case "androidx.appcompat.widget.AppCompatSpinner":case "Spinner":view = createSpinner(context, attrs);verifyNotNull(view, name);break;case "androidx.appcompat.widget.AppCompatImageButton":case "ImageButton":view = createImageButton(context, attrs);verifyNotNull(view, name);break;case "androidx.appcompat.widget.AppCompatCheckBox":case "CheckBox":view = createCheckBox(context, attrs);verifyNotNull(view, name);break;case "androidx.appcompat.widget.AppCompatRadioButton":case "RadioButton":view = createRadioButton(context, attrs);verifyNotNull(view, name);break;case "androidx.appcompat.widget.AppCompatCheckedTextView":case "CheckedTextView":view = createCheckedTextView(context, attrs);verifyNotNull(view, name);break;case "AppCompatAutoCompleteTextView":case "AutoCompleteTextView":view = createAutoCompleteTextView(context, attrs);verifyNotNull(view, name);break;case "androidx.appcompat.widget.AppCompatMultiAutoCompleteTextView":case "MultiAutoCompleteTextView":view = createMultiAutoCompleteTextView(context, attrs);verifyNotNull(view, name);break;case "androidx.appcompat.widget.AppCompatRatingBar":case "RatingBar":view = createRatingBar(context, attrs);verifyNotNull(view, name);break;case "androidx.appcompat.widget.AppCompatSeekBar":case "SeekBar":view = createSeekBar(context, attrs);verifyNotNull(view, name);break;case "androidx.appcompat.widget.AppCompatToggleButton":case "ToggleButton":view = createToggleButton(context, attrs);verifyNotNull(view, name);break;default:// The fallback that allows extending class to take over view inflation// for other tags. Note that we don't check that the result is not-null.// That allows the custom inflater path to fall back on the default one// later in this method.view = createView(context, name, attrs);}

然后把自定义的view给new进去,写一个基类继承自AppCompatActivity,
重写getDelegate方法,再写一个CustomCompatDelegate类继承自AppCompatDelegateImpl

在CustomCompatDelegate里重写createView方法

@Overridepublic View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) {if (customAppCompatViewInflater == null) {TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);String viewInflaterClassName =a.getString(R.styleable.AppCompatTheme_viewInflaterClass);if ((viewInflaterClassName == null)|| AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {// Either default class name or set explicitly to null. In both cases// create the base inflater (no reflection)customAppCompatViewInflater = new CustomAppCompatViewInflater();} else {try {Class viewInflaterClass = Class.forName(viewInflaterClassName);customAppCompatViewInflater =(CustomAppCompatViewInflater) viewInflaterClass.getDeclaredConstructor().newInstance();} catch (Throwable t) {Log.i(TAG, "Failed to instantiate custom view inflater "+ viewInflaterClassName + ". Falling back to default.", t);customAppCompatViewInflater = new CustomAppCompatViewInflater();}}}boolean inheritContext = false;if (IS_PRE_LOLLIPOP) {inheritContext = (attrs instanceof XmlPullParser)// If we have a XmlPullParser, we can detect where we are in the layout? ((XmlPullParser) attrs).getDepth() > 1// Otherwise we have to use the old heuristic: shouldInheritContext((ViewParent) parent);}return customAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */true, /* Read read app:theme as a fallback at all times for legacy reasons */VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */);}

把里面的AppCompatViewInflater替换成CustomAppCompatViewInflater。
替换完成后,我们的界面都继承自这个基类Activity,重写dispatchTouchEvent方法,这样所有的view都可以获取触摸坐标了。

这里要感谢海哥的无私提醒,才能让我打通这一关。

Android之hook dispatchTouchEvent方法相关推荐

  1. [Android]objection hook指定方法

    最近调试一款app,遇到个问题是某个类里面有多个重名的重载函数,但我只想hook某个指定的函数.看了一下网上很多文章基本都是简略带过,只有这篇还提示了一下可以带上参数来区分: objection 指南 ...

  2. Android中免root的hook框架学习——whale(二)实战hook java方法

    文末附项目完整代码下载地址 一.在android studio中创建一个新的项目 Hook Java, 把whale项目里的java文件夹的代码复制到自己的项目中. 复制 built/Android  ...

  3. android socket_盘点Android常用Hook技术

    Android平台开发测试过程中,Hook技术是每个开发人员都常用的技术.可以用于绕过系统限制.修改别人发布的代码.动态化.调用隐藏API.插件化.组件化.自动化测试.沙箱等等. Hook如果要跨进程 ...

  4. Android滑动冲突解决方法

    Android滑动冲突解决方法 滑动冲突 首先讲解一下什么是滑动冲突.当你需要在一个ScrollView中嵌套使用ListView或者RecyclerView的时候你会发现只有ScrollView能够 ...

  5. Android Art Hook 技术方案

    Android Art Hook 技术方案 by 低端码农 at 2015.4.13 www.im-boy.net 0x1 开始 Anddroid上的ART从5.0之后变成默认的选择,可见ART的重要 ...

  6. Android Native Hook工具

    前言 在目前的安卓APP测试中对于Native Hook的需求越来越大,越来越多的APP开始逐渐使用NDK来开发核心或者敏感代码逻辑.个人认为原因如下: 安全的考虑.各大APP越来越注重安全性,NDK ...

  7. Android主流HOOK框架介绍与应用--游戏破解游戏外挂的必杀技

    概述 使用HOOK方案主要是在分析的时候会经常用到,虽然二次打包重新修改代码也可以做到,但是一方面效率低,另一方面如果APP有校验的逻辑就需要进一步绕过,总体还是比较费时费力.所以,通过动态HOOK的 ...

  8. 如何写一个Android inline hook框架

    Android_Inline_Hook https://github.com/GToad/Android_Inline_Hook_ARM64 有32和64的实现,但是是分离的,要用的话还要自己把两份代 ...

  9. android 拦截点击事件,Android双击事件拦截方法

    下文我们介绍两种双击事件拦截的方式 1.通过Android的事件分发机制进行拦截(dispatchTouchEvent) 话不多说,直接上代码: /** 判断是否是快速点击 */ private st ...

最新文章

  1. Git本地仓库管理远程库(GitHub)——clone(下载)、push(提交)、pull(拉取)操作
  2. @keyframes中translate和scale混用问题
  3. Error running tomcat8 Address localhost:1099 is already in use 错误解决
  4. centos7搭建git代码仓库
  5. linux下直接清空日志的方法
  6. 用Jenkins编译asp.net
  7. 线段树(区间合并) LA 3989 Ray, Pass me the dishes!
  8. win7计算机名称格式,win7笔记本电脑如何显示文件扩展名
  9. Bootstrap自定义图标
  10. 云智巡在连锁药店的巡检作用
  11. php调用接口及编写接口
  12. 全球主流云桌面传输协议
  13. 2022年中国工业软件市场现状及发展前景预测分析
  14. 你的童年经历过放牛,放羊,干农活吗?然后你现在对这些都怀念吗?
  15. JavaEE项目bug修复记——一场由特殊空字符(160号ASCII码)引发的血案
  16. Android系统初识
  17. 利用python获取自己的qq群成员信息!
  18. 电脑硬盘分区软件哪个好用,无损分区软件哪个好
  19. 体育生考大学能学计算机专业吗,体育生可以报考的大学和专业有哪些
  20. 酷比魔方可以PHP编程么,酷比魔方iwork12麻烦适配一下,找了很久,没有适配这个本子的rom...

热门文章

  1. clickhouse除数为0时报错
  2. 生信作图神器circos的Windows系统安装
  3. dx12 龙书第七章学习笔记 -- 利用Direct3D绘制几何体(续)
  4. 刚刚登场的新一任杭州80后首富
  5. GPS全球卫星定位导航系统
  6. 黑马头条项目 一 项目设计及基础搭建
  7. OL4两种绘制台风圈方式的比较
  8. 八滩中学2021高考成绩查询,盐城十大名校高考成绩公布,滨海八滩中学应届本科上线525人...
  9. mysql 测试环境过一段时间就连接不上
  10. Java 设计模式之观察者模式(Observer pattern)