文章目录
Android 插件化系列文章目录
前言
一、Hook 实现思路
二、Hook 按钮点击事件
1、按钮点击事件
2、熟悉底层源码
3、获取 View 的 ListenerInfo mListenerInfo 成员
4、分析 Hook 点
5、反射 ListenerInfo 并设置新的 OnClickListener 监听器
三、完整代码示例
四、博客资源
前言
在上一篇博客 【Android 插件化】Hook 插件化框架 ( Hook 技术 | 代理模式 | 静态代理 | 动态代理 ) 中 , 对 Hook 技术进行了简要介绍 , Android 中的 Hook 技术主要是通过
反射
代理模式 ( 动态代理 / 静态代理 )
实现的 ;
之所以使用 Hook 技术 , 是因为反射系统的源码时 , 会出现问题 , Google 官方对 Android 的反射进行了限制 ;
反射出现问题时 , 必须找到一个可以反射的反射点挂钩子 , 如在 A 位置无法进行反射 , 就在 B 位置挂 Hook 钩子 ;
最终要实现的是使用 Hook , 影响 Activity 的启动流程 , 在启动流程中注入我们想要的业务逻辑 , 干涉启动流程 , 以达到能启动插件包 APK 中的 Activity 的目的 ;
一、Hook 实现思路
Hook 点选择规则 : Hook 技术的关键是找好 Hook 点 , 将钩子挂在哪 , 勾住哪个方法 , 需要遵循一定的规则 :
静态变量 / 单例 : 优先选择 静态变量 , 单例对象 , 这些对象一旦创建 , 基本不会改变 , 容易定位 ;
Hook 点实现思路 :
① 查找 Hook 点 : 用于下钩子 ;
② 选择代理模式 : 静态代理 / 动态代理 ;
③ 代理替换 : 通过反射 , 将钩子替换成开发者自定义的代理 , 一般是在原有调用的基础上 , 不影响原来功能的前提下 , 注入新的逻辑 ;
二、Hook 按钮点击事件
1、按钮点击事件
获取布局文件的按钮 , 并为其设置点击事件 , 该点击事件 public void onClick(View v) 就是需要 Hook 的方法 , 我们使用 Hook 技术 , 使用动态代理 , 替换掉该 onClick 方法 , 注入额外的业务逻辑 ;
// 获取按钮 , 并未按钮组件设置点击事件 Button button = findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.i(TAG, "Button OnClickListener onClick"); } });
2、熟悉底层源码
使用 Hook 的前提是 , 必须熟悉要 Hook 功能的底层源码 , 如 : Hook 按钮点击事件 , 必须熟悉 View 组件的 OnClickListener 相关源码 ;
先分析 View 的 setOnClickListener 方法的源码 , 传入 OnClickListener l 监听器 , 将该监听器赋值给 getListenerInfo().mOnClickListener 值 ;
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { @UnsupportedAppUsage ListenerInfo mListenerInfo; /** * Register a callback to be invoked when this view is clicked. If this view is not * clickable, it becomes clickable. * * @param l The callback that will run * * @see #setClickable(boolean) */ public void setOnClickListener(@Nullable OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; } @UnsupportedAppUsage ListenerInfo getListenerInfo() { if (mListenerInfo != null) { return mListenerInfo; } mListenerInfo = new ListenerInfo(); return mListenerInfo; } }
getListenerInfo().mOnClickListener = l;
位置作为钩子的 Hook 点 , 勾住该方法 ;
3、获取 View 的 ListenerInfo mListenerInfo 成员
先使用反射获取 View 组件的 getListenerInfo 方法 ;
// 获取 View 的 getListenerInfo 方法 Method getListenerInfo = null; try { getListenerInfo = View.class.getDeclaredMethod("getListenerInfo"); } catch (NoSuchMethodException e) { e.printStackTrace(); }
设置 getListenerInfo 方法的可见性 , 之后要调用该方法 , 否则会报错 ;
// 执行所有的反射方法 , 设置成员变量 之前 , 都要设置可见性
getListenerInfo.setAccessible(true);
执行 反射获取的 getListenerInfo 方法 , 并获取 ListenerInfo mListenerInfo 成员 ;
// 执行 View view 对象的 getListenerInfo 方法 Object object = null; try { object = getListenerInfo.invoke(view); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); }
4、分析 Hook 点
分析 View 组件的 setOnClickListener 方法 , 最终将 OnClickListener l 点击监听器设置到哪 ?
getListenerInfo() 获取的是 ListenerInfo 类型的对象 , 其中就封装了 OnClickListener mOnClickListener 成员 , 点击监听器就是设置在这里 ;
知道了定义位置 , 就可以通过反射获取 mOnClickListener 对应的成员字段 Field ;
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { static class ListenerInfo { /** * Listener used to dispatch click events. * This field should be made private, so it is hidden from the SDK. * {@hide} */ @UnsupportedAppUsage public OnClickListener mOnClickListener; } /** * Interface definition for a callback to be invoked when a view is clicked. */ public interface OnClickListener { /** * Called when a view has been clicked. * * @param v The view that was clicked. */ void onClick(View v); } }
5、反射 ListenerInfo 并设置新的 OnClickListener 监听器
获取 ListenerInfo 中的 public OnClickListener mOnClickListener 成员 , 并重新设置新的成员 , 注入业务逻辑 ;
① 先根据全类名获取 android.view.View$ListenerInfo 字节码对象 ;
// ① 先根据全类名获取 ListenerInfo 字节码 Class<?> clazz = null; try { clazz = Class.forName("android.view.View$ListenerInfo"); } catch (ClassNotFoundException e) { e.printStackTrace(); }
② 获取 android.view.View$ListenerInfo 中的 mOnClickListener 成员
// ② 获取 android.view.View.ListenerInfo 中的 mOnClickListener 成员 Field field = null; try { field = clazz.getField("mOnClickListener"); } catch (NoSuchFieldException e) { e.printStackTrace(); }
③ 设置该字段访问性, 执行所有的反射方法 , 设置成员变量 之前 , 都要设置可见性 ;
// ③ 设置该字段访问性, 执行所有的反射方法 , 设置成员变量 之前 , 都要设置可见性 field.setAccessible(true);
④ 获取 mOnClickListener 成员变量 ;
// ④ 获取 mOnClickListener 成员变量
View.OnClickListener mOnClickListener = null; try { mOnClickListener = (View.OnClickListener) field.get(mListenerInfo); } catch (IllegalAccessException e) { e.printStackTrace(); }
⑤ 修改 View 的 ListenerInfo 成员的 mOnClickListener 成员 , 重新设置一个自定义的 View.OnClickListener 监听器 , 在该监听器的 onClick 方法中 , 调用之前获取的 监听器的 onClick 方法 , 此外还可以在该点击方法前后注入开发者自定义的业务逻辑 ;
// ⑤ 修改 View 的 ListenerInfo 成员的 mOnClickListener 成员 try { View.OnClickListener finalMOnClickListener = mOnClickListener; field.set(mListenerInfo, new View.OnClickListener(){ @Override public void onClick(View v) { Log.i(TAG, "Hook Before"); finalMOnClickListener.onClick(view); Log.i(TAG, "Hook After"); } }); } catch (IllegalAccessException e) { e.printStackTrace(); }
三、完整代码示例
完整代码示例 :
package com.example.plugin_hook; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 获取按钮 , 并未按钮组件设置点击事件 Button button = findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.i(TAG, "Button OnClickListener onClick"); } }); hook(button); } /** * hook Button 组件的 getListenerInfo 方法 * @param view */ private void hook(View view){ // 获取 View 的 getListenerInfo 方法 Method getListenerInfo = null; try { getListenerInfo = View.class.getDeclaredMethod("getListenerInfo"); } catch (NoSuchMethodException e) { e.printStackTrace(); } // 执行所有的反射方法 , 设置成员变量 之前 , 都要设置可见性 getListenerInfo.setAccessible(true); // 执行 View view 对象的 getListenerInfo 方法 Object mListenerInfo = null; try { mListenerInfo = getListenerInfo.invoke(view); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } // 反射获取 OnClickListener 成员 // ① 先根据全类名获取 ListenerInfo 字节码 Class<?> clazz = null; try { clazz = Class.forName("android.view.View$ListenerInfo"); } catch (ClassNotFoundException e) { e.printStackTrace(); } // ② 获取 android.view.View.ListenerInfo 中的 mOnClickListener 成员 Field field = null; try { field = clazz.getField("mOnClickListener"); } catch (NoSuchFieldException e) { e.printStackTrace(); } // ③ 设置该字段访问性, 执行所有的反射方法 , 设置成员变量 之前 , 都要设置可见性 field.setAccessible(true); // ④ 获取 mOnClickListener 成员变量 View.OnClickListener mOnClickListener = null; try { mOnClickListener = (View.OnClickListener) field.get(mListenerInfo); } catch (IllegalAccessException e) { e.printStackTrace(); } // ⑤ 修改 View 的 ListenerInfo 成员的 mOnClickListener 成员 // 其中 ListenerInfo 成员 是 try { View.OnClickListener finalMOnClickListener = mOnClickListener; field.set(mListenerInfo, new View.OnClickListener(){ @Override public void onClick(View v) { Log.i(TAG, "Hook Before"); finalMOnClickListener.onClick(view); Log.i(TAG, "Hook After"); } }); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
执行结果 :
2021-06-17 11:19:07.513 12251-12251/com.example.plugin_hook I/MainActivity: Hook Before 2021-06-17 11:19:07.513 12251-12251/com.example.plugin_hook I/MainActivity: Button OnClickListener onClick 2021-06-17 11:19:07.513 12251-12251/com.example.plugin_hook I/MainActivity: Hook After
四、博客资源
博客资源 :
GitHub : https://github.com/han1202012/Plugin_Hook