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

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

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




目录
相关文章
|
4天前
|
测试技术 Android开发
Android中使用performClick触发点击事件
Android中使用performClick触发点击事件
|
18天前
|
安全 JavaScript 前端开发
kotlin开发安卓app,JetPack Compose框架,给webview新增一个按钮,点击刷新网页
在Kotlin中开发Android应用,使用Jetpack Compose框架时,可以通过添加一个按钮到TopAppBar来实现WebView页面的刷新功能。按钮位于右上角,点击后调用`webViewState?.reload()`来刷新网页内容。以下是代码摘要:
|
19天前
|
JavaScript Java Android开发
kotlin安卓在Jetpack Compose 框架下跨组件通讯EventBus
**EventBus** 是一个Android事件总线库,简化组件间通信。要使用它,首先在Gradle中添加依赖`implementation &#39;org.greenrobot:eventbus:3.3.1&#39;`。然后,可选地定义事件类如`MessageEvent`。在活动或Fragment的`onCreate`中注册订阅者,在`onDestroy`中反注册。通过`@Subscribe`注解方法处理事件,如`onMessageEvent`。发送事件使用`EventBus.getDefault().post()`。
|
19天前
|
JavaScript 前端开发 Android开发
kotlin安卓在Jetpack Compose 框架下使用webview , 网页中的JavaScript代码如何与native交互
在Jetpack Compose中使用Kotlin创建Webview组件,设置JavaScript交互:`@Composable`函数`ComposableWebView`加载网页并启用JavaScript。通过`addJavascriptInterface`添加`WebAppInterface`类,允许JavaScript调用Android方法如播放音频。当页面加载完成时,执行`onWebViewReady`回调。
|
23天前
|
缓存 Android开发 Kotlin
【安卓app开发】kotlin Jetpack Compose框架 | 先用OKhttp下载远程音频文件再使用ExoPlayer播放
使用 Kotlin 的 Jetpack Compose 开发安卓应用时,可以结合 OkHttp 下载远程音频文件和 ExoPlayer 进行播放。在 `build.gradle` 添加相关依赖后,示例代码展示了如何下载音频并用 ExoPlayer 播放。代码包括添加依赖、下载文件、播放文件及简单的 Compose UI。注意,示例未包含完整错误处理和资源释放,实际应用需补充这些内容。
|
24天前
|
存储 Android开发
安卓app,MediaPlayer播放本地音频 | 按钮控制播放和停止
在Jetpack Compose中,不直接操作原生Android组件如`Button`和`MediaPlayer`,而是使用Compose UI构建器定义界面并结合ViewModel管理音频播放逻辑。以下示例展示如何播放本地音频并用按钮控制播放/停止:创建一个`AudioPlayerViewModel`管理`MediaPlayer`实例和播放状态,然后在Compose UI中使用`Button`根据`isPlaying`状态控制播放。记得在`MainActivity`设置Compose UI,并处理相关依赖和权限。
|
25天前
|
前端开发 JavaScript 测试技术
|
14天前
|
Java Linux API
微信API:探究Android平台下Hook技术的比较与应用场景分析
微信API:探究Android平台下Hook技术的比较与应用场景分析
|
7天前
|
安全 Android开发 iOS开发
探索安卓与iOS开发的差异:平台特性与用户体验的深度对比
在移动应用开发的广阔天地中,安卓和iOS两大平台各占半壁江山。本文旨在通过数据驱动的分析方法,深入探讨这两大操作系统在开发环境、用户界面设计及市场表现等方面的差异。引用最新的行业报告和科研数据,结合技术专家的观点,本文将提供对开发者和市场分析师均有价值的洞见。