学会使用 Java 的反射机制,能够让你在实际工作中,如虎添翼。
一 什么是反射
反射指支持程序在运行状态时,都能够获取该类的内部信息,包裹其中的方法,变量等信息,并可于运行时改变方法或者其内部变量。
简单来说,如果某个系统源码中某个类,比如 Recyclerview 的 mFirst 变量,我想动态改变这个值,就可以使用 反射获取到这个值,并改变它。
java 反射的几个主要的类如下:
类名 | 用途 |
---|---|
Class类 | 编译后的Class对象 |
Constructor类 | 类的构造方法 |
Field类 | 类的成员变量 |
Method | 类的方法成员 |
Annotaion | 类的注解 |
在上面的几个类中,比如 Field 类,都有两种重用格式:
- getField : 表示获取某个共有对象
- getDeclaredField:表示获取所有对象,包括 private 方法
对其他类的也使用。带有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 的实例之后,咱们就可以操作。
2.1、Class 类常用方法
这里介绍一些常用的。
方法 | 应用 |
---|---|
forName() | 根据类名返回类的对象 |
newInstance() | 获取类的实例 |
getSuperClass() | 获取类继承的父类名称 |
.. | .. |
2.2、Constructor 方法
上面的 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)也可以。
2.3、Method 和 Field 类方法
上面已经拿到 PersonBean 的实例,那么接着怎么拿到 age 这个变量的值,护着拿到 getAge() 的方法呢?
Field
方法 | 用途 |
---|---|
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());
}
复制代码
可以看到,数值已经被改变了。
Method
方法 | 用途 |
---|---|
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 实战
学习到上面的反射知识之后,一些 Android 的问题就可以自己修改了。
3.1 修改缩放值
如果你搞过缩放的 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();
}
复制代码
3.2 Banner 与 Recyclerview 结合不滚动的问题
在自己封装过 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();
}
}
}
复制代码
如果你有系统权限,但是需要调用一些隐藏的 API 方法。这个时候也可以使用反射。
3.3 截屏
截屏在系统应用比较常见,但是它的类是隐藏的,方法是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;
}
复制代码
3.4 删除任务
当你做系统管家或者其他一些管理类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高级特性——反射
来源:oschina
链接:https://my.oschina.net/u/4293376/blog/3223040