Xposed反射字段流程分析


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方法,來對字段的值進行設置


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM