反射指支持程序在运行状态时,都能够获取该类的内部信息,包裹其中的方法,变量等信息,并可于运行时改变方法或者其内部变量。
简单来说,如果某个系统源码中某个类,比如 Recyclerview 的 mFirst 变量,我想动态改变这个值,就可以使用 反射获取到这个值,并改变它。
java 反射的几个主要的类如下:
类名 | 用途 |
---|---|
Class类 | 编译后的Class对象 |
Constructor类 | 类的构造方法 |
Field类 | 类的成员变量 |
Method | 类的方法成员 |
Annotaion | 类的注解 |
在上面的几个类中,比如 Field 类,都有两种重用格式:
对其他类的也使用。 带有Declared修饰的方法可以反射到私有的方法,没有Declared修饰的只能用来反射公有的方法。
接着,咱们测试一个简单例子,再讲一些 Android 常常用的几个方法。下面一个简单类,要求动态改变数值。 首先写一个简单的 Bean:
public class PersonBean { private int age; public PersonBean(int age) { this.age = age; } public PersonBean() { } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } 复制代码
我们知道,Class 类是程序运行时的入口,即拿到 Class 的实例之后,咱们就可以操作。
这里介绍一些常用的。
方法 | 应用 |
---|---|
forName() | 根据类名返回类的对象 |
newInstance() | 获取类的实例 |
getSuperClass() | 获取类继承的父类名称 |
.. | .. |
上面的 newInstance() 是无参数的构造方法,但如果构造方法中有参数呢。可以Constructor 的 aClass.getDeclaredConstructor(Class...).newInstance(Object..)。 比如对上面的PersonBean 参数一个数值:
Class<?> aClass = Class.forName("com.zhengsr.androiddemo.bean.PersonBean"); Object instance = aClass.getDeclaredConstructor(int.class).newInstance(23); 复制代码
因为 PersonBean 是我们已知道,所以用Class.forName。当然你直接用 new PersonBean(23)也可以。
上面已经拿到 PersonBean 的实例,那么接着怎么拿到 age 这个变量的值,护着拿到 getAge() 的方法呢?
方法 | 用途 |
---|---|
getField(String name) | 获得某个公有的属性对象 |
getFields() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对象 |
getDeclaredFields() | 获得所有属性对象 |
get(Object obj) | 那个变量的值 |
set(Object obj, Object value) | 通过set设值 |
这里,我们的代码可以修改为:
try { Class<?> aClass = Class.forName("com.zhengsr.androiddemo.bean.PersonBean"); Object instance = aClass.getDeclaredConstructor(int.class).newInstance(23); Field field = aClass.getDeclaredField("age"); field.setAccessible(true); //通过 get 拿到数值 int age = (int) field.get(instance); //通过 set 设置值 Log.d(TAG, "zsr 拿到私有变量 age: "+age); field.set(instance,25); int age2 = (int) field.get(instance); Log.d(TAG, "zsr 拿到被改变的私有变量 age: "+age2); } catch (Exception e) { e.printStackTrace(); Log.d(TAG, "zsr error: "+e.getMessage()); } 复制代码
可以看到,数值已经被改变了。
方法 | 用途 |
---|---|
getMethod(String name, Class...<?> parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class...<?> parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
nvoke(Object obj, Object... args) | 执行对象的目标方法 |
这里,setAge 和 getAge 都是 public 方法,那么可以使用 getMethod 直接拿到。所以代码如下:
try { Class<?> aClass = Class.forName("com.zhengsr.androiddemo.bean.PersonBean"); Object instance = aClass.getDeclaredConstructor(int.class).newInstance(23); Method setAge = aClass.getMethod("setAge", int.class); setAge.setAccessible(true); setAge.invoke(instance,30); Method getAge = aClass.getMethod("getAge"); getAge.setAccessible(true); int age = (int) getAge.invoke(instance); Log.d(TAG, "zsr 拿到被改变的数值: "+age); } catch (Exception e) { e.printStackTrace(); Log.d(TAG, "zsr error: "+e.getMessage()); } 复制代码
这样,我们就讲解完了, 反射的一些基本应用。
学习到上面的反射知识之后,一些 Android 的问题就可以自己修改了。
如果你搞过缩放的 ScaleGestureDetector ,就知道,在比较大的屏幕,当缩小时,缩小到一定比例,就不能再缩放了,那是因为
mMinSpan = res.getDimensionPixelSize(com.android.internal.R.dimen.config_minScalingSpan); 复制代码
这个 限制了大小,而它又是 private 的值,这是如果我们用反射,就可以轻松修改了。
//设置 mMinSpan ,防止不能缩小 try { Field field = mScaleGesture.getClass().getDeclaredField("mMinSpan"); field.setAccessible(true); field.set(mScaleGesture,1); } catch (Exception e) { e.printStackTrace(); } 复制代码
在自己封装过 Banner 的同学应该知道,当用 ViewPager 做 Banner,与 Recyclerview 配合时,当 Recyclerview 往上划,下一次回来的时候,Banner 会直接跳到下一页,而不是有滚动的。 原因就在于 mFirstLayout 这个,因为 Recyclerview 会让 ViewPager 调用 onAttachedToWindow() 方法,所以mFirstLayout 又会被设置为 true,所以就会出现不直接跳到下一页的问题。
这里解决也简单,直接改变mFirstLayout 的值即可。
private boolean firstLayout = true; @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); //处理因为recyclerview的回收机制,导致轮播图不起作用的问题 if (getAdapter() != null) { try { Field mFirstLayout = ViewPager.class.getDeclaredField("mFirstLayout"); mFirstLayout.setAccessible(true); mFirstLayout.set(this, firstLayout); setCurrentItem(getCurrentItem()); } catch (Exception e) { e.printStackTrace(); } } } 复制代码
截屏在系统应用比较常见,但是它的类是隐藏的,方法是public 的静态方法:
其实用反射方法还是挺方便的:
public static Bitmap screenshot(int widht, int height){ Bitmap bitmap = null; try { Class<?> sClass = Class.forName("android.view.SurfaceControl"); Method method = sClass.getMethod("screenshot",int.class,int.class); method.setAccessible(true); bitmap = (Bitmap) method.invoke(sClass,widht,height); } catch (Exception e) { e.printStackTrace(); Log.d(TAG, "zsr --> screenshot: "+e.toString()); } return bitmap; } 复制代码
当你做系统管家或者其他一些管理类app,需要对后台任务删除,这个时候,可以使用 ActivityManager 中的 remvoeTask 方法,但它的方法是 @hide 的。
这个时候 也可以使用反射:
/** * 删除任务列表 * @param taskId * @return * @throws SecurityException */ public static boolean removeTask(Context context,int taskId) throws SecurityException { try { ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); Method method = activityManager.getClass().getDeclaredMethod("removeTask",int.class); method.setAccessible(true); boolean invoke = (boolean) method.invoke(activityManager, taskId); return invoke; } catch (Exception e) { return false; } } 复制代码
最后,学习后反射的基本知识之后,相信你已经不再那么困惑了。
参考: Java高级特性——反射