本人能力有限,如若发现描述不当之处,欢迎留言批评指正。
学到老活到老,路漫漫其修远兮。与众君共勉 !
引子
我之前的一键换肤技术文章里面提到了hook技术的概念,有读者反馈说看不太懂, 看来,还是没有说"人话",其实可以描述地再接地气一点,于是再写一片专文吧。
本文只做入门级引子,旨在让不了解 Hook的人通过本文,能认识到 hook
有什么用
,怎么用
,怎么学
,能达到这个目的,我就满足了.
正文大纲
1. hook的定义
2. 实用价值
3. 前置技能
4. hook通用思路
5. 案例实战
6. 效果展示
正文
1. hook的定义
hook,钩子。勾住系统的程序逻辑。
在某段SDK源码逻辑
执行的过程中,通过代码手段拦截
执行该逻辑,加入自己
的代码逻辑。
2. 实用价值
hook是中级开发通往高级开发的必经之路。
如果把谷歌比喻成 安卓的造物主,那么安卓SDK源码里面就包含了万事万物的本源。
中级开发者,只在利用万事万物,浮于表层,而高级开发者能从本源上去改变万事万物,深入核心。
最有用的实用价值:
hook是安卓面向切面(AOP)编程的基础,可以让我们在不变更原有业务的前提
下,插入额外的逻辑
.
这样,既保护了原有业务的完整性,又能让额外的代码逻辑不与原有业务产生耦合.
(想象一下,让你在一个成熟的app上面给每一个
按钮添加埋点接口,不说一万个,就说成百上千个
控件让你埋点,让你写一千次
埋点调用,你是不是要崩溃,hook
可以轻松实现)
学好了hook,就有希望成为高级工程师,
完成初中级无法完成的开发任务,
升职,加薪,出任CEO,迎娶白富美,走上人生巅峰,够不够实用?
3. 前置技能
- java反射 熟练掌握类
Class,方法Method,成员Field
的使用方法
源码内部,很多类和方法都是@hide
的,外部直接无法访问,所以只能通过反射,去创建源码中的类,方法,或者成员.
- 阅读安卓源码的能力
hook
的切入点都在源码内部,不能阅读源码,不能理清源码逻辑,则不用谈hook
.
其实使用androidStudio
来阅读源码有个坑,,有时候会看到源码里面"一片飘红"
,看似是有什么东西没有引用进来,其实是因为有部分源码没有对开发者开放,解决起来很麻烦,
所以,推荐从安卓官网下载整套源码,然后使用SourceInsight
查看源码。
如果不需要跳来跳去的话,直接用 安卓源码网站 一步到位
4. hook通用思路
无论多么复杂的源码,我们想要干涉其中的一些执行流程,最终的杀招
只有一个: “偷梁换柱”
.
而 “偷梁换柱”
的思路,通常都是一个套路:
1. 根据需求确定 要hook的对象
2. 寻找要hook的对象的持有者,拿到要hook的对象
(持有:B类 的成员变量里有 一个是A的对象,那么B就是A的持有者,如下)
class B{
A a;
}
class A{}
3. 定义“要hook的对象”的代理类,并且创建该类的对象
4. 使用上一步创建出来的对象,替换掉要hook的对象
上面的4个步骤可能还是有点抽象,那么,下面用一个案例,详细说明每一个步骤.
5. 案例实战
这是一个最简单的案例:
我们自己的代码里面,给一个view设置了点击事件,现在要求在不改动这个点击事件的情况下,添加额外的点击事件逻辑.
View v = findViewById(R.id.tv);
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, “别点啦,再点我咬你了…”, Toast.LENGTH_SHORT).show();
}
});
这是view
的点击事件,toast
了一段话,现在要求,不允许改动这个OnClickListener
,要在toast
之前添加日志打印 Log.d(...)
.
乍一看,无从下手.看hook
如何解决.
按照上面的思路来:
第一步:根据需求确定 要hook的对象;
我们的目的是在OnClickListener
中,插入自己的逻辑.所以,确定要hook
的,是v.setOnClickListener()
方法的实参。
第二步:寻找要hook的对象的持有者,拿到要hook的对象
进入v.setOnClickListener
源码:发现我们创建的OnClickListener
对象被赋值给了getListenerInfo().mOnClickListener
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
继续索引:
getListenerInfo()
是个什么玩意?继续追查:
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
结果发现这个其实是一个
伪单例
,一个View对象中只存在一个ListenerInfo
对象.
进入ListenerInfo内部:发现OnClickListener
对象 被ListenerInfo所持有.
static class ListenerInfo {
…
public OnClickListener mOnClickListener;
…
}
到这里为止,完成第二步,找到了点击事件的实际持有者:
ListenerInfo
.
第三步:定义“要
hook
的对象”的代理类,并且创建该类的对象
我们要hook
的是View.OnClickListener
对象,所以,创建一个类 实现View.OnClickListener
接口.
static class ProxyOnClickListener implements View.OnClickListener {
View.OnClickListener oriLis;
public ProxyOnClickListener(View.OnClickListener oriLis) {
this.oriLis = oriLis;
}
@Override
public void onClick(View v) {
Log.d(“HookSetOnClickListener”, “点击事件被hook到了”);
if (oriLis != null) {
oriLis.onClick(v);
}
}
}
然后,
new
出它的对象待用。
ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance);
可以看到,这里传入了一个
View.OnClickListener
对象,它存在的目的,是让我们可以有选择地使用到原先的点击事件逻辑。一般hook
,都会保留原有的源码逻辑.
另外提一句:当我们要创建的代理类,是被接口所约束的时候,比如现在,我们创建的ProxyOnClickListener implements View.OnClickListener
,只实现了一个接口,则可以使用JDK提供的Proxy类来创建代理对象
Object proxyOnClickListener = Proxy.newProxyInstance(context.getClass().getClassLoader(),
new Class[]>>{View.OnClickListener.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.d(“HookSetOnClickListener”, “点击事件被hook到了”);//加入自己的逻辑
return method.invoke(onClickListenerInstance, args);//执行被代理的对象的逻辑
}
});
这个
代理类
并不是此次的重点,所以一笔带过.
到这里为止,第三步:定义“要hook的对象”的代理类,并且创建该类的对象
完成。
第四步:使用上一步创建出来的对象,替换掉要hook的对象,达成
偷梁换柱
的最终目的.
利用反射,将我们创建的代理点击事件对象,传给这个view
field.set(mListenerInfo, proxyOnClickListener);
这里,贴出最终代码:
/**
- hook的辅助类
- hook的动作放在这里
*/
public class HookSetOnClickListenerHelper {
/**
- hook的核心代码
- 这个方法的唯一目的:用自己的点击事件,替换掉 View原来的点击事件
- @param v hook的范围仅限于这个view
*/
public static void hook(Context context, final View v) {//
try {
// 反射执行View类的getListenerInfo()方法,拿到v的mListenerInfo对象,这个对象就是点击事件的持有者
Method method = View.class.getDeclaredMethod(“getListenerInfo”);
method.setAccessible(true);//由于getListenerInfo()方法并不是public的,所以要加这个代码来保证访问权限
Object mListenerInfo = method.invoke(v);//这里拿到的就是mListenerInfo对象,也就是点击事件的持有者
//要从这里面拿到当前的点击事件对象
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
光有这些思路和搞懂单个知识的应用是还远远不够的,在Android开源框架设计思想中的知识点还是比较多的,想要搞懂还得学会整理和规划:我们常见的**Android热修复框架、插件化框架、组件化框架、图片加载框架、网络访问框架、RxJava响应式编程框架、IOC依赖注入框架、最近架构组件Jetpack等等Android第三方开源框架,**这些都是属于Android开源框架设计思想的。如下图所示:
这位阿里P8大佬针对以上知识点,熬夜整理出了一本长达1042页的完整版如何解读开源框架设计思想PDF文档,内容详细,把Android热修复框架、插件化框架、组件化框架、图片加载框架、网络访问框架、RxJava响应式编程框架、IOC依赖注入框架、最近架构组件Jetpack等等Android第三方开源框架这些知识点从源码分析到实战应用都讲的简单明了。
由于文档内容过多,篇幅受限,只能截图展示部分
整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~
你的支持,我的动力;祝各位前程似锦,offer不断!!!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
容过多,篇幅受限,只能截图展示部分**
[外链图片转存中…(img-Gfk6PUF1-1712274157899)]
[外链图片转存中…(img-KLNZWxAN-1712274157900)]
整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~
你的支持,我的动力;祝各位前程似锦,offer不断!!!