阅读 305

Java 反射理解以及Android实战

学会使用 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());
        }
复制代码

r

这样,我们就讲解完了, 反射的一些基本应用。

三、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高级特性——反射