luoyesiqiu 发表于 2019-8-13 16:59

Xposed反射字段流程分析

在(https://github.com/rovo89/XposedBridge)中,反射字段的方法封装在`de.robv.android.xposed.XposedHelpers`类里面.下面来看看Xposed是如何获取和设置字段的值的

## 获取字段的值

获取字段的值有许多个方法,有获取基本类型字段的值的方法(getIntField,getLongField,getDoubleField...),也有获取对象类型字段的值的方法.它们的实现大同小异.我们以获取对象类型字段的值为例,来分析Xposed是如何反射获取字段对象的值.这个功能在XposedHelpers类的`getObjectField`方法

```
public static Object getObjectField(Object obj, String fieldName) {
        try {
                return findField(obj.getClass(), fieldName).get(obj);
        } catch (IllegalAccessException e) {
                // should not happen
                XposedBridge.log(e);
                throw new IllegalAccessError(e.getMessage());
        } catch (IllegalArgumentException e) {
                throw e;
        }
}
```
`getObjectField`有两个参数:一个是要获取的字段的所属对象,另一个是字段的名称.
`getObjectField`主要调用`findField`方法和调用`findField`方法返回的对象的set方法,去看看findField方法:

```
public static Field findField(Class<?> clazz, String fieldName) {
        String fullFieldName = clazz.getName() + '#' + fieldName;

        if (fieldCache.containsKey(fullFieldName)) {
                Field field = fieldCache.get(fullFieldName);
                if (field == null)
                        throw new NoSuchFieldError(fullFieldName);
                return field;
        }

        try {
                Field field = findFieldRecursiveImpl(clazz, fieldName);
                field.setAccessible(true);
                fieldCache.put(fullFieldName, field);
                return field;
        } catch (NoSuchFieldException e) {
                fieldCache.put(fullFieldName, null);
                throw new NoSuchFieldError(fullFieldName);
        }
}
```

`findField`方法的开始,先拼接一个完整的字段名称.接下来根据完整字段名判断缓存中是否存在这个字段,如果有就从缓存中取出这个字段,就不用在查找了,这样子做可以提高反射速率.如果从缓存取出的为空值,代表这个字段之前找过了,找不到,并抛出异常.


缓存名叫`fieldCache`,它是一个HashMap.

```
private static final HashMap<String, Field> fieldCache = new HashMap<>();
```

如果缓存中没有这个字段呢,调用`findFieldRecursiveImpl`查找字段:

```
private static Field findFieldRecursiveImpl(Class<?> clazz, String fieldName) throws NoSuchFieldException {
        try {
                return clazz.getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
                while (true) {
                        clazz = clazz.getSuperclass();
                        if (clazz == null || clazz.equals(Object.class))
                                break;

                        try {
                                return clazz.getDeclaredField(fieldName);
                        } catch (NoSuchFieldException ignored) {}
                }
                throw e;
        }
}
```

`findFieldRecursiveImpl`方法首先在传进来的Class类中查找有没有这个字段,如果有返回Field对象.如果没有,就开一个死循环从它的父类中找,如果这个类没有父类或者一直找到`Object`类也找不到这个方法,说明这个类真的没有要找的字段,抛出一个异常.如果在它的父类中找到了这个字段,返回Field对象.

`findFieldRecursiveImpl`调用结束,将会回到`findField`的后半段:

```
public static Field findField(Class<?> clazz, String fieldName) {
        ......
        try {
                Field field = findFieldRecursiveImpl(clazz, fieldName);
                field.setAccessible(true);
                fieldCache.put(fullFieldName, field);
                return field;
        } catch (NoSuchFieldException e) {
                fieldCache.put(fullFieldName, null);
                throw new NoSuchFieldError(fullFieldName);
        }
}
```

如果`findFieldRecursiveImpl`没有抛出异常,那么将这个字段加入缓存,并将这个字段返回.如果如果findFieldRecursiveImpl抛出异常,也放入缓存,但是放入的是空值.

`findField`方法结束,回到最初的`getObjectField`方法,调用`get`方法,来返回字段存储的对象值

## 设置字段的值

和获取字段的值的方法一样,设置字段的方法也是有许多,它们的实现也是大同小异.我们来看看其中的`setObjectField`方法:

```
public static void setObjectField(Object obj, String fieldName, Object value) {
        try {
                findField(obj.getClass(), fieldName).set(obj, value);
        } catch (IllegalAccessException e) {
                // should not happen
                XposedBridge.log(e);
                throw new IllegalAccessError(e.getMessage());
        } catch (IllegalArgumentException e) {
                throw e;
        }
}
```
`setObjectField`方法和上面讲解的`getObjectField`几乎相同,也是通过调用`findField`方法得到Field类对象.不同的是,`setObjectField`得到Field对象后调用的是它的set方法,来对字段的值进行设置.

whyerect 发表于 2019-8-13 18:54

好深奥啊!

好市民刘青云O 发表于 2019-8-13 20:47

好像很高级的样子,试试

一剑断长生 发表于 2019-8-14 00:22

感谢楼主分享,很强大已拿走

ColoThor 发表于 2019-8-14 09:13

其实就是java的反射

佚丶名 发表于 2019-8-14 10:48

看不太懂 还是要支持

wycmxg 发表于 2019-8-14 11:50

看不太懂 支持支持

fhxylang 发表于 2019-8-14 13:50

技术贴,看得迷糊,大致意思懂了

BlueTears_ 发表于 2019-8-15 01:28

看不懂...

wowowowo 发表于 2019-8-15 09:20

大佬666,就是看不懂
页: [1] 2
查看完整版本: Xposed反射字段流程分析