Android Hook技术详解
文章目录
- 代理模式
- 静态代理
- 动态代理
- Android Hook
- 实例:Hook实现Activity插件化
- Hook技术在项目优化中的用处
- Toast WindowManager$BadTokenException
- TimeoutException
- 结语
由于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 点。
当然,这里我建议在灰度环境经过大量测试,通过没问题以后再放到生产环境上,毕竟黑科技还是具有一定风险的。
个人微信公共账号已上线,欢迎关注:
Android Hook技术详解相关推荐
- Android 3D游戏开发技术详解与典型案例
下载地址 <Android3D游戏开发技术详解与典型案例>主要以Android平台下3D游戏的开发为主题,并结合真实的案例向读者详细介绍了OpenGL ES的基础 知识及3D游戏程序开发的 ...
- 【备注】【C24】《Android 3D游戏开发技术详解与典型案例》PDF 下载
[C24]<Android 3D游戏开发技术详解与典型案例>PDF 下载 目前市面上的Android技术书籍还比较少,Android3D游戏开发的书籍更是没有.因此,在现在市面上,Andr ...
- Android 3D游戏开发技术详解与典型案例pdf
下载地址:网盘下载 <Android3D游戏开发技术详解与典型案例>主要以Android平台下3D游戏的开发为主题,并结合真实的案例向读者详细介绍了OpenGL ES的基础知识及3D游戏程 ...
- java技术详解_Java反射技术详解及实例解析
前言 相信很多人都知道反射可以说是Java中最强大的技术了,它可以做的事情太多太多,很多优秀的开源框架都是通过反射完成的,比如最初的很多注解框架,后来因为java反射影响性能,所以被运行时注解APT替 ...
- 视频直播技术详解(8)直播云 SDK 性能测试模型
<视频直播技术详解>系列之八:直播云 SDK 性能测试模型 牛小七2016年10月12日发布在 视频直播技术详解 七牛云于 6 月底发布了一个针对视频直播的实时流网络 LiveNet 和完 ...
- 视频直播技术详解(7)现代播放器原理
<视频直播技术详解>系列之七:现代播放器原理 牛小七2016年9月29日发布在 视频直播技术详解 from: http://blog.qiniu.com/archives/7040 七牛云 ...
- 视频直播技术详解(0)开篇
(原标题:<视频直播技术详解>系列之一:开篇) 文|何李石 随着互联网用户消费内容和交互方式的升级,支撑这些内容和交互方式的基础设施也正在悄悄发生变革.手机设备拍摄视频能力和网络的升级催生 ...
- Android LiveData组件详解以及LiveDataBus
转载请标明出处:https://blog.csdn.net/zhaoyanjun6/article/details/99749323 本文出自[赵彦军的博客] 一.LiveData简介 LiveDat ...
- 《视频直播技术详解》系列之七:现代播放器原理
七牛云于 6 月底发布了一个针对视频直播的实时流网络 LiveNet 和完整的直播云解决方案,很多开发者对这个网络和解决方案的细节和使用场景非常感兴趣. 结合七牛实时流网络 LiveNet 和直播云解 ...
- 《视频直播技术详解》系列之八:直播云 SDK 性能测试模型
七牛云于 6 月底发布了一个针对视频直播的实时流网络 LiveNet 和完整的直播云解决方案,很多开发者对这个网络和解决方案的细节和使用场景非常感兴趣. 结合七牛实时流网络 LiveNet 和直播云解 ...
最新文章
- VMware下ghost安装XP后无法从硬盘启动的问题
- go get 代理 找不到包_初步看看Go1.10 支持 HTTPS 代理
- lucene倒排索引瘦身的一些实验——merge的本质是减少cfx文件 变为pos和doc;存储term vector多了tvx和tvd文件有337M...
- HDU2188-Bash博弈
- ArrayList 有序集合 c#
- python ico_Python协程asynico模块解读
- 【转】 ids for this class must be manually assigned before calling save()
- 开辟 Dart 到 Native 的超级通道,饿了么跨平台的最佳实践
- Stanford CoreNLP - 自然语言软件
- 使用Div自动换行一事
- mysql数据库封装类_基于mysqli封装的数据库类
- 高稳定度低纹波直流电源设计 【转自电子工程师世界】
- JavaScript函数防抖与截流
- php 执行定时任务.
- IOS内购验证 (Java版)
- Final Cut pro快捷键大全
- Win10更改用户名
- error: cannot open Packages database in /var/lib/rpm
- 大气湍流下的少模光纤耦合
- 前端面试题汇总(含答案)(JS篇)