术语俗话 --- 什么是反射
我用一个非常通俗的比喻来解释“反射”这个概念。
通俗解释:拆解和操控“盲盒”
想象一下,你有一个密封的、不透明的“智能盲盒”(这代表一个编译后的Java类)。这个盲盒有自己的规则:
-
正常情况下:你只能通过盒子上官方预留的几个按钮(公共方法,
public methods
)和插槽(公共属性,public fields
)来与它互动。你知道按“A按钮”它会播放音乐,往“B插槽”里放硬币它会吐出糖果。这是常规的、安全的用法。 -
使用反射:现在,你得到了一副特殊的“透视与操控手套”。戴上这副手套后,你可以:
-
X光透视:直接看穿盲盒的内部结构,不管它是否密封。你能看到里面所有的零件、线路、甚至隐藏的、没有标记的按钮和插槽(即类的私有方法
private methods
和私有属性private fields
)。 -
隔空操作:你可以绕过盒子官方预留的按钮,直接用手套去拨动里面那些本不该被触碰的、隐藏的开关和线路。
-
总结一下:
反射(Reflection) 就是在程序运行的过程中,对于任何一个已经加载到内存的类,都能够:
-
知道:这个类有哪些方法、属性、构造方法等(透视)。
-
访问和调用:这个类的任何方法和属性,甚至是
private
的(隔空操作)。
它打破了代码的封装性,让你可以在运行时动态地分析和操作类。
联系到你提供的代码:
你的代码就是一个完美的使用反射的例子。它为什么要用反射?因为Android系统不希望普通应用直接调用PackageParser
等内部系统类的隐藏方法(这些方法没有包含在公开的SDK里,是@hide
的)。
但你的代码又想做到签名验证(可能用于防止破解),所以它必须“走后门”:
// 1. “透视”并找到那个隐藏的类(盲盒)
Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser$Package");
// 2. 找到那个隐藏的、不对外公开的构造函数(找到隐藏的按钮)
Constructor<?> constructor = packageParserClass.getDeclaredConstructor(String.class);
// 3. 强制让这个构造函数变得可以访问(获得操作权限)
constructor.setAccessible(true);
// 4. 创建这个类的实例(终于造出了这个盲盒)
Object packageObj = constructor.newInstance(getContext().getPackageName());
// 5. 同样用反射的方式,找到并调用一个内部方法 `collectCertificates`(去拨动里面的隐藏开关)
Method collectCertificatesMethod = parserClass.getDeclaredMethod("collectCertificates", packageParserClass, File.class, int.class);
collectCertificatesMethod.invoke(null, packageObj, new File(apkPath), 0x40); // 调用它!
// 6. 最后,再用反射去获取内部的签名信息(拿到盲盒里的核心机密)
Field signaturesField = packageParserClass.getDeclaredField("mSignatures");
Signature[] signatures = (Signature[]) signaturesField.get(packageObj);
所以,在这段代码里,反射的核心目的就是:突破Android系统的限制,去访问和调用它不想让你直接使用的内部API,从而获取到应用的签名信息进行验证。
需要注意的是:大量使用反射来访问系统隐藏API在高版本Android上是被严格限制的,可能会导致应用无法正常运行(Google旨在维护系统的稳定性和安全性)。代码中为不同Android版本写了不同逻辑,也正是为了应对这种限制的变化。