这是效果图,通过反射hook了所有的view的点击事件,有时候我们项目做完了,但是领导要求按钮不能重复点击,大家可能会使用时间来简单的过滤,比如说2s内不能重复点击啊等等!那么我们不可能所有的界面一个点击事件的加吧,这时候就可以利用反射hook住所有的onclickListener事件
先来简单复习一下反射的基础知识
第一步,怎样获取要反射的类对象?注意这里是类对象要区别类和对象的关系。有如下三种方式(采用大家都经常用的一个类Student)
第一种:Student.class;
第二种:new Student().getClass();
第三种:Class.forName(包名+类名);
前面两种都有一个致命的缺点,需要导入进来的包,才能拿到这个对象,用的最广泛的是第三种Class.forName();所以我们关键介绍第三种。
首先是Student的源码
package cms.dialog.easymining.com.lib; public class Student { private String name; private int age; public Student() {} private Student(String name){ this.name = name; } public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { System.out.println("调用getName" + name); return name; } public int getAge() { System.out.println("调用getAge" + age); return age; } public void setMName(String name) { this.name = name; System.out.println("name是" + name); } private void setNameAndAge(String name, int age) { this.age = age; this.name = name; System.out.println("setNameAndAge" + name + "--" + age); } }
/*** * 采用默认构造方法实例化对象 */ Class mClass = Class.forName("cms.dialog.easymining.com.lib.Student"); Constructor constructor = mClass.getConstructor(); Object mStu = constructor.newInstance(); /*** * 采用参数构造 */ Class mClass = Class.forName("cms.dialog.easymining.com.lib.Student"); Constructor constructor1 = mClass.getConstructor(String.class,int.class); constructor1.setAccessible(true); Object object = constructor1.newInstance("晓强",18); /*** * 调用private的有参构造方法 */ Class mClass = Class.forName("cms.dialog.easymining.com.lib.Student"); Constructor constructor = mClass.getDeclaredConstructor(String.class); constructor.setAccessible(true); Object object = constructor.newInstance("罗志祥"); 这是两种实例化反射对象的方法 中间的constructor1.setAccessible方法的意思是获取到反射的权限,如果构造方法为私有的则需要设置为true。 第二步反射调用方法 /*** * 调用无参的方法 */ Method nameMethod = mClass.getDeclaredMethod("getName"); nameMethod.setAccessible(true); nameMethod.invoke(object); /*** * 反射调用setMName方法 */ Method getNameMethod = mClass.getDeclaredMethod("setMName",String.class); getNameMethod.invoke(mStu,"吴彦祖"); /*** * 反射调用setNameAndAge */ Method setNameAndAgeMethod = mClass.getDeclaredMethod("setNameAndAge",String.class,int.class); setNameAndAgeMethod.setAccessible(true); setNameAndAgeMethod.invoke(mStu,"阿娇",18); 第三步反射设置属性的值 /*** * 反射调用属性 */ Field nameField = mClass.getDeclaredField("name"); nameField.setAccessible(true); nameField.set(mStu,"谢霆锋");
留下一些思考,如何反射内部类?如果把反射应用在android里面。
应用:反射android里面所有的onclickListener事件
package chihane.jdaddressselector.demo.hook; import android.app.Activity; import android.os.Bundle; import android.support.annotation.Nullable; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; import chihane.jdaddressselector.demo.R; public class HookListenerActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_hook_listener); Button button = (Button) this.findViewById(R.id.id_btn); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getApplicationContext(), "点击事件", Toast.LENGTH_SHORT).show(); } }); getWindow().getDecorView().post(new Runnable() { @Override public void run() { View decorView = getWindow().getDecorView(); HookClickListenerUtils.getInstance().hookDecorViewClick(decorView); } }); } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); Log.e("onAttachedToWindow", "onAttachedToWindow"); // 第二种方式 // View decorView = getWindow().getDecorView(); // HookClickListenerUtils.getInstance().hookDecorViewClick(decorView); } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); Log.e("onWindowFocusChanged", "onWindowFocusChanged中" + hasFocus); } }
package chihane.jdaddressselector.demo.hook; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; import java.lang.reflect.Field; import java.lang.reflect.Method; public class HookClickListenerUtils { private static HookClickListenerUtils mHookClickListenerUtils; private HookClickListenerUtils() { } public static HookClickListenerUtils getInstance() { synchronized ("getInstance") { if (mHookClickListenerUtils == null) { mHookClickListenerUtils = new HookClickListenerUtils(); } } return mHookClickListenerUtils; } /*** * 递归调用 * @param decorView */ public void hookDecorViewClick(View decorView) { if (decorView instanceof ViewGroup) { int count = ((ViewGroup) decorView).getChildCount(); for (int i = 0; i < count; i++) { if (((ViewGroup) decorView).getChildAt(i) instanceof ViewGroup) { hookDecorViewClick(((ViewGroup) decorView).getChildAt(i)); } else { hookViewClick(((ViewGroup) decorView).getChildAt(i)); } } } else { hookViewClick(decorView); } } public void hookViewClick(View view) { try { Class viewClass = Class.forName("android.view.View"); Method getListenerInfoMethod = viewClass.getDeclaredMethod("getListenerInfo"); if (!getListenerInfoMethod.isAccessible()) { getListenerInfoMethod.setAccessible(true); } Object listenerInfoObject = getListenerInfoMethod.invoke(view);// 反射view中的getListenerInfo方法 // 第二步:获取到view中的ListenerInfo中的mOnClickListener属性 Class mListenerInfoClass = Class.forName("android.view.View$ListenerInfo"); Field mOnClickListenerField = mListenerInfoClass.getDeclaredField("mOnClickListener"); mOnClickListenerField.setAccessible(true); // 这里为何传入的View.OnClickListener为Field.get()的值 mOnClickListenerField.set(listenerInfoObject, new HookClickListener((View.OnClickListener) mOnClickListenerField.get(listenerInfoObject))); } catch (Exception e) { e.printStackTrace(); } } public static class HookClickListener implements View.OnClickListener { private View.OnClickListener onClickListener; public HookClickListener(View.OnClickListener onClickListener) { this.onClickListener = onClickListener; } @Override public void onClick(View v) { Toast.makeText(v.getContext(), "hook住点击事件了,禽兽", Toast.LENGTH_SHORT).show(); if (onClickListener != null) { onClickListener.onClick(v); } } } }
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/id_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击事件" /> </LinearLayout>
反射这块经常用到,像cglib,hook以及一些框架底层都有使用,还有最开始接触java的时候,使用的Spring框架都有使用反射。所以这块还是挺重要的。
参考链接
java基础反射
知乎
反射在android的调用
反射activity界面的所有onclick事件
https://www.jianshu.com/p/dfc6e3989511
反射包括recyclerview里面的onclick事件
https://blog.csdn.net/zhongwn/article/details/54381337