Java反射以及在Android中的使用

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

一、反射概念

​ 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中使用还是挺广,后续可能会说到热修复知识点,其中也涉及到反射知识。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容