Java反射以及在Android中的使用

作者: EvanZch | 来源:发表于2019-11-23 17:07 被阅读0次

    文章内容尽可能的详细,方便自己后续查阅。

    一、反射概念

    ​ java反射是指,在运行状态中,对于任意一个类,都能知道这个类的所有属性及方法,对于任何一个对象,都能调用他的任何一个方法和属性,这种动态获取新的及动态调用对象的方法的功能叫做反射.

    二、Class 类 (java.lang.Class)

    ​ Java中万物皆为对象,每个类也是一个对象,每个类的java文件在编译的时候会产生同名的.class文件,这个.class文件包含了这个java类的元数据信息,包括成员变量,属性,接口,方法等,生成.class文件的同时会产生其对应的Class对象,并放在.class文件的末尾。当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象。

    三、在Android中的应用场景

    在Android 中,处于安全考虑,google会对系统的某些方法使用@hide或者使用Private修饰,导致我们没办法正常调用此类方法,但是有些场景我们又需要使用这些方法,这个时候就可以直接通过反射来调用修改。

    比如 Android 中 StorageManager 类中的 getVolumePaths() 方法,该方法为隐藏方法,没办法正常调用,但是在实际使用中我们也可能用上,如果你有系统权限,那你就可以像 Android SD卡及U盘插拔状态监听及内容读取 这样为所欲为,如果没有权限,那你就可以通过反射来实现了,Demo放在最后,认真看完,你也会:grin:

    image

    在Android9.0以后,Google对反射也做了限制,Google对反射的方法做了划分,并针对不同的等级的隐藏方法做了反射限制。

    白名单:SDK
    浅灰名单:仍可以访问的非 SDK 函数/字段。
    深灰名单:
    对于目标 SDK 低于 API 级别 28 的应用,允许使用深灰名单接口。
    对于目标 SDK 为 API 28 或更高级别的应用:行为与黑名单相同
    黑名单:受限,无论目标 SDK 如何。 平台将表现为似乎接口并不存在。 例如,无论应用何时尝试使用接口,平台都会引发 NoSuchMethodError/NoSuchFieldException,即使应用想要了解某个特殊类别的字段/函数名单,平台也不会包含接口。

    名单列表

    四、具体使用

    我们先新建一个Person类和Man类,需要注意一下他们的继承关系及成员变量和方法的修饰符 (正常情况下不会这么写,我方便后面方法说明,就刻意的给了不同的修饰符

    Person.java:

    public class Person {
    
        public int age;
        private String name;
    
        public Person() {
        }
    
        private Person(int age, String name) {
            this.age = age;
            this.name = name;
        }
    
        private Person(int age) {
            this.age = age;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    

    Man.java:

    public class Man extends Person {
    
        private String address;
        public int phoneNum;
    
        public Man() {
        }
    
        public Man(String address) {
            this.address = address;
        }
    
        private Man(int phoneNum, String address) {
            this.address = address;
            this.phoneNum = phoneNum;
        }
    
        public String getAddress() {
            return address;
        }
    
        private void setAddress(String address) {
            this.address = address;
        }
    
        public int getPhoneNum() {
            return phoneNum;
        }
    
        private void setPhoneNum(int phoneNum) {
            this.phoneNum = phoneNum;
        }
    }
    

    3.1、 获取类的Class对象

    前面说到过,java虚拟机(JVM)会根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值,所以,我们要用到反射,就首先要拿到这个类的Class对象,获取Class对象有下面三种方法

    1、直接通过类.class,获取其Class对象
    Class manClass = Man.class;
    
    2、通过Class的forName方法

    该方法如果路径找不到会抛出 ClassNotFoundException 异常

    try {
         // forName 中要传入完整路径
         manClass1 = Class.forName("com.evan.reflection.Man");
       } catch (ClassNotFoundException e) {
          e.printStackTrace();
      }
    
    3、通过类实例对象 getClass() 方法
    Man main = new Man();
    Class<? extends Man> manClass2 = main.getClass();
    

    通过打印,可以看到三者获取结果一致。

    image

    拿到其Class对象,可以直接通过 newInstance() 方法,获取到类的实例对象,并对其操作

    Man man = (Man) manClass.newInstance();
    man.setPhoneNum(123);
    int phoneNum = man.getPhoneNum();
    System.out.println("getPhoneNum=" + phoneNum);
    

    结果:

    image

    你可能有疑惑,绕来绕去又绕回来了,干嘛不直接new一个对象,非要绕一大圈,其实我们这里最主要的是拿到其Class对象,然后用Class对象去执行私有方法或设置私有变量。

    3.2、通过反射获取构造方法

    先贴结论,可以跟着后面的demo看结论,可能就会比较清晰。

    构造方法只针对本类

    方法 说明
    getConstructors() 获取当前类公有构造方法
    getConstructor(Class<?>... parameterTypes) 获取参数类型匹配的公有构造方法
    getDeclaredConstructors() 获取当前类所有构造方法,包括私有。
    getDeclaredConstructor(Class<?>... parameterTypes) 获取所有参数类型匹配的构造方法(公有+私有

    我这里贴心的再贴一下我们写的Man类的构造方法,可以看到一个公有的无参和一个公有的单参构造方法,一个私有的双参构造方法。

    public Man() {
    }
    
    public Man(String address) {
        this.address = address;
    }
    
    private Man(int phoneNum, String address) {
        this.address = address;
        this.phoneNum = phoneNum;
    }
    
    1、Constructor<?>[] getConstructors()

    获取当前类中所有public修饰的构造方法。

        Constructor[] constructors = manClass.getConstructors();
            for (Constructor c : constructors) {
             System.out.println("getConstructors--" + c);
        }
    

    结果:

    image

    从打印的结果可以看到,我们拿到了一个无参的构造方法和一个String参数的构造方法,而这两个构造方法刚好是用Public修饰的。

    2、Constructor<?>[] getDeclaredConstructors()

    拿到该类的所有构造方法,不管修饰符是啥。

        Constructor[] declaredConstructors = manClass.getDeclaredConstructors();
            for (Constructor c : declaredConstructors) {
                System.out.println("getDeclaredConstructors--" + c);
            }
    

    结果:

    image

    不用说了吧,全部都拿到了!!

    3、Constructor<T> getConstructor(Class<?>... parameterTypes)

    根据参数类型Class对象,只能获取指定公有构造方法。

        try {
            // getConstructor(String.class) 传入对应构造方法的参数类型Class对象
            Constructor constructorPublic = manClass.getConstructor(String.class);
            System.out.println("getConstructor(String.class)=" + constructorPublic);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    

    结果:

    image

    该方法只能获取公有构造,如果我们去获取私有的构造方法就会就会抛 NoSuchMethodException 异常。

    4、Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

    获取指定参数Class对象的构造方法,无限制,获取公有或者私有都可以。

      try {
             constructorPublicDeclared = manClass.getDeclaredConstructor(String.class);
             Constructor constructorPrivateDeclared2 = manClass.getDeclaredConstructor(int.class, String.class);
             System.out.println("getDeclaredConstructor(String.class)--------" + constructorPublicDeclared);
             System.out.println("getDeclaredConstructor(int.class, String.class)--------" + constructorPrivateDeclared2);
         } catch (NoSuchMethodException e) {
                 e.printStackTrace();
          }
    

    结果:

    image
    5、创建对象

    拿到了构造方法我们直接调用 newInstance() 方法并传入对应参数可以拿到类对象。

    try {
        // 调用 newInstance 传入对应参数,获取Man实例并进行操作。
        Man man = (Man) constructorPublicDeclared.newInstance("Test");
        String address = man.getAddress();
        System.out.println("newInstance   address=" + address);
        } catch (InvocationTargetException e) {
           e.printStackTrace();
     }
    
      // 结果
      newInstance   address=Test
    

    3.2 获取方法

    Method 类

    方法 说明
    getMethods() 获取类本身及其父类所有公有方法
    getMethod(String name, Class<?>... parameterTypes) 获取类本身及其父类通过方法名参数类型指定的公有方法
    getDeclaredMethods() 获取类本身所有方法
    getDeclaredMethod(String name, Class<?>... parameterTypes) 通过类本身通过方法名及参数类型获取本类指定的方法,无限制
    1、Method[] getMethods()

    获取当前类及其父类Public方法

            Method[] methods = manClass.getMethods();
            for (Method method : methods) {
                System.out.println("getMethods--->" + method);
            }
    

    结果:

    image

    可以看到获取的全是Public修饰的方法,不光获取了自身类,其父类Person类的公有方法一样打印出来了,你们可能会说Object类是什么鬼?Object类位于java.lang包中,是所有java类的父类。

    2、Method[] getDeclaredMethods()

    获取本类的所有方法。

            Method[] declaredMethods = manClass.getDeclaredMethods();
            for (Method declareMethod : declaredMethods) {
                System.out.println("getDeclaredMethods--->" + declareMethod);
            }
    

    结果:

    image
    3、Method getMethod(String name, Class<?>... parameterTypes)

    获取本类及父类指定方法名和参数Class对象的方法,比如获取其父类的setName方法和自己的getPhoneNum方法

    public void setName(String name)

    public int getPhoneNum()

    try {
        // 获取Person父类SetName方法
         Method setName = manClass.getMethod("setName", String.class);
        // 获取自己 getPhoneNum 方法
         Method getPhoneNum = manClass.getMethod("getPhoneNum");
        System.out.println("getMethod(String name, Class<?>... parameterTypes)--->" + setName);
        System.out.println("getMethod(String name, Class<?>... parameterTypes)--->" + getPhoneNum);
      } catch (NoSuchMethodException e) {
          e.printStackTrace();
      }
    

    结果:

    image

    如果你不信邪,去获取私有方法,会报错 NoSuchMethodException

    4、Method getDeclaredMethod(String name, Class<?>... parameterTypes)

    获取本类指定方法名和参数Class对象的方法,无限制

            try {
                Method setAddress = manClass.getDeclaredMethod("setAddress", String.class);
                Method getAddress = manClass.getDeclaredMethod("getAddress");
                System.out.println("getDeclaredMethod--->" + setAddress);
                System.out.println("getDeclaredMethod--->" + getAddress);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
    

    结果:

    image
    5、方法调用

    拿到方法后,方法调用使用:

    Object invoke(Object obj, Object... args)
    

    就拿上面的例子

    // 私有方法赋予权限
    setAddress.setAccessible(true);
    setAddress.invoke(manClass.newInstance(), "重庆市");
    

    setAccessible 当我们需要对非公有方法进行操作的时候,需要先调用此方法赋予权限,不然也会抛异常

    3.3 获取成员变量

    Field 类

    方法 说明
    getFields() 获取类本身及其父类所有公有成员变量
    getField(String name) 获取类本身及其父类指定的公有成员变量
    getDeclaredFields() 获取类本身所有成员变量(私有,公有,保护)
    getDeclaredField(String name) 获取类本身指定名字的成员变量
    1、Field[] getFields()

    获取本类及父类所有公有变量

            Field[] fields = manClass.getFields();
            for (Field field : fields) {
                System.out.println("getFields--->" + field);
            }
    

    结果:

    image
    2、Field[] getDeclaredFields()

    获取本类所有成员变量

            Field[] fields = manClass.getDeclaredFields();
            for (Field field : fields) {
                System.out.println("getDeclaredFields--->" + field);
            }
    

    结果:

    image
    3、Field getField(String name)

    获取本类或者父类指定的成员变量

            try {
                Field age = manClass.getField("age");
                Field phoneNum = manClass.getField("phoneNum");
                System.out.println("getField--->" + age);
                System.out.println("getField--->" + phoneNum);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
    

    结果:

    image
    4、Field getDeclaredField(String name)

    获取本类指定的成员变量,无限制

            try {
                Field address = manClass.getDeclaredField("address");
                Field phoneNum = manClass.getDeclaredField("phoneNum");
                System.out.println("getField--->" + address);
                System.out.println("getField--->" + phoneNum);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
    

    结果:

    image
    5、成员变量赋值
    // 私有成员变量要赋予权限
    address.setAccessible(true);
    address.set(manClass.newInstance(), "重庆市");
    phoneNum.set(manClass.newInstance(), 023);
    

    五、实战

    啰嗦这么久,直接拿文章开头说的那个方法动手,再看一下这个隐藏方法。

    StorageManager.java ——> getVolumePaths

    getVolumePaths() : 返回全部存储卡路径, 包括已挂载的和未挂载的

    image

    虽然这个方法是 Public 修饰的,但是使用了@hide注解,我们直接使用 StorageManager 对象是找不到这个方法的,如下:

    image

    5.1、使用反射获取

    1、拿到 StorageManagerClass对象
    StorageManager sm = (StorageManager)
                    this.getSystemService(Context.STORAGE_SERVICE);
    Class<? extends StorageManager> storageManagerClass = sm.getClass();
    
    2、反射获取 getVolumePaths方法
    // getVolumePaths 是用public修饰,所以这里getMethod和getDeclaredMethod都可以
    // getVolumePaths 方法没有参数,可以不填
    Method method = storageManagerClass.getMethod("getVolumePaths");
    
    3 、方法调用

    如果方法非public修饰,还需要 使用 setAccessible(true) 赋予权限

    String[] paths = (String[]) method.invoke(sm, null);
    

    完整代码:

     /**
         * 反射调用  StorageManager ——>  getVolumePaths 方法
         */
        public void getVolumePaths() {
            StorageManager sm = (StorageManager)
                    this.getSystemService(Context.STORAGE_SERVICE);
            Class<? extends StorageManager> storageManagerClass = sm.getClass();
            try {
                // 反射拿到getVolumePaths方法
                Method method =
                        storageManagerClass.getDeclaredMethod("getVolumePaths");
                String[] paths = (String[]) method.invoke(sm, null);
                for (String path : paths) {
                    Log.d(TAG, "getVolumePaths: path=" + path);
                }
            } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
                e.printStackTrace();
                Log.e(TAG, "getVolumePaths: " + e.getLocalizedMessage(), e);
            }
        }
    

    结果:

    image

    六、结论

    其实咋一看,反射还是很简单,主要区分私有和公有,以及获取的是本类的还是本类加父类,当然,还有更多方法,比如你可以通过获取的方法,获取它的修饰符,变量等等,方法类似,本文内容还是比较浅,敲一遍基本就知道是咋回事了,看的话可能有点绕,建议自己动手敲一遍,还有其其它获取反射中的知识点你也可以去拓展一下,反射在Android中使用还是挺广,后续可能会说到热修复知识点,其中也涉及到反射知识。

    相关文章

      网友评论

        本文标题:Java反射以及在Android中的使用

        本文链接:https://www.haomeiwen.com/subject/xxihwctx.html