前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Android 插件化】Hook 插件化框架 ( Hook 实现思路 | Hook 按钮点击事件 )

【Android 插件化】Hook 插件化框架 ( Hook 实现思路 | Hook 按钮点击事件 )

作者头像
韩曙亮
发布2023-03-29 14:12:55
6730
发布2023-03-29 14:12:55
举报
文章被收录于专栏:韩曙亮的移动开发专栏

Android 插件化系列文章目录

【Android 插件化】插件化简介 ( 组件化与插件化 )

【Android 插件化】插件化原理 ( JVM 内存数据 | 类加载流程 )

【Android 插件化】插件化原理 ( 类加载器 )

【Android 插件化】“ 插桩式 “ 插件化框架 ( 原理与实现思路 )

【Android 插件化】“ 插桩式 “ 插件化框架 ( 类加载器创建 | 资源加载 )

【Android 插件化】“ 插桩式 “ 插件化框架 ( 注入上下文的使用 )

【Android 插件化】“ 插桩式 “ 插件化框架 ( 获取插件入口 Activity 组件 | 加载插件 Resources 资源 )

【Android 插件化】“ 插桩式 “ 插件化框架 ( 运行应用 | 代码整理 )

【Android 插件化】Hook 插件化框架 ( Hook 技术 | 代理模式 | 静态代理 | 动态代理 )

【Android 插件化】Hook 插件化框架 ( Hook 实现思路 | Hook 按钮点击事件 )


文章目录


前言

在上一篇博客 【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 方法 , 注入额外的业务逻辑 ;

代码语言:javascript
复制
// 获取按钮 , 并未按钮组件设置点击事件
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 值 ;

代码语言:javascript
复制
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;
    }
}

如果要替换 按钮点击事件 , 可以反射 View 的 getListenerInfo() 方法 , 直接设置一个新的 点击监听器 ;

代码语言:javascript
复制
getListenerInfo().mOnClickListener = l;

位置作为钩子的 Hook 点 , 勾住该方法 ;

3、获取 View 的 ListenerInfo mListenerInfo 成员

先使用反射获取 View 组件的 getListenerInfo 方法 ;

代码语言:javascript
复制
// 获取 View 的 getListenerInfo 方法
Method getListenerInfo = null;
try {
    getListenerInfo = View.class.getDeclaredMethod("getListenerInfo");
} catch (NoSuchMethodException e) {
    e.printStackTrace();
}

设置 getListenerInfo 方法的可见性 , 之后要调用该方法 , 否则会报错 ;

代码语言:javascript
复制
// 执行所有的反射方法 , 设置成员变量 之前 , 都要设置可见性
getListenerInfo.setAccessible(true);

执行 反射获取的 getListenerInfo 方法 , 并获取 ListenerInfo mListenerInfo 成员 ;

代码语言:javascript
复制
// 执行 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 ;

代码语言:javascript
复制
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 字节码对象 ;

代码语言:javascript
复制
// ① 先根据全类名获取 ListenerInfo 字节码
Class<?> clazz = null;
try {
    clazz = Class.forName("android.view.View$ListenerInfo");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

② 获取 android.view.View$ListenerInfo 中的 mOnClickListener 成员

代码语言:javascript
复制
// ② 获取 android.view.View.ListenerInfo 中的 mOnClickListener 成员
Field field = null;
try {
    field = clazz.getField("mOnClickListener");
} catch (NoSuchFieldException e) {
    e.printStackTrace();
}

③ 设置该字段访问性, 执行所有的反射方法 , 设置成员变量 之前 , 都要设置可见性 ;

代码语言:javascript
复制
// ③ 设置该字段访问性, 执行所有的反射方法 , 设置成员变量 之前 , 都要设置可见性
field.setAccessible(true);

④ 获取 mOnClickListener 成员变量 ;

代码语言:javascript
复制
// ④ 获取 mOnClickListener 成员变量
View.OnClickListener mOnClickListener = null;
try {
    mOnClickListener = (View.OnClickListener) field.get(mListenerInfo);
} catch (IllegalAccessException e) {
    e.printStackTrace();
}

⑤ 修改 View 的 ListenerInfo 成员的 mOnClickListener 成员 , 重新设置一个自定义的 View.OnClickListener 监听器 , 在该监听器的 onClick 方法中 , 调用之前获取的 监听器的 onClick 方法 , 此外还可以在该点击方法前后注入开发者自定义的业务逻辑 ;

代码语言:javascript
复制
// ⑤ 修改 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();
}

三、完整代码示例


完整代码示例 :

代码语言:javascript
复制
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();
        }
    }
}

执行结果 :

代码语言:javascript
复制
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

四、博客资源


博客资源 :

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-06-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Android 插件化系列文章目录
    • 文章目录
    • 前言
    • 一、Hook 实现思路
    • 二、Hook 按钮点击事件
      • 1、按钮点击事件
        • 2、熟悉底层源码
          • 3、获取 View 的 ListenerInfo mListenerInfo 成员
            • 4、分析 Hook 点
              • 5、反射 ListenerInfo 并设置新的 OnClickListener 监听器
              • 三、完整代码示例
              • 四、博客资源
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档