1. 程式人生 > >Java進階知識-反射

Java進階知識-反射

獲取Class物件

有三種方式獲取Class物件:

  1. 根據類的完整包名獲取Class
Class clazz = Class.forName("com.example.xjp.demo.reflect.PersonInfo");
  1. 根據類名直接獲取Class
Class clazz = PersonInfo.class;
  1. 根據例項類的物件獲取Class
 PersonInfo personInfo = new PersonInfo();
 Class clazz = personInfo.getClass();

建立例項

通過反射來生成物件主要有兩種方式
1. 使用Class物件的newInstance()方法來建立Class物件對應類的例項

Class clazz = PersonInfo.class;
PersonInfo personInfo = (PersonInfo) clazz.newInstance();

2.使用Class物件的構造器來建立例項

 Constructor constructor = clazz.getConstructor(PersonInfo.class);
 //有構造器來建立例項,可以傳引數給newInstance(Object ... initargs)
 PersonInfo personInfo = (PersonInfo) constructor.newInstance();

獲取方法

通過反射,可以獲取某個類中的所有方法,包括private,public,protect型別的方法
1. 獲取類的所有申明的方法,包括public,private,protect型別的方法

 Class clazz = PersonInfo.class;
 Method[] declaredMethods = clazz.getDeclaredMethods();

2.獲取類中所有public方法

Class clazz = PersonInfo.class;
Method[] methods = clazz.getMethods();

3.獲取類中指定的public方法

Class
clazz = PersonInfo.class; PersonInfo personInfo = (PersonInfo) clazz.newInstance(); //第一個引數是方法名,第二個引數是該方法引數的型別 Method method = clazz.getMethod("getName", String.class); //呼叫 PersonInfo 類中的 getName()方法 String name = (String) method.invoke(personInfo , "是最帥的");

4.獲取指定的private方法

Class clazz = PersonInfo.class;
PersonInfo personInfo = (PersonInfo) clazz.newInstance();
//第一個引數是方法名,第二個引數是該方法引數的型別
Method method = clazz.getDeclaredMethod("getAge", Integer.class);
//由於該方法是private的,所以需要設定訪問許可權
method.setAccessible(true);
//呼叫PersonInfo 類中的 getAge()方法
int age = (int) method.invoke(personInfo, 18);

獲取類的成員變數

  1. 獲取類中所有成員變數,包括public,private,protect型別
Field[] declaredFields = clazz.getDeclaredFields();

2.獲取類中所有public型別的成員變數

Field[] fields = clazz.getFields();

3.獲取指定的成員變數,public型別

Field nameField = clazz.getField("mName");
//修改成員變數mName的值為Tom
nameField.set(personInfo, "Tom");
//得到成員變數nName的值
String name = nameField.get(personInfo);

4.獲取指定的成員變數,private型別

 //得到私有的成員變數 mAge
 Field ageField = clazz.getDeclaredField("mAge");
 //設定其訪問許可權
 ageField.setAccessible(true);
 //修改成員變數 mAge 的值
 ageField.set(test, 23);
 //獲取該成員變數的值
 int age = (int) ageField.get(test);
public class PersonInfo {
    private int mAge = 18;
    public String mName = "xjp";

    private int getAge(int age) {
        return mAge;
    }

    public String getName(String msg) {
        return mName + ":" + msg;
    }
}

反射的應用場景

我們來做一個有意思的功能,在你的應用所有啟動Activity之前的地方加一個列印,打印出一些相關資訊。你可能會想到如下策略:
應用中所有的Activity都繼承自一個BaseActivity基類,基類中實現一個startActivity方法,在該方法之前加上一句列印,那麼所有startActivity的地方都呼叫基類中的方法。

但是有這麼一種情況,專案已經進行了80%,大部分Activity已經寫好了,好多Activity都不是繼承自同一個BaseActivity基類,如果需要改的話,改動地方太多,改動大,不划算。那麼有沒有一種更好的辦法,修改少,又能實現該功能的方法呢?

答案就是利用反射,在系統呼叫startActivity的地方換成我們自己的startActivity方法,這一招叫做偷樑換柱。那麼怎麼實現呢?

我們先找到Android系統的startActivity方法是怎麼實現的,該方法是Context類的方法,而Context只是一個藉口,其實現類是ContextImpl類,程式碼如下:

 @Override
    public void startActivity(Intent intent, Bundle options) {
        warnIfCallingFromSystemProcess();

        // Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
        // generally not allowed, except if the caller specifies the task id the activity should
        // be launched in.
        if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
                && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
            throw new AndroidRuntimeException(
                    "Calling startActivity() from outside of an Activity "
                    + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                    + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);
    }

最終啟動activity是有mMainThread物件的getInstrumentation()方法獲取Instrumentation物件,然後由該物件呼叫execStartActivity()方法來啟動activity。而mMainThread物件是ActivityThread型別,該類是我們的主執行緒類,裡面有有一個mInstrumentation成員變數,該成員變數屬於Instrumentation型別。

我們的思路是替換ActivityThread類總的mInstrumentation物件,使用我們自己的 Instrumentation物件。實現如下:

public class ProxyInstrumentation extends Instrumentation {

    private static final String TAG = "ProxyInstrumentation";

    // ActivityThread中原始的物件, 儲存起來
    Instrumentation mBase;

    public ProxyInstrumentation(Instrumentation base) {
        mBase = base;
    }

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {

        // Hook之前, XXX到此一遊!
        Log.d(TAG, "\n執行了startActivity, 引數如下: \n" + "who = [" + who + "], " +
                "\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " +
                "\ntarget = [" + target + "], \nintent = [" + intent +
                "], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]");

        // 開始呼叫原始的方法, 調不呼叫隨你,但是不呼叫的話, 所有的startActivity都失效了.
        // 由於這個方法是隱藏的,因此需要使用反射呼叫;首先找到這個方法
        try {
            Method execStartActivity = Instrumentation.class.getDeclaredMethod(
                    "execStartActivity",
                    Context.class, IBinder.class, IBinder.class, Activity.class,
                    Intent.class, int.class, Bundle.class);
            execStartActivity.setAccessible(true);
            return (Instrumentation.ActivityResult) execStartActivity.invoke(mBase, who,
                    contextThread, token, target, intent, requestCode, options);
        } catch (Exception e) {
            // 某該死的rom修改了  需要手動適配
            throw new RuntimeException("do not support!!! pls adapt it");
        }
    }

然後通過反射拿到ActivityThread類中的mInstrumentation,程式碼如下:

public static void attachContext() throws Exception{
        // 先獲取到當前的ActivityThread物件
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
        currentActivityThreadMethod.setAccessible(true);
        Object currentActivityThread = currentActivityThreadMethod.invoke(null);

        // 拿到原始的 mInstrumentation欄位
        Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
        mInstrumentationField.setAccessible(true);
        Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);

        // 建立代理物件
        Instrumentation evilInstrumentation = new ProxyInstrumentation(mInstrumentation);

        // 偷樑換柱
        mInstrumentationField.set(currentActivityThread, evilInstrumentation);
    }

然後在你應用的Application類中呼叫如上方法即可:

public class DemoApplication extends Application {
    private static final String TAG = "DemoApplication";

    private static Context mContext;

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = DemoApplication.this.getApplicationContext();
        String processName = mContext.getApplicationInfo().processName;
        Log.i(TAG, "onCreate: the processName=" + processName);
    }

    public static Context getContext(){
        return mContext;
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        try {
            attachContext();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void attachContext() throws Exception{
        // 先獲取到當前的ActivityThread物件
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
        currentActivityThreadMethod.setAccessible(true);
        Object currentActivityThread = currentActivityThreadMethod.invoke(null);

        // 拿到原始的 mInstrumentation欄位
        Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
        mInstrumentationField.setAccessible(true);
        Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);

        // 建立代理物件
        Instrumentation evilInstrumentation = new ProxyInstrumentation(mInstrumentation);

        // 偷樑換柱
        mInstrumentationField.set(currentActivityThread, evilInstrumentation);
    }
}

列印如下:
這裡寫圖片描述