手把手讲解系列文章,是我写给各位看官,也是写给我本身的。 文章可能过度详细,可是这是为了帮助到尽可能多的人,毕竟工做5,6年,不能老吸血,也到了回馈开源的时候. 这个系列的文章: 一、用通俗易懂的讲解方式,讲解一门技术的实用价值 二、详细书写源码的追踪,源码截图,绘制类的结构图,尽可能详细地解释原理的探索过程 三、提供Github 的 可运行的Demo工程,可是我所提供代码,更可能是提供思路,抛砖引玉,请酌情cv 四、集合整理原理探索过程当中的一些坑,或者demo的运行过程当中的注意事项 五、用gif图,最直观地展现demo运行效果java
若是以为细节太细,直接跳过看结论便可。 本人能力有限,如若发现描述不当之处,欢迎留言批评指正。android
学到老活到老,路漫漫其修远兮。与众君共勉 !git
我以前的一键换肤技术文章里面提到了hook技术的概念,有读者反馈说看不太懂, 看来,仍是没有说"人话",其实能够描述地再接地气一点,因而再写一片专文吧。github
本文只作入门级引子,旨在让不了解 Hook的人经过本文,能认识到 hook
有什么用
,怎么用
,怎么学
,能达到这个目的,我就知足了.编程
1. hook的定义
2. 实用价值
3. 前置技能
4. hook通用思路
5. 案例实战
6. 效果展现
hook,钩子。勾住系统的程序逻辑。 在某段
SDK源码逻辑
执行的过程当中,经过代码手段拦截
执行该逻辑,加入本身
的代码逻辑。api
hook是中级开发通往高级开发的必经之路。 若是把谷歌比喻成 安卓的造物主,那么安卓SDK源码里面就包含了万事万物的本源。 中级开发者,只在利用万事万物,浮于表层,而高级开发者能从本源上去改变万事万物,深刻核心。数组
最有用的实用价值: hook是安卓面向切面(AOP)编程的基础,可让咱们在
不变动原有业务的前提
下,插入额外的逻辑
. 这样,既保护了原有业务的完整性,又能让额外的代码逻辑不与原有业务产生耦合. (想象一下,让你在一个成熟的app上面给每个
按钮添加埋点接口,不说一万个,就说成百上千个
控件让你埋点,让你写一千次
埋点调用,你是否是要崩溃,hook
能够轻松实现)bash
学好了hook,就有但愿成为高级工程师, 完成初中级没法完成的开发任务, 升职,加薪,出任CEO,迎娶白富美,走上人生巅峰,够不够实用?app
- java反射 熟练掌握类
Class,方法Method,成员Field
的使用方法 源码内部,不少类和方法都是@hide
的,外部直接没法访问,因此只能经过反射,去建立源码中的类,方法,或者成员.
- 阅读安卓源码的能力
hook
的切入点都在源码内部,不能阅读源码,不能理清源码逻辑,则不用谈hook
. 其实使用androidStudio
来阅读源码有个坑,,有时候会看到源码里面"一片飘红"
,看似是有什么东西没有引用进来,实际上是由于有部分源码没有对开发者开放,解决起来很麻烦, 因此,推荐从安卓官网下载整套源码,而后使用SourceInsight
查看源码。 若是不须要跳来跳去的话,直接用 安卓源码网站 一步到位
不管多么复杂的源码,咱们想要干涉其中的一些执行流程,最终的杀招
只有一个: “偷梁换柱”
. 而 “偷梁换柱”
的思路,一般都是一个套路:ide
1. 根据需求肯定 要hook的对象 2. 寻找要hook的对象的持有者,拿到要hook的对象 (持有:B类 的成员变量里有 一个是A的对象,那么B就是A的持有者,以下)
class B{ A a; } class A{} 复制代码
3. 定义“要hook的对象”的代理类,而且建立该类的对象 4. 使用上一步建立出来的对象,替换掉要hook的对象
上面的4个步骤可能仍是有点抽象,那么,下面用一个案例,详细说明每个步骤.
这是一个最简单的案例: 咱们本身的代码里面,给一个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().mOnClickListenerpublic 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的对象,达成
偷梁换柱
的最终目的. 利用反射,将咱们建立的代理点击事件对象,传给这个viewfield.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对象,也就是点击事件的持有者
//要从这里面拿到当前的点击事件对象
Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");// 这是内部类的表示方法
Field field = listenerInfoClz.getDeclaredField("mOnClickListener");
final View.OnClickListener onClickListenerInstance = (View.OnClickListener) field.get(mListenerInfo);//取得真实的mOnClickListener对象
//2\. 建立咱们本身的点击事件代理类
// 方式1:本身建立代理类
// ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance);
// 方式2:因为View.OnClickListener是一个接口,因此能够直接用动态代理模式
// Proxy.newProxyInstance的3个参数依次分别是:
// 本地的类加载器;
// 代理类的对象所继承的接口(用Class数组表示,支持多个接口)
// 代理类的实际逻辑,封装在new出来的InvocationHandler内
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);//执行被代理的对象的逻辑
}
});
//3\. 用咱们本身的点击事件代理类,设置到"持有者"中
field.set(mListenerInfo, proxyOnClickListener);
//完成
} catch (Exception e) {
e.printStackTrace();
}
}
// 还真是这样,自定义代理类
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);
}
}
}
}
复制代码
这段代码阅读起来的可能难点:
Method,Class,Field
的使用method.setAccessible(true);
//因为getListenerInfo()
方法并非public
的,因此要加这个代码来保证访问权限field.set(mListenerInfo, proxyOnClickListener);
//把一个proxyOnClickListener
对象,设置给mListenerInfo
对象的field
属性.Proxy.newProxyInstance
的使用Proxy.newProxyInstance
的3个参数依次分别是: 本地的类加载器; 代理类的对象所继承的接口(用Class数组表示,支持多个接口) 代理类的实际逻辑,封装在new出来的InvocationHandler
内 到这里,最后一步,也完成了.
先给出Demo:GithubDemo 当我点击这个 hello World:
弹出一个
Toast
,而且:在日志中能够看到
同时我并无改动
setOnClickListener
的代码,我只是在它的后面,加了一行HookSetOnClickListenerHelper.hook(this, v);
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "别点啦,再点我咬你了...", Toast.LENGTH_SHORT).show();
}
});
HookSetOnClickListenerHelper.hook(this, v);//这个hook的做用,是 用咱们本身建立的点击事件代理对象,替换掉以前的点击事件。
复制代码
v.setOnClickListener
已经被hook
.前方有坑,高能提示:
我曾经尝试,是否是能够将上面两段代码换个顺序. 结果证实,换了以后,hook就无论用了,缘由是,hook方法的做用,是将v已有的 点击事件,替换成 咱们代理的点击事件。因此,在v尚未点击事件的时候进行hook,是没用的
Hook的水很深,这个只是一个入门级的案例,我写这个,目的是说明hook技术的套路,无论咱们要hook源码的哪一段逻辑,都逃不过 hook通用思路 这“三板斧”,套路掌握了,就有能力学习更难的Hook技术.
Hook的学习,须要咱们大量地阅读源码,要对SDK有较为深刻的了解,不再是浮于表面,只会对SDK的api进行调用,而是真正地干涉“造物主谷歌”的既定规则. 学习难度很大,可是收益也不小,高级开发和初中级开发的薪资差距巨大,职场竞争力也不可同日而语.
高级开发之路漫漫长,与众君共勉!
做者:波澜步惊 连接:www.jianshu.com/p/74c12164f…