天猫网站的建设,公共交易中心招标网,同济建筑人才网,自己做网站维护挣钱吗延续之前的MonkeyLei#xff1a;Android-模块化、组件化、插件化、热修复-插件化-起个头#xff0c;我们复习下里面的关于反射和动态代理点的知识。然后尝试简单了解下Hook...看之前文章#xff0c;记得多复习下反射代理#xff0c;比如使用这些....#xff1a;public cla… 延续之前的MonkeyLeiAndroid-模块化、组件化、插件化、热修复-插件化-起个头我们复习下里面的关于反射和动态代理点的知识。然后尝试简单了解下Hook... 看之前文章记得多复习下反射代理比如使用这些.... public class Proxy
extends Object
implements Serializable
Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.To create a proxy for some interface Foo:InvocationHandler handler new MyInvocationHandler(...);Class proxyClass Proxy.getProxyClass(Foo.class.getClassLoader(), new Class[] { Foo.class });Foo f (Foo) proxyClass.getConstructor(new Class[] { InvocationHandler.class }).newInstance(new Object[] { handler });or more simply:Foo f (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),new Class[] { Foo.class },handler); 多多实践。之后我们尝试建一个Android工程但是先用Java main方法来做相关Hook测试。后面再尝试结合Hook Android的东西.SO..开始吧... Then看下Hook基本介绍吧...一、什么是 Hook 技术Hook 技术又叫做钩子函数在系统没有调用该函数之前钩子程序就先捕获该消息钩子函数先得到控制权这时钩子函数既可以加工处理改变该函数的执行行为还可以强制结束消息的传递。简单来说就是把系统的程序拉出来变成我们自己执行代码片段。要实现钩子函数有两个步骤1. 利用系统内部提供的接口通过实现该接口然后注入进系统特定场景下使用2.动态代理使用所有场景二、Hook 技术实现的步骤Hook 技术实现的步骤也分为两步1.找到 hook 点Java 层该 hook 点必须满足以下的条件需要 hook 的方法所属的对象必须是静态的因为我们是通过反射来获取对象的我们获取的是系统的对象所以不能够 new 一个新的对象必须用系统创建的那个对象所以只有静态的才能保证和系统的对象一致。2.将 hook 方法放到系统之外执行放入我们自己的逻辑 我就以我觉得的比较简单的方式来理解一下Hook我要实现的功能是 1. 继承某个可以继承的对象然后重写某个方法添加中间处理比如验证等。完事了既可以用super调用父类的方法. 2. 然后用这个新的对象变量替换掉原有的变量实现对象变量的动态替换 3. 重点也就是Field、Proxy的基本使用 直接看测试代码 - HookTestMain.java package com.skl.hooktest;import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class HookTestMain {private static class MyView{// 通过反射替换对象的该对象变量private MyTestView myTestView; // new MyTestView();static class MyTestView{public void test(){System.out.println(啊啊啊);}}// 通过Proxy生成代理对象然后替换该变量private ProcInterface other2;}/*** 重写旧有的某个方法作为新的对象注入替换掉MyView的myTestView变量*/private static class Other extends MyView.MyTestView{Overridepublic void test(){System.out.println(BBBBB);super.test();}}/*** Proxy生成代理对象必须是某个接口*/private interface ProcInterface{void test();}public static void main(String[] args){try {// 创建一个对象 - 我们即将替换这个对象的某个变量达到替换方法的效果// --我是不是可以想象一下如果要做方法热修复是不是也可以呢// --但是这个是限于我们有该对象的前提如果是其他情况可能就需要你去找到某个对象 它的某个方法进而实现替换MyView myView new MyView();// myView.myTestView.test();// 这是内部静态类类的表示方法Field field MyView.class.getDeclaredField(myTestView);field.setAccessible(true);// System.out.println(field.getName());// 用新的对象替换掉myView对象内部的对象变量Other other new Other();field.set(myView, other);// 或者用Proxy方法生成代理对象这种方式下代理的对象必须实现某个接口Field field2 MyView.class.getDeclaredField(other2);field2.setAccessible(true);ProcInterface other2 (ProcInterface) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{ProcInterface.class}, new InvocationHandler() {Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(这是代理的对象呀 method method);return null;}});// 替换对象变量然后运行field2.set(myView, other2);myView.other2.test();// 重新运行该方法达到替换方法的效果!myView.myTestView.test();} catch (NoSuchFieldException e) {System.out.println(e.getMessage());} catch (IllegalAccessException e) {System.out.println(e.getMessage());}//**************此时我们尝试加载Apk文件然后拿到补丁的方法来修复上面对象的方法}
}
解释步骤 1. 定义一个MyView类该类包含一个MyTestView类然后定义了一个MyTestView对象变量myTestView。 private static class MyView{// 通过反射替换对象的该对象变量private MyTestView myTestView; // new MyTestView();static class MyTestView{public void test(){System.out.println(啊啊啊);}}} 2. 定一个类Other继承MyTestView然后重新它的test方法同时加入自己的操作 /*** 重写旧有的某个方法作为新的对象注入替换掉MyView的myTestView变量*/private static class Other extends MyView.MyTestView{Overridepublic void test(){System.out.println(BBBBB);super.test();}} 3. 测试流程首先创建一个MyView的对象然后我们将针对这个对象进行反射操作Hook掉它的myTestView变量 public static void main(String[] args){try {// 创建一个对象 - 我们即将替换这个对象的某个变量达到替换方法的效果// --我是不是可以想象一下如果要做方法热修复是不是也可以呢// --但是这个是限于我们有该对象的前提如果是其他情况可能就需要你去找到某个对象 它的某个方法进而实现替换MyView myView new MyView();// 这是内部静态类类的表示方法Field field MyView.class.getDeclaredField(myTestView);field.setAccessible(true);// 用新的对象替换掉myView对象内部的对象变量Other other new Other();field.set(myView, other);// 重新运行该方法达到替换方法的效果!myView.myTestView.test();} catch (NoSuchFieldException e) {System.out.println(e.getMessage());} catch (IllegalAccessException e) {System.out.println(e.getMessage());}} 这样我们就替换掉了这个对象变量插入了我们自己的操作比如日志统计。有时候我们再不想改变原来代码的基础上可以这么设计当然还可以通过静态代理或者动态代理的方式实现。。 这里我们就想借此了解下Hook的思想。。。可能的大概的这样一个概念....当我们真的去深入这块的时候我们会发现有更复杂的操作和逻辑。 比如有些情况下你不能知道系统的某个静态内部类你没办法继承重写。那么你只能一步步的Hook到最终需要替换的目标对象然后通过Proxy.newProxyInstance创建动态代理对象动态代理类需要实现某个接口 4. 上面步骤我们是重新对象的方式。然后开头的全部代码的其他部分我们是采用动态代理的方式然后反射来实现的。 重点就是Proxy的使用 ProcInterface other2 (ProcInterface) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{ProcInterface.class}, new InvocationHandler() {Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(这是代理的对象呀 method method);return null;}}); 至此我们就在Java main里面实践了一把。。 And 我们还可以利用这个思想在Android里面实现类似的操作比如Button按钮的点击事件的Hook... 开始之前有必要了解下setOnClickListener(new View.OnClickListener() {}的内部逻辑不然你不知道应该Hook哪个目标对象. 这里我直接粘贴出来几个重要的源码逻辑。。。 -- 可以看到我们最终要实现mOnClickListener接口变量的替换而这个是在ListenerInfo里面所以最终是替换对象的 ListenerInfo变量里面的mOnClickListener变量。 记住我们的操作都是针对Button button控件的不能单独New一个实例出来你那样不是Hook的该button的变量。 // Hook一下这个点击事件增加点预处理// 1.跟踪下setOnClickListener方法都做了啥关键的监听设置在哪里public void setOnClickListener(Nullable OnClickListener l) {if (!isClickable()) {setClickable(true);}getListenerInfo().mOnClickListener l;}static class ListenerInfo {/*** Listener used to dispatch click events.* This field should be made private, so it is hidden from the SDK.* {hide}*/public OnClickListener mOnClickListener;}// 1.1 所以我们最终要实现替换getListenerInfo()-ListenerInfo的mOnClickListener为我们自己代理的监听方法 a. ListenerInfo是静态内部类我们拿不到只能通过getListenerInfo获取。所以我们通过反射拿 Method method View.class.getDeclaredMethod(getListenerInfo);method.setAccessible(true);Log.e(test, method method.getName());// 获取Button的ListenerInfo对象mListenerInfoObject mListenerInfo method.invoke(button); 记住是button的b. 拿到Object mListenerInfo后还需要获取ListenerInfo的mOnClickListener变量同样也只能通过反射获取 // 内部类需要使用$分隔Class? classListenerInfo Class.forName(android.view.View$ListenerInfo);// 获取内部Field mOnClickListenerField field classListenerInfo.getDeclaredField(mOnClickListener);// 然后获取Button的ListenerInfo对象mListenerInfo的mOnClickListener变量// --这就是真正的拿到了Button的监听回调View.OnClickListener的实例对象final View.OnClickListener onClickListener (View.OnClickListener) field.get(mListenerInfo); 记住是上一步获取的对象Object mListenerInfo的mOnClickListener c. 然后上一步的Field field classListenerInfo.getDeclaredField(mOnClickListener);获取的field我们就可以用set方法替换调用对象mListenerInfo的mOnClickListener变量: 之前记得创建一个动态代理对象: // 然后准备替换为我们自己的点击事件// 1. 创建代理点击对象然后替换 (这里继承接口实现一个类也可以)Object proxyOnClickListener Proxy.newProxyInstance(this.getClassLoader(),new Class[]{View.OnClickListener.class},new InvocationHandler() {Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Toast.makeText(MainActivity.this,你点击我嘛我很烦的,Toast.LENGTH_SHORT).show();}});// 2. 然后替换掉Button的点击事件field.set(mListenerInfo, proxyOnClickListener);// End.当点击的时候就会执行我们代理对象的invoke方法。然后你可以在invoke里面增加自己额外的操作。// --甚至你啥都不做就这么让点击事件失效了哈哈 当我们点击的时候就会走动态代理对象的invoke方法 d. 我们Hook了这个点击事件插入了我们自己的处理但是我们不能干扰其他的逻辑所以我们invoke里面还是需要执行 被我们替换的mOnClickListener点击事件的方法回调所以我们要增加如下处理 当然如果你想完全拦截自己做一些事情那你就不要这个处理了。 d. 到此我们基本上就搞定了这个东东。。但是我有个疑问我们只能针对一个控件实例如果多个控件都想要这样的操作应该如何搞 - - 控件的点击事件封装到Base页面统一Hook还是说有其他的方式再学习看看吧... 完整代码MainActivity.java package com.skl.hooktest;import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/**
*Author: hl
*Date: created at 2019/10/16 19:19
*Description: https://www.jianshu.com/p/74c12164ffca?tdsourcetags_pcqq_aiomsg* 文章讲的蛮好的不过新手理解还是需要先搞搞反射这些知识才行。其实代理还好。你就是要知道怎么反射调方法获取字段设置字段等操作.* 慢慢熟悉吧争取多屡屡逻辑这些
*/
public class MainActivity extends AppCompatActivity {Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button button findViewById(R.id.hookClick);button.setOnClickListener(new View.OnClickListener() {Overridepublic void onClick(View v) {Log.e(test, 点击事件继续);}});// Hook一下这个点击事件增加点预处理// 1.跟踪下setOnClickListener方法都做了啥关键的监听设置在哪里// public void setOnClickListener(Nullable OnClickListener l) {// if (!isClickable()) {// setClickable(true);// }// getListenerInfo().mOnClickListener l;// }// static class ListenerInfo {// /**// * Listener used to dispatch click events.// * This field should be made private, so it is hidden from the SDK.// * {hide}// */// public OnClickListener mOnClickListener;// }// 1.1 所以我们最终要实现替换getListenerInfo()-ListenerInfo的mOnClickListener为我们自己代理的监听方法try {Method method View.class.getDeclaredMethod(getListenerInfo);method.setAccessible(true);Log.e(test, method method.getName());// 获取Button的ListenerInfo对象mListenerInfoObject mListenerInfo method.invoke(button);// 内部类需要使用$分隔Class? classListenerInfo Class.forName(android.view.View$ListenerInfo);// 获取内部Field mOnClickListenerField field classListenerInfo.getDeclaredField(mOnClickListener);// 然后获取Button的ListenerInfo对象mListenerInfo的mOnClickListener变量// --这就是真正的拿到了Button的监听回调View.OnClickListener的实例对象final View.OnClickListener onClickListener (View.OnClickListener) field.get(mListenerInfo);// 然后准备替换为我们自己的点击事件// 1. 创建代理点击对象然后替换 (这里继承接口实现一个类也可以)Object proxyOnClickListener Proxy.newProxyInstance(this.getClassLoader(),new Class[]{View.OnClickListener.class},new InvocationHandler() {Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Toast.makeText(MainActivity.this,你点击我嘛我很烦的,Toast.LENGTH_SHORT).show();// 为了保证其点击逻辑除了插入我们的操作我们还是要处理正常的调用逻辑return method.invoke(onClickListener, args);}});// 2. 然后替换掉Button的点击事件field.set(mListenerInfo, proxyOnClickListener);// End.当点击的时候就会执行我们代理对象的invoke方法。然后你可以在invoke里面增加自己额外的操作。// --甚至你啥都不做就这么让点击事件失效了哈哈} catch (NoSuchMethodException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();} catch (NoSuchFieldException e) {e.printStackTrace();}}
}要不先到这里肚子饿了。。。 多多熟悉反射代理等知识啊哈。。。完事了多练习吧。。 趁机可以了解下热修复原理以及Hook实现不用注册manifest启动页面的预备知识Dex热修复原理 Android的热修复 - 建议不要直接照着写。 基础不懂的知识多联系再整。 争取你去整的时候心里还是对流程清楚了解才行。。同时学习实践尽量带上自己的逻辑和想法去实践多扩展。。。 附录https://blog.csdn.net/qq_30207527/article/details/85169582