在andorid开发中,经常遇见在某些工具类中没有Context上下文对象时,一些系统服务的代理对象无法创建出来,举个例子:比如在源码(framework/base/graphics/java/android/graphics)路径下的Canvas.j
在andorid开发中,经常遇见在某些工具类中没有Context上下文对象时,一些系统服务的代理对象无法创建出来,举个例子:比如在源码(framework/base/graphics/java/android/graphics)路径下的Canvas.java Bitmap.javaPicture.java Paint.java 类就没有上下文对象。当时有需要调用AMS的API获取当前界面activity的名称作为判断条件去修改这些工具类中的参数,但是这几个工具类中又没有上下文对象,怎么办呢? 一般情况下,获取顶层activity名称的方法如下:
上述代码是在有上下文对象context的情况下,直接获取。 而在上面提及到的Picture.java等类中, 根本就没有上下文,怎么搞? 这个时候我们通过java反射来实现,接下来,本篇文章就来讲解一下java反射机制,并利用反射实现获取顶层activity。 2. java反射2.1 什么是反射反射(Reflection)是程序的自我分析能力,通过反射可以确定类中有哪些方法、有哪些构造方法以及有哪些成员变量,在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。 2.2 什么情况下要用反射首先java是面向对象编程的,万物皆对象, 当我们拿到这个类对象后,就可以访问类中的成员变量,调用类中的方法了,但是呢?对于private变量和方法,它的访问权限作用域只在本类中,在外部类或者继承类中,就算你创建了该类对象,也是无法直接调用的。来看下这个例子: 普通的一个User 类
如果在外部内中创建该类对象,还能直接访问私有方法和变量吗? 测试一下:
报错的两行代码注释掉了,可以把代码复制过去验证一下。 当然了,在andorid开发中,还有其他场景会使用到反射, 很多类或方法中经常加上了“@hide”注释标记,这些API是不允许在第三方应用直接调用的比如:SystemProperties类在android.os下,但这个类是隐藏的,没有系统权限(android:sharedUserId="android.uid.system")的app无法直接使用,只能通过java反射来调用。 还有 Andorid中动态代理模式 , Android插件化(Hook)这些都会用到反射,这里不一一说明,因为每个技术点都需要去深挖才能理解透彻。 2.3 反射的优缺点优点: 1.可以调用私有方法、变量 2.可以调用隐藏api接口 3.能够运行时动态获取类的实例,提高代码灵活性 缺点: 1.安全问题:反射可以获取到类对应的所有方法和属性,如果存在外部调用的情况,可能出现超出预期的调用,从而导致安全风险, 而且也破坏java面向对象编程的封装这一特性。 2.性能问题:无论是通过字符串获取Class、Method还是Field,都需要JVM的动态链接机制动态的进行解析和匹配,势必造成性能开销。每一次的反射调用都会造成Java安全机制进行额外的安全性验证,造成性能开销。反射代码使得许多JVM的运行时优化无法进行。 3. Java反射机制APIJava反射机制API主要是 java.lang.Class类 和 java.lang.reflect包。
3.1 获取Class对象
第一种:直接new出对象,然后通过对象获取class对象, 但是你想想类对象都可以直接new出来,在外部内中除了private 变量和方法不能直接访问外,都可以直接调用,我还反射干嘛?写那么多代码岂不是为难自己,没事找事,不推荐! 第二种:通过类名.class 获取字节码class对象,需要导入包,依赖性比较强,不然会编译报错 第三种:Class的静态方法,安全性强, 参数为:类包名+类名 运行结果如下:
三个都是同一个Class对象,在运行期间,一个类,只有一个Class对象产生 3.2 反射调用类构造方法方法名解释说明
我们看下Demo
反射测试类:
打印log:
通过上面的例子,总结: 1. 反射Class对象, 反射Constructor对象, 反射类实例对象 是3个不同的对象,如果刚开始不熟悉API,建议分开创建; 2.getConstructor() 和 getDeclaredConstructor(类<?>... parameterTypes)方法的区别 前者是获取public类型构造方法的Constructor对象,后者是获取全类型(public protect default private)类型的构造方法的Constructor对象 3.反射私有构造方法的时候,一定要setAccessible(true),不然会报错java.lang.IllegalAccessException错误 * true表示:禁止java语言使用时安全检查,暴力访问 4. 调用Constructor类中newInstance()这个方法时,注意newInstance没有带参数(不带参数可以不写,或写成null 都可以行) 此方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以,否则会抛出java.lang.InstantiationException异常。 3.3 反射调用类中方法
我们看看这个例子
反射测试类
打印log:
代码中基本都做了注释,通过上面的例子,总结: 1. 反射class对象, 反射Method对象 反射类实例对象 是3个不同的对象,要区分开 2. 在反射调用private方法时,一定要禁止java语言使用时安全检查,要setAccessible(true),其他修饰符方法可以不用加这句话 3. (反射Method对象).invoke(Object obj, Object ...args) 方法参数的理解 : 1. 第一个参数为 通过用反射方法构造出来的 反射类实例对象 2. 第二个可变参数为: 该方法需要传入的参数 3. 如果方法正常完成,则将返回值返回 3.4 反射类中的成员变量
我们通过Demo讲解:
反射测试类:
打印log:
通过例子,总结如下: 1. 反射class对象, 反射Field对象 反射类实例对象 是3个不同的对象,要区分开 2.在反射调用private字段时,一定要禁止java语言使用时安全检查,要setAccessible(true) 3. Field public void set(Object obj,Object value): 参数说明: 1.obj: 通过反射方法 构造出来的类对象 2.value:要为字段设置的值; 3.5 反射类中静态方法和变量怎么修改static修饰的变量呢?静态变量和方法是在类的实例化之前就进行了初始化(类的初始化阶段),静态变量和方法是从属于类本身的,跟new出来的具体对象无关,所以我们获取变量就不需要传入对象,直接传入null即可。 Demo如下:
反射测试类:
打印结果:
总结如下: 1. 对于static 变量和方法,因为它是从属于类本身,在类的实例化之前就进行了初始化,当传入obj对象参数的时候,直接传入null即可。 4. 反射在Android中的应用4.1反射实现获取顶层activity的名称如前言背景中的问题描述,有了上面的理论知识做为基础,实现代码如下:
4.2 反射调用SystemProperties中set get方法再者,比如第三方应用是无法直接调用SystemProperties中 get set 方法,我们也可以通过反射来实现, 这个工具类已经实现好,可以拿去直接用:
4.3 通过反射创建Bitmap缩略图
MediaMetadataRetriever.java 类中有个public无参构造方法, 可以通过反射方式clazz.getDeclaredConstructor().newInstance() 拿到这个 类对象 接下来,就是获取Method Field 对象 再通过invoke set 方法去修改方法 变量的值。 5. 总结反射在Andorid中开发应用的比较多,Class对象, Constructor 对象,Method 对象 Field对象各自的常用API要理解并熟练运用, 结合源码多阅读多仿写,相信你也可以carry住反射。 |
2022-04-23
2022-01-26
2021-11-15
2021-08-02
2019-12-15