Android 中反射還能這么用?

cnmd9247 8年前發布 | 42K 次閱讀 Android 反射 Android開發 移動開發

這幾天稍微過了一下Weex的源碼,可謂是親眼目睹了它的源碼。無意間發現一個類,叫WXHack,搜索一下代碼,發現在Weex里用的地方就一處,好奇心驅使下去看了WXHack的源碼,好家伙!看完之后總覺得這個類似曾相識,后來昨天在看OpenAtlas的代碼的時候又看到了這個類,相關鏈接如下

后來我斷定這個類應該是淘寶Atlas里的類,Weex直接抽出來了。為了驗證自己的想法,下了個淘寶客戶端反編譯了下去找這個類,真的找到了。

這里寫圖片描述

想看源碼的可以直接點上面的鏈接去看OpenAtals里的類,也可以看這個保持了淘寶的包結構,但是反編譯并完全的項目,也就是包含了一些字節碼的,但是這兩個類應該是完整的。

那么攜程的動態加載框架也算是有Atlas的成分了,自然也就有這兩個類了,不出所料的找到了它們。

其實這個應該也不是淘專的專利,因為后來我在github上搜索了一番,發現了幾個類似的類,這幾個類在eclipse中竟然出現了。

那么,這兩個類到底有什么作用呢,莫慌,容我慢慢道來。

首先,從名字上可以看出這是一個Hack類,它的作用就是輔助反射,但是它們卻不是簡簡單單的輔助反射,而是反射后包裝的形式:類,方法,字段 ,也就是HackedClass,HackedConstructor,HackedMethod,HackedField這幾個反射的包裝形式。并且還有AssertionFailureHandler用于處理反射發生異常時的處理,你可以選擇扔出異常或者自己處理掉。

在看他的源碼之前,我們先來看看傳統的反射是怎么玩的。
寫了兩三個類用來測試。

Student實體類,內部有一個IBehavior類,代表學生的行為;此外還有靜態變量,非靜態變量,靜態方法,非靜態方法,泛型變量,作用就是等下測試不同場景的反射。

public class Student implements Serializable {
    private static String school = "清華大學";
    private String name;
    private int age;
    private HashMap<String, Integer> scores = new HashMap<String, Integer>();

    private IBehavior behavior = new BehaviorImpl();

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }


    public IBehavior getBehavior() {
        return behavior;
    }

    public void setBehavior(IBehavior behavior) {
        this.behavior = behavior;
    }

    public void addScore(String name, int score) {
        scores.put(name, score);
    }


    public HashMap<String, Integer> getScores() {
        return scores;
    }

    public void setScores(HashMap<String, Integer> scores) {
        this.scores = scores;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public static String getSchool() {
        return school;
    }

    public static void setSchool(String school) {
        Student.school = school;
    }

    public void say(String word) {
        System.out.println(word);
    }


    @Override
    public String toString() {
        return "Student{" +
                "school=" + school +
                ", age=" + age +
                ", name='" + name + '\'' +
                ", scores=" + scores +
                ", behavior=" + behavior +
                '}';
    }
}

IBehavior接口以及其實現類的代碼如下

public interface IBehavior {
    void perform(String behaiviorName, String behaiviorContent);
}
public class BehaviorImpl implements IBehavior {
    @Override
    public void perform(String behaiviorName, String behaiviorContent) {
        System.out.println("behaiviorName:" + behaiviorName);
        System.out.println("behaiviorContent:" + behaiviorContent);
    }
}

你要反射,就先得拿到Class對象,兩種方式,一直是Class在你的項目中,可以直接引用,第二種就是這個Class可能你不能直接引用,比如Android中的ContextImpl,一般來說,第二種的使用場景會更廣泛。

//直接通過class
Class<?> studentClassByClass = Student.class;

//通過class的字符串全類名
Class<?> studentClassByName = Class.forName("cn.edu.zafu.Student");

然后你可以直接調用class的newInstance方法新建一個對象,但是對于沒有默認構造函數的類來說,你需要拿到構造函數再去新建對象。這里需要注意的是你不知道修飾符是private,protected,public中的哪一個時,建議調用setAccessible設置成true,不然極有可能會發生異常。

//構造函數new對象
Constructor<?> constructor = studentClassByName.getConstructor(String.class, int.class);
constructor.setAccessible(true);
Student student = (Student) constructor.newInstance("區長", 22);
System.out.println(student);

對于靜態變量的訪問,可以拿到Field,直接設置即可。set方法的第一個參數傳null就是了。

//靜態變量
Field schoolField = studentClassByName.getDeclaredField("school");
schoolField.setAccessible(true);
schoolField.set(null, "浙江大學");
System.out.println(Student.getSchool());

但是對于非靜態變量,set方法的第一個參數你就需要傳遞一個實例進去,比如上面通過構造函數new出來的對象。

//非靜態變量
Field ageFiled = studentClassByName.getDeclaredField("age");
ageFiled.setAccessible(true);
Integer age = (Integer) ageFiled.get(student);
System.out.println(age);

ageFiled.set(student, 100);
System.out.println(student.getAge());

同理靜態方法同靜態變量

//靜態方法調用
Method setSchoolMethod = studentClassByName.getDeclaredMethod("setSchool", String.class);
setSchoolMethod.invoke(null, "清華大學");
System.out.println(Student.getSchool());

非靜態方法同非靜態變量

//非靜態方法調用
Method sayMethod = studentClassByName.getDeclaredMethod("say", String.class);
sayMethod.setAccessible(true);
sayMethod.invoke(student, "hello world");

等等,還有一個泛型變量呢

 private HashMap<String, Integer> scores = new HashMap<String, Integer>();

懵逼了,不會寫。。。。。其實還是一樣,強轉就ok了,只不過強轉前需要對類型進行校驗。

然后就是異常處理,反射大多數人都是通過try catch直接捕捉異常。。。這也沒什么好說的。

劫持,什么叫劫持呢,其實就是替換字段,有人說你直接反射拿到變量,直接反射替換就是了,這是一種方式,但是這并不是我想要的,比如我要劫持Student里的behavior變量,但是我不想修改它,我想保留它原來的邏輯,但是我又想加入新的東西,這個就有點類型面向切面編程了,比如日志的注入。怎么做?懵逼了。。。。顯然是動態代理啊。使用代理類去完成劫持操作,既可以保留原有操作,又可以增加新的邏輯。

那么Hack類是怎么做的呢。首先生成HackedClass類,怎么生成的呢,調用Hack的into方法,入參有兩種形式,一種就是直接傳Class,另一種就是傳遞Class的全類名字符串。

public static <T> HackedClass<T> into(final Class<T> clazz) {
    return new HackedClass<T>(clazz);
}

@SuppressWarnings({"rawtypes", "unchecked"})
public static <T> HackedClass<T> into(final String class_name) throws HackDeclaration.HackAssertionException {
    try {
        return new HackedClass(Class.forName(class_name));
    } catch (final ClassNotFoundException e) {
        fail(new HackDeclaration.HackAssertionException(e));
        return new HackedClass(null);    // TODO: Better solution to avoid null?
    }
}

into方法返回的是HackedClass對象,這個對象是對反射的包裝類之一。其源碼如下

public static class HackedClass<C> {

    protected Class<C> mClass;

    public HackedClass(final Class<C> clazz) {
        mClass = clazz;
    }

    public HackedField<C, Object> staticField(final String name) throws HackDeclaration.HackAssertionException {
        return new HackedField<C, Object>(mClass, name, Modifier.STATIC);
    }

    public HackedField<C, Object> field(final String name) throws HackDeclaration.HackAssertionException {
        return new HackedField<C, Object>(mClass, name, 0);
    }

    public HackedMethod staticMethod(final String name, final Class<?>... arg_types) throws HackDeclaration.HackAssertionException {
        return new HackedMethod(mClass, name, arg_types, Modifier.STATIC);
    }

    public HackedMethod method(final String name, final Class<?>... arg_types) throws HackDeclaration.HackAssertionException {
        return new HackedMethod(mClass, name, arg_types, 0);
    }

    public HackedConstructor constructor(final Class<?>... arg_types) throws HackDeclaration.HackAssertionException {
        return new HackedConstructor(mClass, arg_types);
    }

    public Class<C> getmClass() {
        return mClass;
    }
}   

如果你要獲得靜態方法,就調用staticMethod,靜態變量就調用staticField,非靜態方法就調用method,非靜態變量就調用field,要獲得構造函數就調用constructor,當然你也可以調用getmClass獲得Class。最終如果你調用方法相關的函數會得到HackedMethod,調用變量相關的會得到HackedField,調用構造函數相關的會獲得HackedConstructor,這三個類的源碼如下。自己看源碼。。。。不解釋了。。。太長了,主要是對反射的封裝,并達到面向對象的效果。

public static class HackedField<C, T> {

    private final Field mField;


    HackedField(final Class<C> clazz, final String name, int modifiers) throws HackDeclaration.HackAssertionException {
        Field field = null;
        try {
            if (clazz == null) {
                return;
            }
            field = clazz.getDeclaredField(name);
            if (modifiers > 0 && (field.getModifiers() & modifiers) != modifiers) {
                fail(new HackDeclaration.HackAssertionException(field + " does not match modifiers: " + modifiers));
            }
            field.setAccessible(true);
        } catch (final NoSuchFieldException e) {
            HackDeclaration.HackAssertionException hae = new HackDeclaration.HackAssertionException(e);
            hae.setHackedClass(clazz);
            hae.setHackedFieldName(name);
            fail(hae);
        } finally {
            mField = field;
        }
    }

    @SuppressWarnings("unchecked")        
    public <T2> HackedField<C, T2> ofGenericType(final Class<?> type) throws HackDeclaration.HackAssertionException {
        if (mField != null && !type.isAssignableFrom(mField.getType())) {
            fail(new HackDeclaration.HackAssertionException(new ClassCastException(mField + " is not of type " + type)));
        }
        return (HackedField<C, T2>) this;
    }

    @SuppressWarnings("unchecked")
    public HackedField<C, T> ofType(final String type_name) throws HackDeclaration.HackAssertionException {
        try {
            return (HackedField<C, T>) ofType(Class.forName(type_name));
        } catch (final ClassNotFoundException e) {
            fail(new HackDeclaration.HackAssertionException(e));
            return this;
        }
    }

    @SuppressWarnings("unchecked")
    public <T2> HackedField<C, T2> ofType(final Class<T2> type) throws HackDeclaration.HackAssertionException {
        if (mField != null && !type.isAssignableFrom(mField.getType())) {
            fail(new HackDeclaration.HackAssertionException(new ClassCastException(mField + " is not of type " + type)));
        }
        return (HackedField<C, T2>) this;
    }


    public void hijack(final C instance, final Interception.InterceptionHandler<?> handler) {
        final T delegatee = get(instance);
        if (delegatee == null) {
            throw new IllegalStateException("Cannot hijack null");
        }
        final Class<?>[] interfaces = delegatee.getClass().getInterfaces();
        set(instance, Interception.proxy(delegatee, handler, interfaces));
    }



    public T get(final C instance) {
        try {
            @SuppressWarnings("unchecked") final T value = (T) mField.get(instance);
            return value;
        } catch (final IllegalAccessException e) {
            e.printStackTrace();
            return null; 
        }
    }


    public void set(final C instance, final Object value) {
        try {
            mField.set(instance, value);
        } catch (final IllegalAccessException e) {
            e.printStackTrace();

        }
    }

    public Field getField() {
        return mField;
    }
}

public static class HackedMethod {

    protected final Method mMethod;


    HackedMethod(final Class<?> clazz, final String name, final Class<?>[] arg_types, int modifiers) throws HackDeclaration.HackAssertionException {
        Method method = null;
        try {
            if (clazz == null) {
                return;
            }
            method = clazz.getDeclaredMethod(name, arg_types);
            if (modifiers > 0 && (method.getModifiers() & modifiers) != modifiers) {
                fail(new HackDeclaration.HackAssertionException(method + " does not match modifiers: " + modifiers));
            }
            method.setAccessible(true);
        } catch (final NoSuchMethodException e) {
            HackDeclaration.HackAssertionException hae = new HackDeclaration.HackAssertionException(e);
            hae.setHackedClass(clazz);
            hae.setHackedMethodName(name);
            fail(hae);
        } finally {
            mMethod = method;
        }
    }


    public Object invoke(final Object receiver, final Object... args) throws IllegalArgumentException, InvocationTargetException {
        Object obj = null;
        try {
            obj = mMethod.invoke(receiver, args);
            return obj;
        } catch (final IllegalAccessException e) { /* Should never happen */
            e.printStackTrace();
        }
        return obj;
    }

    public Method getMethod() {
        return mMethod;
    }
}

public static class HackedConstructor {

    protected Constructor<?> mConstructor;

    HackedConstructor(final Class<?> clazz, final Class<?>[] arg_types) throws HackDeclaration.HackAssertionException {
        try {
            if (clazz == null) {
                return;
            }
            mConstructor = clazz.getDeclaredConstructor(arg_types);
        } catch (NoSuchMethodException e) {
            HackDeclaration.HackAssertionException hae = new HackDeclaration.HackAssertionException(e);
            hae.setHackedClass(clazz);
            fail(hae);
        }
    }

    public Object getInstance(final Object... arg_types) throws IllegalArgumentException {
        Object obj = null;
        mConstructor.setAccessible(true);
        try {
            obj = mConstructor.newInstance(arg_types);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }
}

注意HackedField中有個方法叫hijack,這個方法的作用就是劫持。

最終,如果反射失敗的話會進入Hack的fail方法處理,我們可以設置AssertionFailureHandler處理器,返回false會扔出異常,否則不會扔出異常。可以在里面做一些事,比如埋點。這樣就可以集中到一個地方處理了。

private static void fail(HackDeclaration.HackAssertionException e) throws HackDeclaration.HackAssertionException {
    if (sFailureHandler == null || !sFailureHandler.onAssertionFailure(e)) {
        throw e;
    }
}


public static void setAssertionFailureHandler(AssertionFailureHandler handler) {
    sFailureHandler = handler;
}


public interface AssertionFailureHandler {
    boolean onAssertionFailure(HackDeclaration.HackAssertionException failure);
}

瞎說了這么多,來實踐一把。

獲得HackedClass對象,調用Hack.into方法

//通過Class來獲得一個HackedClass
Hack.HackedClass<Student> hackPersonByClass = Hack.into(Student.class);
//輸出class測試
System.out.println(hackPersonByClass.getmClass());
//通過Class的全類名獲得一個HackedClass
Hack.HackedClass<Student> hackPersonByName = Hack.into("cn.edu.zafu.Student");
//輸出class測試
System.out.println(hackPersonByName.getmClass());

創建實例,調用HackedClass的constructor方法,將參數的類型傳入,然后調用getInstance方法就可以獲得一個實例了。

//獲得構造函數
Hack.HackedConstructor personConstructor = hackPersonByName.constructor(String.class, int.class);
//創建并獲得實例
Student student = (Student) personConstructor.getInstance("區長", 121);
//輸出結果測試
System.out.println(student);
//直接調用反射獲得的對象的方法
student.say("世界你好,世界再見");

非靜態變量需要通過field方法獲得一個HackedField對象,這時候你獲得的field是一個Object類型的,因此如果你想要類型安全,需要調用一下ofType方法,將類型傳入。

//通過field -> ofType 獲得一個HackedField,非靜態
Hack.HackedField<Student, String> hackName = hackPersonByName.field("name").ofType(String.class);
//反射調用
String name = hackName.get(student);
//輸出結果測試
System.out.println(name);

//通過field -> ofType 獲得一個HackedField,非靜態
Hack.HackedField<Student, Integer> hackAge = hackPersonByName.field("age").ofType(int.class);
//反射設置屬性
hackAge.set(student, 16);
//反射獲得age,進行驗證
Integer age = hackPersonByName.field("age").ofType(int.class).get(student);
//輸出結果測試
System.out.println(age);

靜態變量直接調用staticField方法即可,除了上面的ofType將類型傳入,這個類型可以是Class對象,也可以是Class字符串的全類名。

//反射獲得靜態變量
Hack.HackedField<Student, Object> hackSchool = hackPersonByName.staticField("school").ofType("java.lang.String");

//獲得靜態變量值
String sSchool = (String) hackSchool.get(null);
//輸出結果測試
System.out.println(sSchool);

//設置值
hackSchool.getField().set(null, "北京大學");
//獲得值驗證是否設置成功,通過getField()方式
sSchool = (String) hackSchool.getField().get(null);
//輸出結果測試
System.out.println(sSchool);

泛型參數可以調用ofGenericType方法轉為泛型,比如下面的Map

//泛型參數
Hack.HackedField<Student, Map<String, Integer>> hackScores = hackPersonByName.field("scores").ofGenericType(Map.class);
Map<String, Integer> stringIntegerMap = hackScores.get(student);
stringIntegerMap.put("語文", 80);
stringIntegerMap.put("數學", 90);
//泛型參數設置
hackScores.set(student, stringIntegerMap);
//輸出結果測試
System.out.println(student.getScores());

方法的調用需要調用method相關的方法,非靜態方法直接調用method方法獲得HackedMethod對象,需要將方法的參數類型傳入。之后直接invoke調用的時候將調用對象實例和參數傳入。

//反射非靜態方法調用
hackPersonByName.method("say", String.class).invoke(student, "fuck the source code");

靜態方法和非靜態方法相比就是invoke的時候調用對象可以直接傳null。

//反射靜態方法調用
hackPersonByName.staticMethod("setSchool", String.class).invoke(null, "南京大學");
//輸出結果測試
System.out.println(Student.getSchool());
//反射靜態方法調用
String school = (String) hackPersonByName.staticMethod("getSchool").getMethod().invoke(null);
System.out.println(school);

以上調用的最終輸出如下圖所示,代碼可能跟圖有出入(貌似修改過前后順序)

這里寫圖片描述

Hack類里的異常處理最終都會走到fail方法中,fail方法中會判斷AssertionFailureHandler是否為空,空的情況下會直接扔出異常,否則會看AssertionFailureHandler的處理結果,如果結果返回true,則不扔出異常,返回fasle的情況下也會扔出異常,我們通過setAssertionFailureHandler方法設置一個異常處理器就可以了。下面調用兩個不存在的字段,讓它進入到這個處理器中,如果字段名是notHandler則扔出異常,否則輸出信息并返回true。

//異常處理
Hack.setAssertionFailureHandler(new Hack.AssertionFailureHandler() {
    @Override
    public boolean onAssertionFailure(Hack.HackDeclaration.HackAssertionException failure) {
        //如果是notHandler字段,則不處理,即扔出異常,否則打印輸出,不扔出異常
        if ("notHandler".equals(failure.getHackedFieldName())) {
            return false;
        }

        Class<?> hackedClass = failure.getHackedClass();
        String hackedFieldName = failure.getHackedFieldName();
        String hackedMethodName = failure.getHackedMethodName();
        System.out.println("=====onAssertionFailure start=====");
        System.out.println("hackedClass:" + hackedClass);
        System.out.println("hackedFieldName:" + hackedFieldName);
        System.out.println("hackedMethodName:" + hackedMethodName);
        System.out.println("=====onAssertionFailure end=====");
        //返回true不會拋出異常,否則拋出異常

        return true;
    }
});
//獲得一個不存在的對象,驗證onAssertionFailure回調
Hack.HackedField<Student, String> unknownField = hackPersonByName.field("unknownField").ofType(String.class);
Hack.HackedField<Student, String> notHandler = hackPersonByName.field("notHandler").ofType(String.class);

最終的效果如下

這里寫圖片描述

劫持字段,字段的劫持就是通過動態代理來實現的,內部的邏輯如下

public static abstract class InterceptionHandler<T> implements InvocationHandler {
    private T mDelegatee;

    @Override
    public Object invoke(Object obj, Method method, Object[] args)
            throws Throwable {
        Object obj2 = null;
        try {
            obj2 = method.invoke(delegatee(), args);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e2) {
            e2.printStackTrace();
        } catch (InvocationTargetException e3) {
            throw e3.getTargetException();
        }
        return obj2;
    }

    protected T delegatee() {
        return this.mDelegatee;
    }

    void setDelegatee(T t) {
        this.mDelegatee = t;
    }
}

這個字段的方法調用都是調用委托對象,這個對象就是我們原始的字段,這個delegatee方法我們可以重寫返回新對象,這樣就和反射直接替換沒有什么差別了。當然也可以重寫invoke方法,在調用前和調用后做一些處理。比如我做一些日志輸出。如下代碼

//字段劫持處理
Interception.InterceptionHandler handler = new Interception.InterceptionHandler<IBehavior>() {
    @Override
    public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
        System.out.println("hijack:[invoke start]");

        Object o = super.invoke(obj, method, args);

        System.out.println("hijack:[invoke end]");
        return o;
    }
};
//劫持behavior字段
hackPersonByName.field("behavior").hijack(student, handler);
//測試劫持效果
student.getBehavior().perform("sleep", "sleep 10h");

最終效果如下
這里寫圖片描述

好了說了這么多,還是不明白這個東西有什么軟用呢?Android動態加載資源的時候我們需要用到的ContextImpl對象,需要對AssetManager和Resources需要做一些操作,就可以這么來了。

import java.lang.reflect.InvocationTargetException;

public class Hacks extends HackDeclaration implements AssertionFailureHandler {
    public static HackedClass<AssetManager> AssetManager;
    public static HackedMethod AssetManager_addAssetPath;
    public static HackedClass<Object> ContextImpl;
    public static HackedField<Object, Resources> ContextImpl_mResources;
    public static boolean sIsReflectChecked;
    public static boolean sIsReflectAvailable;

    public static boolean defineAndVerify() {
        if (sIsReflectChecked) {
            return sIsReflectAvailable;
        }
        Hacks hacks = new Hacks();
        try {
            Hack.setAssertionFailureHandler(hacks);
            allClasses();
            allConstructors();
            allFields();
            allMethods();
            sIsReflectAvailable = true;
            return sIsReflectAvailable;
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            Hack.setAssertionFailureHandler(null);
            sIsReflectChecked = true;
        }
        return false;
    }

    private static void allClasses() throws HackAssertionException {
        AssetManager = Hack.into(AssetManager.class);
        ContextImpl = Hack.into("android.app.ContextImpl");
    }

    private static void allConstructors() throws HackAssertionException {
    }

    private static void allFields() throws HackAssertionException {
        ContextImpl_mResources = ContextImpl.field("mResources").ofType(Resources.class);
    }

    private static void allMethods() throws HackAssertionException {
        AssetManager_addAssetPath = AssetManager.method("addAssetPath", String.class);
    }

    @Override
    public boolean onAssertionFailure(HackAssertionException hackAssertionException) { // throw it
        return false;
    }

}


    

調用 Hacks.defineAndVerify反復進行定義和驗證,成功后就可以直接調用了。

boolean flag = Hacks.defineAndVerify();

if (flag) {
    AssetManager assetManager = AssetManager.class.newInstance();
    Hacks.AssetManager_addAssetPath.invoke(assetManager,"assetPath");
    Hacks.ContextImpl_mResources.set(context, resources);
}

上面的這個例子是偽代碼,實際情況自己去把握~~~~寫的比較隨意,看看就好了~

來自: http://blog.csdn.net/sbsujjbcy/article/details/51280274

 本文由用戶 cnmd9247 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!