virtualapp-RefClass反射机制(转载)

转自:https://www.jianshu.com/p/d040c7f1a46f

声明

package mirror.android.app;

public class ContextImpl {
    public static Class<?> TYPE = RefClass.load(ContextImpl.class, "android.app.ContextImpl");
    @MethodParams({Context.class})
    public static RefObject<String> mBasePackageName;
    public static RefObject<Object> mPackageInfo;
    public static RefObject<PackageManager> mPackageManager;

    public static RefMethod<Context> getReceiverRestrictedContext;
}

上述类是VirtualApp里对ContextImpl类的反射的定义。

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

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

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

下面这个表格,能更直观形象的表现它的优雅:
在这里插入图片描述
除了形式上略有差异,两个类之间的结构上是保持一一对应的!

使用

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

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

// .....

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

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

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

实现分析

结构

在这里插入图片描述
摆在中间的RefClass是最核心的类。
围绕在它周边的RefBooleanRefConstructorRefDoubleRefFloatRefIntRefLongRefMethodRefObjectRefStaticIntRefStaticMethodRefStaticObject则是用于声明和使用的反射结构的定义。从名字也能直观的看出该反射结构的类型信息,如构造方法、数据类型、是否静态等。

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

这里有个很重要的特点,如果没有MethodParamsMethodReflectParams,也就是无参时,我们定义的RefMethod
变量名一定要和原始的保持同名,参看以下代码:

    public RefStaticMethod(Class<?> cls, Field field) throws NoSuchMethodException {
        if (field.isAnnotationPresent(MethodParams.class)) {
			。。。。。
        } else if (field.isAnnotationPresent(MethodReflectParams.class)) {
		    。。。。。
            this.method.setAccessible(true);
        } else {
            for (Method method : cls.getDeclaredMethods()) {  
                if (method.getName().equals(field.getName())) {  // 同名才能匹配
                    this.method = method;
                    this.method.setAccessible(true);
                    break;
                }
            }
        }

        if (this.method == null) {
            throw new NoSuchMethodException(field.getName());
        }
    }

因为它是同名配置的

运作

初始化

从上面的表格可以知道,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> mBasePackageName = new RefObject<String>(...))

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

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

查找的代码如下:

public static Class load(Class mappingClass, Class<?> realClass) {
    // 遍历一遍内部定义的成员
    Field[] fields = mappingClass.getDeclaredFields();
    for (Field field : fields) {
        try {
            // 如果是静态类型
            if (Modifier.isStatic(field.getModifiers())) {
                // 且是反射结构, REF_TYPES的value是注册器(Constructor)对象,通过newInstance()创建对象
                Constructor<?> constructor = REF_TYPES.get(field.getType()); //field.getType() 返回一个Class对象
                if (constructor != null) {
                    // 实例化该成员
                    field.set(null, constructor.newInstance(realClass, field));// 调用RefStaticMethod类中的RefStaticMethod(Class<?> cls, Field field) 
                }
            }
        } 
        catch (Exception e) {
            // Ignore
        }
    }
    return realClass;
}

这其实就是整个RefClass.load(...)的实现了。可以看到,实例化的过程仅仅是简单的调用构造函数实例化对象,然后用反射的方式赋值给该变量。

REF_TYPES是一个Map,里面注册了所有的反射结构(Ref*)。Class对象==>注册器(Constructor)对象 源码如下:

private static HashMap<Class<?>,Constructor<?>> REF_TYPES = new HashMap<Class<?>, Constructor<?>>();
static {
    try {
        REF_TYPES.put(RefObject.class, RefObject.class.getConstructor(Class.class, Field.class));
        REF_TYPES.put(RefMethod.class, RefMethod.class.getConstructor(Class.class, Field.class));
        REF_TYPES.put(RefInt.class, RefInt.class.getConstructor(Class.class, Field.class));
        REF_TYPES.put(RefLong.class, RefLong.class.getConstructor(Class.class, Field.class));
        REF_TYPES.put(RefFloat.class, RefFloat.class.getConstructor(Class.class, Field.class));
        REF_TYPES.put(RefDouble.class, RefDouble.class.getConstructor(Class.class, Field.class));
        REF_TYPES.put(RefBoolean.class, RefBoolean.class.getConstructor(Class.class, Field.class));
        REF_TYPES.put(RefStaticObject.class, RefStaticObject.class.getConstructor(Class.class, Field.class));
        REF_TYPES.put(RefStaticInt.class, RefStaticInt.class.getConstructor(Class.class, Field.class));
        REF_TYPES.put(RefStaticMethod.class, RefStaticMethod.class.getConstructor(Class.class, Field.class));
        REF_TYPES.put(RefConstructor.class, RefConstructor.class.getConstructor(Class.class, Field.class));
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}

发现没有?在RefClass.laod(...)里,实例化的过程简单到不可思议?因为每个反射结构代表的含义都不一样,初始化时要做的操作也各有不同。与其将这些不同都防止load的函数里,还不如将对应的逻辑分解到构造函数里更合适。这样既降低了RefClass.laod(...)实现的复杂度,保持了简洁,也将特异代码内聚到了对应的反射结构Ref*中去。

反射结构定义

挑几个有代表性的反射结构来分析。

1. RefInt

RefInt这种是最简单的。依旧先不放源码。先思考下,对于一个这样的放射结构,需要关心的东西有什么?

1.首先是这个反射结构映射到原始类中是哪个Field

2.紧接着就是Field的类型是什么。

上文表格里可以看到,反射结构的名称和实际类中对应的Field的名称的一一对应的。我们只要拿到反射结构的名称就可以了。

Field的类型,由于RefInt直接对应到了int类型,所以这个是直接可知的信息。

public RefInt(Class cls, Field field) throws NoSuchFieldException {
    this.field = cls.getDeclaredField(field.getName());
    this.field.setAccessible(true);
}

源码里也是这么做的,从反射结构的Field里,取得反射结构定义时的名字,用这个名字去真正的类里,查找到对应的Field,并设为可访问的,然后作为反射结构的成员变量持有了。

为了方便使用,又新增了getset两个方法,便于快捷的存取这个Field内的值。如下:

ublic int get(Object object) {
    try {
        return this.field.getInt(object);
    } catch (Exception e) {
        return 0;
    }
}

public void set(Object obj, int intValue) {
    try {
        this.field.setInt(obj, intValue);
    } catch (Exception e) {
        //Ignore
    }
}

就这样,RefInt就分析完了。这个类的实现依旧保持了一贯的简洁优雅。

2. RefStaticInt

RefStaticIntRefInt的基础上,加了一个限制条件:该变量是静态变量,而非类的成员变量。熟悉反射的朋友们知道,通过反射Field是没有区分静态还是非静态的,都是调用Class.getDeclaredField(fieldName)方法。所以这个类的构造函数跟RefInt是一毛一样毫无差别的。

public RefStaticInt(Class<?> cls, Field field) throws NoSuchFieldException {
    this.field = cls.getDeclaredField(field.getName());
    this.field.setAccessible(true);
}

当然,熟悉反射的朋友也知道,一个Field是否静态是能够根据Modifier.isStatic(field.getModifiers())来判定的。这里若是为了严格要求查找到的Feild一定是static field的话,可以加上这个限制优化下。

静态变量和成员变量在通过反射进行数据存取则是有差异的。成员变量的Field需要传入目标对象,而静态变量的Field不需要,传null即可。这个差异,对应的getset方法也做了调整,不再需要传入操作对象。源码如下:

public int get() {
    try {
        return this.field.getInt(null);
    } catch (Exception e) {
        return 0;
    }
}

public void set(int value) {
    try {
        this.field.setInt(null, value);
    } catch (Exception e) {
        //Ignore
    }
}

3.RefObject

RefObject<T>RefInt相比,理解起来复杂了一点:Field的数据类型由泛型的<T>提供。但实际上,和RefStaticInt一样,构造函数类并没有做严格的校验,即运行时不会在构造函数检查实际的类型和泛型里的期望类型是否一致。所以,构造函数依旧没什么变化。

public RefObject(Class<?> cls, Field field) throws NoSuchFieldException {
    this.field = cls.getDeclaredField(field.getName());
    this.field.setAccessible(true);
}

实际上,要做严格检查也依旧是可以的。我猜想,我猜想作者之所以没有加严格的检查,一是为了保持实现的简单,二是这种错误,属于定义的时候的错误,即写出了bug,那么在接下来的使用中一样会报错,属于开发过程中必然会发现的bug,因此实现上做严格的校验意义不大。

泛型<T>的作用在于数据存取的时候,做相应的类型规范和转换。源码如下:

public T get(Object object) {
    try {
        return (T) this.field.get(object);
    } catch (Exception e) {
        return null;
    }
}

public void set(Object obj, T value) {
    try {
        this.field.set(obj, value);
    } catch (Exception e) {
        //Ignore
    }
}

4. RefMethod和@MethodParams

最后再分析下RefMethod这个Method相关的反射结构,与之类似的有RefConstructorRefStaticeMethod,实现原理上也是大同小异。

和前面Field相关的反射结构不同,Method的反射结构确实稍微复杂了一丢丢。RefMethod对应的是方法,对方法来说,它有方法名、返回值、参数这三个信息要关心。

前面分析可知,变量名信息是通过反射结构定义的名字来确定的,方法名也一样,通过反射结构的Field就能获取到。

返回值呢?所有的Method.invoke(...)都有一个返回值,和RefObject<T>一样,类型信息通过泛型提供,在使用的时候,仅仅做了转义。

参数这个信息,则是Method.invoke(...)调用里必不可少的参数。VirtualApp通过给RefMethod定义加注解创造性地解决了这个问题,即实现了声明式,也保证了实现的简单优雅。理解这段代码不难,但这个用法确实很新颖。

看下构造方法的源码:

public RefMethod(Class<?> cls, Field field) throws NoSuchMethodException {
    if (field.isAnnotationPresent(MethodParams.class)) {
        Class<?>[] types = field.getAnnotation(MethodParams.class).value();
        for (int i = 0; i < types.length; i++) {
            Class<?> clazz = types[i];
            if (clazz.getClassLoader() == getClass().getClassLoader()) {
                try {
                    Class.forName(clazz.getName());
                    Class<?> realClass = (Class<?>) clazz.getField("TYPE").get(null);
                    types[i] = realClass;
                } catch (Throwable e) {
                    throw new RuntimeException(e);
                }
            }
        }
        this.method = cls.getDeclaredMethod(field.getName(), types);
        this.method.setAccessible(true);
    } else if (field.isAnnotationPresent(MethodReflectParams.class)) {
        String[] typeNames = field.getAnnotation(MethodReflectParams.class).value();
        Class<?>[] types = new Class<?>[typeNames.length];
        for (int i = 0; i < typeNames.length; i++) {
            Class<?> type = getProtoType(typeNames[i]);
            if (type == null) {
                try {
                    type = Class.forName(typeNames[i]);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
            types[i] = type;
        }
        this.method = cls.getDeclaredMethod(field.getName(), types);
        this.method.setAccessible(true);
    }
    else {
        for (Method method : cls.getDeclaredMethods()) {
            if (method.getName().equals(field.getName())) {
                this.method = method;
                this.method.setAccessible(true);
                break;
            }
        }
    }
    if (this.method == null) {
        throw new NoSuchMethodException(field.getName());
    }
}

看起来很长的实现,实际上是对三种可能的情况做了区分处理:

1.@MethodParams注解声明参数的情况

2.@MethodReflectParams注解声明参数的情况

3.没有使用注解的情况,即无参的场景

然后照例,增加了一个便捷的调用方法call(Object receiver, Object... args)。同样的,这里也没过多的校验,直接透传给实际的Method实例。看下代码:

public T call(Object receiver, Object... args) {
    try {
        return (T) this.method.invoke(receiver, args);
    } catch (InvocationTargetException e) {
        if (e.getCause() != null) {
            e.getCause().printStackTrace();
        } else {
            e.printStackTrace();
        }
    } catch (Throwable e) {
        e.printStackTrace();
    }
    return null;
}

5. 小结

至此,也就把几个有代表性的反射结构分析了一遍。可以看到,声明里重要的信息都是通过RefClass内的反射结构的Field定义提供的,反射结构在实例化的过程中,从中取出信息,做处理。这种用法,实在高明。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值