专栏名称: 蚂蚁金服ProtoTeam
数据前端团队
目录
相关文章推荐
51好读  ›  专栏  ›  蚂蚁金服ProtoTeam

震惊!他竟然把反射用得这么优雅!

蚂蚁金服ProtoTeam  · 掘金  · 前端  · 2017-12-08 05:20

正文

请到「今天看啥」查看全文


public static Class<?> TYPE = RefClass.load(ContextImpl.class, "android.app.ContextImpl"); 这句才是实际的初始化入口。第二个参数指定反射的操作目标类为 android.app.ContextImpl 。这个是框架的硬性要求。

接下来几个都是对要反射的变量。分别对应实际的 ContextImpl 类内部的 mBasePackageName mPackageInfo mPackageManager getReceiverRestrictedContext 成员和方法。

注意,这里只有声明的过程,没有赋值的过程。这个过程,便完成了传统的查找目标类内的变量域、方法要干的事情。从代码上看,相当的简洁直观。

下面这个表格,能更直观形象的表现它的优雅:

反射结构类型 声明 实际类型 实际声明
RefClass mirror.android.app.ContextImp Class android.app.ContextImp
RefObject<String> mBasePackageName String mBasePackageName
RefObject<Object> mPackageInfo LoadedApk mPackageInfo
RefObject<PackageManager> mPackageManager PackageManager mPackageManager
@MethodParams ({Context.class}) Params (Context.class)
RefMethod<Context> getReceiverRestrictedContext Method getReceiverRestrictedContext

除了形式上略有差异,两个类之间的结构上是保持一一对应的!

使用

接着,查找到这些变量域和方法后,当然是要用它们来修改内容,调用方法啦,怎么用呢:

// 修改mBasePackageName内的值
ContextImpl.mBasePackageName.set(context, hostPkg);

// .....

// 调用getReceiverRestrictedContext方法
Context receiverContext = ContextImpl.getReceiverRestrictedContext.call(context);

用起来是不是也相当直观?一行代码,就能看出要做什么事情。比起最开始提及的那种方式,这种方式简直清晰简洁得不要不要的,一鼓作气读下来不带停顿的。这样的代码几乎没有废话,每一行都有意义,信息密度杠杠的。

到这里就讲完了声明和使用这两个步骤。确实很简单吧?接下来再来看看实现。

实现分析

结构

首先看看这个框架的类图:

摆在中间的 RefClass 是最核心的类。

围绕在它周边的 RefBoolean RefConstructor RefDouble RefFloat RefInt RefLong RefMethod RefObject RefStaticInt RefStaticMethod RefStaticObject 则是用于声明和使用的反射结构的定义。从名字也能直观的看出该反射结构的类型信息,如构造方法、数据类型、是否静态等。

在右边角落的两个小家伙 MethodParams MethodReflectParams 是用于定义方法参数类型的注解,方法相关的反射结构的定义会需要用到它。它们两个的差别在于, MethodParams 接受的数据类型是 Class<?> ,而 MethodReflectParams 接受的数据类型是字符串,对应类型的全描述符,如 android.app.Context ,这个主要是服务于那些Android SDK没有暴露出来的,无法直接访问到的类。

运作

初始化

从上面的表格可以知道, RefClass 是整个声明中最外层的结构。这整个结构要能运作,也需要从这里开始,逐层向里地初始化。上文也提到了, RefClass.load(Class mappingClass, Class<?> realClass) 是初始化的入口。初始化的时机呢?我们知道,Java虚拟机在加载类的时候,会初始化静态变量,定义里的 TYPE = RefClass.laod(...) 就是在这个时候执行的。也就是说,当我们需要用到它的时候,它才会被加载,通过这种方式,框架具备了按需加载的特性,没有多余的代码。

入口知道了,我们来看看 RefClass.load(Class<?> mappingClass, Class<?> realClass) 内部的逻辑。

先不放源码,简单概括一下:

  1. mappingClass 内部,查找需要初始化的反射结构(如 RefObject<String> mBasePackageName )
  2. 实例化查到到的反射结构变量(即做了 RefObject<String> mmBasePackageName = new RefObject<String>(...) )

查找,就需要限定条件范围。结合定义,可以知道,要查找的反射结构,具有以下特点:

  1. 静态成员
  2. 类型为 Ref*

查找的代码如下:

public static Class load(Class mappingClass, Class<?> realClass) {
    // 遍历一遍内部定义的成员
    Field[] fields = mappingClass.getDeclaredFields();
    for






请到「今天看啥」查看全文