Android Hook技术详解

Android Hook技术详解代理模式详解,动态代理原理分析,AndroidHook技术详解以及其在插件化,性能优化上的一些案例分析。

大家好,又见面了,我是你们的朋友全栈君。

由于Android Hook技术底层原理其实说白了就是java的反射和动态代理,所以这里我们先来讲一下代理模式。

代理模式

代理模式主要是为了给某些不想直接访问或者访问起来有些困难的对象提供一个代理对象来简洁的访问,分为静态代理和动态代理。

静态代理

首先我们先来讲下静态代理,这里举一个小例子,我想要买一双aj,然后就朋友圈找了个微商代理:

在这里插入图片描述
在这里插入图片描述

代理类,也就是微商:
在这里插入图片描述

让我们来看下输出结果:
在这里插入图片描述

动态代理

相对于静态代理,在代码运行前就已经存在了代理类的calss编译文件,动态代理则是在代码运行时通过反射来动态的生成代理类的对象,并确定要代理谁。接下来我们来看代码,同样的例子:

在这里插入图片描述

在这里插入图片描述

java提供了动态的代理接口InvocationHandler:
在这里插入图片描述

在这里插入图片描述

来看下运行结果:
在这里插入图片描述

Android Hook

在Android操作系统中,有一套自己的事件分发机制,所有的代码调用和回调都是按照一定顺序执行的,Hook技术存在的意义就在于,我们可以在事件传送到终点前截获并监控该事件的传输,并且做一些自己的处理,可以简单的理解为把一件事中间拦截掉了,然后搞了点自己的小动作然后让他继续走下去。

为了保证hook的稳定性,一般拦截的点都会选择比较容易找到并且不易发生变化的对象,比如静态变量和单例。

实例:Hook实现Activity插件化

tips:这一小段源码层面我们基于Android api 24,也就是7.0。8.0上启动Activity实现不同,基本原理相同,读者可以自行操作。

目前,圈内的几个插件化框架在Activity插件化问题上,主要有3种实现方式,反射、接口和Hook技术。而反射因为性能问题,接口因为效率问题,Hook技术是主流实现方式,这里我们就用hook来实现Activity插件化。

这里我们主要注意点放在hook上,所以插件化具体不展开,具体Activity插件化过程可以描述成,A Activity要跳转到一个在Manifest.xml里没有注册过的B Activity,这个过程需要在AMS校验之前把跳转Activity目标从B改成一个在Manifest.xml注册过的C Activity,然后在AMS校验之后再把C改成B,然后实现跳转逻辑。

hook第一步,首先阅读源码,寻找hook点。注意上文中提到的,为了保证hook的稳定性,一般拦截的点都会选择比较容易找到并且不易发生变化的对象,比如静态变量和单例。这里需要读者熟悉Activity启动流程,这里就不展开了,有兴趣的朋友可以先去网上搜下大概有个概念然后再阅读下去。

我们来看startActivity()方法,不断的跟踪进去,我们会发现:
在这里插入图片描述
由mInstrumentation调用了execStartActivity方法启动Activity,注意这只在AMS校验之前,也就是这里AMS还没有校验要跳转的Activity是否在Manifest.xml里注册,我们可以在这里实现把B Activity替换成C的过程。

然后会在ActivityThread中的performLaunchActivity方法里调用mInstrumentation的newActivity方法用类加载器创建Activity的实例,我们可以在这里把它替换成我们要跳转的B Activity.。

在这里插入图片描述

这里,我们就找到hook点了就是mInstrumentation,我们只要自定义一个instrumentation替换掉即可,下面贴下代码,代码中都有注释,原理懂了,代码理解起来就很方便了。

工具类:FieldUtil.java
在这里插入图片描述
自定义instruction:ProxyInstrumentation.java

在这里插入图片描述

controll操作类:HookUtil.java
在这里插入图片描述

然后Application调用下操作即可:
在这里插入图片描述

Hook技术在项目优化中的用处

Toast WindowManager$BadTokenException

tips:这一小段源码层面我们主要针对于Android7.x。

相信Android朋友们平时开发的时候应该都遇到过这个问题,这是我在做的app的线上报上来的日志:
在这里插入图片描述
ok,先来简单分析下这个问题的原因:token失效。在看Toast.java源码的过程中,我们会发现,Toast的展示并不是自己控制的,而是通过AIDL使用INotificationManager中的NotificationManagerService控制的。当要显示一个Toast的时候,NotificationManagerService会产生一个token用于校验。在WindowManager要添加这个Toast的时候会去校验这个token,如果token有效,则添加窗口,无效则报crash。

通常情况下是不会出现这个问题的,但是在某种情况下Android 进程某个 UI 线程的某个消息阻塞,导致toast.show()方法一直无法被调用,这个的同时NotificationManager的超时检测结束,删除了token,在show()方法之前,就会出现这个异常。

这个crash我在跑demo的时候的时候使用让ui线程休眠的方式在Android 7.1.1的虚拟机上没有复现出来,朋友们可以参考腾讯这篇文章Toast问题深度剖析(一),有复现出来的朋友欢迎评论区一起交流。

虽然没复现出来这个token is valid,但我在阅读Toast源代码后想到用另一种方式来复现这个BadTokenException:
在这里插入图片描述

先来看点击了按钮以后报的错误:
在这里插入图片描述
因为type==TYPE_TOAST的类型的toast不能重复添加,所以这样也会报一个BadTokenException,接下来我们就要通过这个demo,用hook的解决方案来解决这个异常。

阅读源码我们发现,在Android 7.0 Toast.java上:
在这里插入图片描述
和在Android 8.0 Toast.java上:
在这里插入图片描述
对比我们发现在Android 8.0中,在WindowManager进行addView的时候8.0进行了一层try catch保护,而在7.0上并没有。那么我们就可以参考8.0的方法,直接catch住这个异常。

然后我们就可以来找hook点了,这里Toast 里面有一个静态变量mTN,TN类中通过调用handleShow()方法来把toast添加到window上,而handleShow()是怎么调用的呢?通过一个final的Handler,所以就很简单了,我们hook点就定位这个mTN,然后反射替换TN的内部成员变量mHandler,从而添加try-catch做到保护即可。

具体代码如下:
在这里插入图片描述

TimeoutException

TimeoutException这个问题相信朋友们应该也不会陌生:
在这里插入图片描述
这个exception又是为什么会出现呢?我们先来分析下原因:
在VM GC的时候,为了减少程序的卡顿,会启动FinalizerWatchdogDaemon等四个守护线程,而FinalizerWatchdogDaemon的作用是用来监控FinalizerDaemon线程的执行的。一旦检测到执行成员函数finalize时超出一定时间,那么就会退出VM,抛出TimeoutException。

所以,如果要模拟这个问题,完美只要引用一个重写了finalize方法的实例,并且在finalize方法中有耗时操作,然后我们手动GC就可以了。关于finalizer对象对内存和性能的影响有兴趣的朋友可以去阅读下这篇[再谈Finalizer对象--大型App中内存与性能的隐性杀手](https://yq.aliyun.com/articles/225755)

ok接下来我们来寻找解决问题的点,这里我AS上阅读源码没找到Daemons这个类,然后我就在这里凑合着看了看,Daemons.java

阅读源码我们发现,可以通过反射将FinalizerWatchdogDaemon中的thread置空,这样就不会执行此线程,也就不会出现TimeoutException了

在这里插入图片描述

这个问题上,我推荐有兴趣的朋友再去看一下极客时间张绍文老师对这个问题的想法和demo,这里会出现一个问题就是在Android 6.0之前会有线程安全问题,在demo中对这个问题有全面的处理。

结语

Hook这个黑科技还是比较实用的,关键在于阅读源码,然后通过代码的依赖关系,发现一个取巧的 Hook 点。

当然,这里我建议在灰度环境经过大量测试,通过没问题以后再放到生产环境上,毕竟黑科技还是具有一定风险的。

个人微信公共账号已上线,欢迎关注:
在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/141434.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • Nginx+DNS负载均衡「建议收藏」

    Nginx+DNS负载均衡「建议收藏」今天看了很多关于nginx负载均衡的博客,人家推荐的都是自己的ip来做负载,但是同样有说DNS负载均衡,刚开始我也是一头雾水,慢慢的分析才知道真正意义上的Nginx+DNS负载均衡。1.nginx负载均衡的5种策略(先了解这个后面的才好懂)轮询策略(默认)这种策略下每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。upstreambackserver{server192.168.0.14;server192.168.0.15;}指定

  • 什么是pnp问题_pnp什么意思

    什么是pnp问题_pnp什么意思点击关注上方“五分钟学算法”,设为“置顶或星标”,第一时间送达干货。转自后端技术指南针1前言今天和大家一起了解个高能知识点:P=NP问题。看到这里我们可能是一头雾水,不由得发问:P问题…

  • pycharm安装使用_anaconda环境变量配置

    pycharm安装使用_anaconda环境变量配置1.打开AnacondaPrompt,进入虚拟环境condaactivateTF1.142.安装pyinstaller,在anaconda中输入pipinstallPyInstaller3.在pycharm中配置pyinstaller打开Pycharm,进入settings按下图操作3.1点击ExternalTools3.2点击新建3.3输入任意名称3.4在E:\RuanJian\Anaconda\anzhuang\envs\TF1.14\Scripts\路径

  • Python wxPython基本教程「建议收藏」

    Python wxPython基本教程「建议收藏」PythonwxPython在资源上比较小,而且官方文档也不好找,wxPython在python2.x和python3.x安装上有区别:以下为python3.x安装为例:1.网上下载whl文件安装:路径:https://wxpython.org/Phoenix/snapshot-builds/文件名解释:wxPython_Phoenix-3.0.3.dev2812+b3485d4-c…

  • OpenCV人脸识别的原理 完整版代码

    OpenCV人脸识别的原理 完整版代码http://blog.csdn.net/yanming901012/article/details/8606183本程序首先利用从摄像头检测到的人脸图片,先进行直方图均衡化 并缩放到92*112的图片大小,然后根据train.txt的采集到的人脸模版 进行匹配识别(最好是在统一光照下,采集不同角度的人脸图片各一张) 注意:影响的极大因素在于光照,模版若与采集的图像光照不一样,识别率很低。…

  • 手机扫码登录实现思路是什么_扫码登录wifi如何实现

    手机扫码登录实现思路是什么_扫码登录wifi如何实现手机扫码登录实现思路,从业务场景逐个解决问题,引出实现方案

    2022年10月24日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号