Hook 英文意思是钩子, 可以把一段执行着的代码钩下来, 然后加入我们自己的逻辑, 最后在放回去. 比如我们可以 Hook 住一段系统代码, 在执行系统代码之前加入我们自己的逻辑.
Hook 技术主要用到 java 反射和 java 动态代理两个知识点, 下面来个简单的例子, 我们来 Hook 一个按钮的点击事件
- Button button = findViewById(R.id.btn_click);
- button.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Toast.makeText(getApplicationContext(),((Button) v).getText(),Toast.LENGTH_SHORT).show();
- }
- });
点击按钮的时候, 我们如何能在不改变上面代码的前提下, 在 Toast 弹出之前执行一些别的逻辑呢? 比如现在 Toast 弹出的是按钮上的字, 我们动态的把子给它改了
思路就是我们通过反射拿到系统中的 OnClickListener 的对象, 通过动态代理, 创建一个该对象的代理对象, 这个代理对象中就可以写一些别的逻辑啦, 最后把这个代理对象通过反射设置回系统中来个偷梁换柱就可以啦.
OK 下面开始按照步骤来
第一步
反射拿到设置的 OnClickListener 对象, 上面的代码中, 我们进入 setOnClickListener 方法可以看到
- public void setOnClickListener(@Nullable OnClickListener l) {
- if (!isClickable()) {
- setClickable(true);
- }
- getListenerInfo().mOnClickListener = l;
- }
这里把我们传进来的回调对象设置给了 getListenerInfo().mOnClickListener.
- ListenerInfo getListenerInfo() {
- if (mListenerInfo != null) {
- return mListenerInfo;
- }
- mListenerInfo = new ListenerInfo();
- return mListenerInfo;
- }
getListenerInfo() 方法返回一个 ListenerInfo 对象, 它内部就保存了我们传过来的 mOnClickListener 回调对象. 我们功过反射执行 getListenerInfo 方法就能拿到 ListenerInfo 对象了如下
- Class<?> viewClass = Class.forName("android.view.View");
- // 需要拿到 setOnClickListener 方法 set 过去的对象
- Method getListenerInfoMethod = viewClass.getDeclaredMethod("getListenerInfo");
- getListenerInfoMethod.setAccessible(true);
- // 本质是 ListenerInfo 对象
- Object listenerInfo = getListenerInfoMethod.invoke(view);
拿到了 ListenerInfo 对象, 在通过它拿到它的成员变量 mOnClickListener
- Class<?> listenerInfoClass = Class.forName("android.view.View$ListenerInfo");
- Field onClickListenerField = listenerInfoClass.getField("mOnClickListener");
- final Object onClickListener = onClickListenerField.get(listenerInfo);
第二步
使用动态代理定义一个代理的 onClickListener, 在回调函数中添加一些别的逻辑
- Object proxyClickListener = Proxy.newProxyInstance(MainActivity.class.getClassLoader(),
- // 需要将监听的方法
- new Class[]{View.OnClickListener.class}, new InvocationHandler() {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- Button button = new Button(MainActivity.this);
- button.setText("我就点你");
- return method.invoke(onClickListener, button);
- }
- });
在 InvocationHandler 方法中就可以添加别的逻辑了, 这里添加了 Toast 用来测试, 最后返回一个代理的 OnClickListener 对象.
第三步
把代理的 OnClickListener 对象设置回系统中, 在第一步中已经拿到了 mOnClickListener 对象, 最后把它给改了就好了.
onClickListenerField.set(listenerInfo,proxyClickListener);
OK Hook 完成, 效果
完整代码:
- /**
- * hook
- * @param view 需要 hook 的 view
- */
- private void hook(View view) {
- try {
- Class<?> viewClass = Class.forName("android.view.View");
- // 需要拿到 setOnClickListener 方法 set 过去的对象
- Method getListenerInfoMethod = viewClass.getDeclaredMethod("getListenerInfo");
- getListenerInfoMethod.setAccessible(true);
- Object listenerInfo = getListenerInfoMethod.invoke(view);
- Class<?> listenerInfoClass = Class.forName("android.view.View$ListenerInfo");
- Field onClickListenerField = listenerInfoClass.getField("mOnClickListener");
- final Object onClickListener = onClickListenerField.get(listenerInfo);
- Object proxyClickListener = Proxy.newProxyInstance(MainActivity.class.getClassLoader(),
- // 需要将监听的方法
- new Class[]{View.OnClickListener.class}, new InvocationHandler() {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- Button button = new Button(MainActivity.this);
- button.setText("我就点你");
- return method.invoke(onClickListener, button);
- }
- });
- onClickListenerField.set(listenerInfo,proxyClickListener);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
来源: http://www.tuicool.com/articles/QnURVbN