Java反射以及在Android中的特殊應用

反射的定義以及組成java

關於反射,通常書上的定義是這樣的:JAVA反射機制是在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性和方法;對於任意一個對象,都可以調用它的任意方法和屬性;這種動態獲取信息以及動態調用對象方法的功能稱爲java語言的反射機制,這幾句解釋說明了反射的做用,動態的跟類進行交互,好比獲取隱藏屬性,修改屬性,獲取對象,建立對象或者方法等等,總之就一句話:android

反射是一種具備與類進行動態交互能力的一種機制 爲何要強調動態交互呢?由於通常狀況下都是動態加載,也就是在運行的時候纔會加載,而不是在編譯的時候,在須要的時候才進行加載獲取,或者說你能夠在任什麼時候候加載一個不存在的類到內存中,而後進行各類交互,或者獲取一個沒有公開的類的全部信息,換句話說,開發者能夠隨時隨意的利用反射的這種機制動態進行一些特殊的事情。設計模式

反射的組成數組

因爲反射最終也必須有類參與,所以反射的組成通常有下面幾個方面組成:bash

1.java.lang.Class.java:類對象;app

2.java.lang.reflect.Constructor.java:類的構造器對象;框架

3.java.lang.reflect.Method.java:類的方法對象;ide

4.java.lang.reflect.Field.java:類的屬性對象;模塊化

下面一張圖說明了關係:函數

根據虛擬機的工做原理,通常狀況下,類須要通過:加載->驗證->準備->解析->初始化->使用->卸載這個過程,若是須要反射的類沒有在內存中,那麼首先會通過加載這個過程,並在在內存中生成一個class對象,有了這個class對象的引用,就能夠發揮開發者的想象力,作本身想作的事情了。

反射的做用

前面只是說了反射是一種具備與Java類進行動態交互能力的一種機制,在Java和Android開發中,通常狀況下下面幾種場景會用到反射機制.

● 須要訪問隱藏屬性或者調用方法改變程序原來的邏輯,這個在開發中很常見的,因爲一些緣由,系統並無開放一些接口出來,這個時候利用反射是一個有效的解決方法

● 自定義註解,註解就是在運行時利用反射機制來獲取的。

●在開發中動態加載類,好比在Android中的動態加載解決65k問題等等,模塊化和插件化都離不開反射,離開了反射步履維艱。

反射的工做原理

咱們知道,每一個java文件最終都會被編譯成一個.class文件,這些Class對象承載了這個類的全部信息,包括父類、接口、構造函數、方法、屬性等,這些class文件在程序運行時會被ClassLoader加載到虛擬機中。當一個類被加載之後,Java虛擬機就會在內存中自動產生一個Class對象,而咱們通常狀況下用new來建立對象,實際上本質都是同樣的,只是這些底層原理對咱們開發者透明罷了,咱們前面說了,有了class對象的引用,就至關於有了Method,Field,Constructor的一切信息,在Java中,有了對象的引用就有了一切,剩下怎麼發揮是開發者本身的想象力所能決定的了。

反射的簡單事例

前面說了這麼多理論,下面簡單實踐一下

public class Student {
    private int age;//年齡
    private String name;//姓名
    private String address;//地址
     private static String sTest;
    public Student() {
         throw new IllegalAccessError("Access to default Constructor Error!");
    }

    private Student(int age, String name, String address) {
        this.age = age;
        this.name = name;
        this.address = address;
         sTest = "測試反射";
    }

    private int getAge() {
        return age;
    }
    
    private void setAge(int age) {
        this.age = age;
    }

    private String getName() {
        return name;
    }

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

    private String getAddress() {
        return address;
    }

    private void setAddress(String address) {
        this.address = address;
    }
    private static String getTest() {
        return sTest;
    }
}
複製代碼

在這裏爲了練習,刻意用了private來修飾成員變量和方法 下面代碼用構造器,方法和屬性和靜態方法分別來獲取一下,

public class StudentClient {
    public static void main(String[] args) throws Exception{
        Class<?> clazz=Class.forName("ClassLoader.Student");
        Constructor constructors=clazz.getDeclaredConstructor(int.class,String.class,String.class);
        constructors.setAccessible(true);
        //利用構造器生成對象
        Object mStudent=constructors.newInstance(27,"小文","北京市海定區XX號");
        System.out.println(mStudent.toString());
        //獲取隱藏的int屬性
        Field mAgeField=clazz.getDeclaredField("age");
        mAgeField.setAccessible(true);
        int age= (int) mAgeField.get(mStudent);
        System.out.println("年齡爲:"+age);
        //調用隱藏的方法
        Method getAddressMethod=clazz.getDeclaredMethod("getAge");
        getAddressMethod.setAccessible(true);
        int newage= (int) getAddressMethod.invoke(mStudent);
        System.out.println("年齡爲:"+newage);
        //調用靜態方法
        Method getTestMethod=clazz.getDeclaredMethod("getTest");
        getTestMethod.setAccessible(true);
        String result= (String) getTestMethod.invoke(null);
        System.out.println("調用靜態方法:"+result);
    }
}
複製代碼

結果以下:

你們都看得懂,應該能夠理解,有同窗說不是有不少getDeclared***和get***的方法嗎, 實際上都差很少的,只不過用的範圍不同而已,getDeclared***獲取的是僅限於本類的全部的不受訪問限制的,而get***獲取的是包括父類的但僅限於public修飾符的,Field和Method也是同樣的道理,這個你們注意一下就好,最後一個須要注意的是調用靜態方法和調用實例方法有點區別,調用實例方法必定須要一個類的實例,而調用靜態方法不須要實例的引用,其實這是JVM的在執行方法上的有所區別,JVM在執行方法的時候會建立一個堆棧,堆棧裏面保存了局部變量表以及其餘的一些必要的信息,其中局部變量表裏面也包含了局部參數,而局部參數裏面保存了當前方法的形參,若是是調用實例方法的話,那麼形參的第一個參數就是當前的類的引用了,而調用的是靜態方法的話,那麼第一個參數是爲null的,這一點沒法經過任何手段去繞過,換句話說調用實例方法必定須要一個類的引用,關於這一點,讀者能夠本身去查閱有關JVM的書籍。

固然了,反射的做用毫不止這些,在數組,泛型,設計模式等方面依然發揮了巨大的做用,但原理並無脫離上面說的,讀者能夠多查看相關源碼學習,源碼就是最好的學習資源。

反射在Android框架層的應用

這是本文須要重點說明的,衆所周知,Android中的FrameWork是用Java語言編寫的,天然離不開一些反射的影子,而利用反射更是能夠達到咱們一些常規方法難於達到的目的,再者反射也是Java層中進行Hook的重要手段,目前的插件化更是大量利用反射。

首先提出需求:如何監控Activity的建立和啓動過程? 有同窗說了,我在Activity裏面重寫生命週期方法不就能夠了嗎?實際上這個是達不到需求的,由於很簡單,這些生命週期方法的調用是在建立和啓動以後好久的事情了,裏面的生命週期方法相對於整個Activity來講是比較後面的事情,要想解決這個問題,必需要知道Activity是怎麼來,中間通過了哪一個流程,最後去了哪裏,只有明白了這些,才能知道在哪一個階段作哪些事情,咱們知道,Activity的啓動是一個IPC過程,也就是Binder機制,裏面通過了本地進程->AMS進程-->再回到本地進程,下面是實例圖:

圖畫的有些粗糙,你們將就看吧,從上面能夠看到,Activity從本地到遠程AMS之後,遠程AMS只是作了權限以及屬性的檢查,而後再回到本地進程,這纔開始真正的建立和檢查,咱們才代碼來分析一下,涉及到的類有Handler以及ActivityThread和Instrumentation類,首先從遠端進程回到本地進程以後,系統的Handler類H會發送一個消息:LAUNCH_ACTIVITY,代碼以下:省略了一些非必要代碼,否則篇幅太長,下面的代碼都是在ActivityThread.java裏面的

public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } 
複製代碼

隨後調用了handleLaunchActivity方法,handleLaunchActivity方法裏面又調用了 performLaunchActivity方法,代碼以下:

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();
        mSomeActivitiesChanged = true;

        if (r.profilerInfo != null) {
            mProfiler.setProfiler(r.profilerInfo);
            mProfiler.startProfiling();
        }

        // Make sure we are running with the most recent config.
        handleConfigurationChanged(null, null);

        if (localLOGV) Slog.v(
            TAG, "Handling launch of " + r);

        // Initialize before creating the activity
        WindowManagerGlobal.initialize();

        Activity a = performLaunchActivity(r, customIntent);
        
        ....
        }
複製代碼

在performLaunchActivity裏面終於建立了Activity了,進入performLaunchActivity裏面看看有一段很是核心的代碼:

Activity activity = null;
        try {
        //
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            //經過mInstrumentation.newActivity()方法建立了Activity,mInstrumentation是Instrumentation類的實例
            ,對象的類爲:Instrumentation.java
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }
複製代碼

咱們如今已經知道了Activity的建立了,是由Instrumentation的newActivity()方法實現,咱們看一下方法:

public Activity newActivity(Class<?> clazz, Context context, 
            IBinder token, Application application, Intent intent, ActivityInfo info, 
            CharSequence title, Activity parent, String id,
            Object lastNonConfigurationInstance) throws InstantiationException, 
            IllegalAccessException {
        Activity activity = (Activity)clazz.newInstance();
        ActivityThread aThread = null;
        activity.attach(context, aThread, this, token, 0, application, intent,
                info, title, parent, id,
                (Activity.NonConfigurationInstances)lastNonConfigurationInstance,
                new Configuration(), null, null, null);
        return activity;
    }
複製代碼

看到沒,做爲四大組件的Activity其實也是一個普通對象,也是由反射建立的,只不過因爲加入了生命週期方法,纔有組件這個活生生的對象存在, 因此說Android中反射無處不在,分析完了啓動和建立的過程,回到剛纔那個需求來講,如何監控Activity的啓動和建立呢?讀者能夠先本身想一下,首先啓動是由Handler來發送消息,具體的在裏面handlerMessage方法實現的, 這也是Handler裏面的處理代碼的順序,以下代碼:

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
複製代碼

大不了咱們本身弄一個自定義的Handler.Callback接口,而後替換掉那個H類裏面的處理接口,這樣就能夠監控Activity的啓動了,好方法,咱們來寫一下代碼:

public static void hookHandler(Context context) throws Exception {
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
        currentActivityThreadMethod.setAccessible(true);
        //獲取主線程對象
        Object activityThread = currentActivityThreadMethod.invoke(null);
        //獲取mH字段
        Field mH = activityThreadClass.getDeclaredField("mH");
        mH.setAccessible(true);
        //獲取Handler
        Handler handler = (Handler) mH.get(activityThread);
        //獲取原始的mCallBack字段
        Field mCallBack = Handler.class.getDeclaredField("mCallback");
        mCallBack.setAccessible(true);
        //這裏設置了咱們本身實現了接口的CallBack對象
        mCallBack.set(handler, new UserHandler(handler));
    }
複製代碼
public class UserHandler  implements Callback {
    //這個100通常狀況下最好也反射獲取,固然了你也能夠直接寫死,跟系統的保持一致就行了
    public static final int LAUNCH_ACTIVITY = 100;
    private Handler origin;
    public UserHandler( Handler mHandler) {
        this.origin = mHandler;
    }

    @Override
    public boolean handleMessage(Message msg) {
        if (msg.what == LAUNCH_ACTIVITY) {
        //這樣每次啓動的時候能夠作些額外的事情
            Log.d("[app]","作你想要的事情");
        }
        origin.handleMessage(msg);
        return false;
    }
}
複製代碼

好了,Activity的啓動監控就這樣了,通常寫在application裏面的attachBaseContext()方法裏面,由於這個方法時機最先。 好了,下面來講說Activity的建立的監控,前面咱們知道了,Instrumentation的newActivity方法負責建立了Activity,那麼突破口也就是在這裏了,建立爲咱們自定義的Instrumentation,而後反射替換掉就好,同時重寫newActivity方法,能夠作些事情,好比記錄時間之類,下面是代碼:

public static void hookInstrumentation() throws Exception{
        Class<?> activityThread=Class.forName("android.app.ActivityThread");
        Method currentActivityThread=activityThread.getDeclaredMethod("currentActivityThread");
        currentActivityThread.setAccessible(true);
        //獲取主線程對象
        Object activityThreadObject=currentActivityThread.invoke(null);

        //獲取Instrumentation字段
        Field mInstrumentation=activityThread.getDeclaredField("mInstrumentation");
        mInstrumentation.setAccessible(true);
        Instrumentation instrumentation= (Instrumentation) mInstrumentation.get(activityThreadObject);
        CustomInstrumentation customInstrumentation=new CustomInstrumentation(instrumentation);
        //替換掉原來的,就是把系統的instrumentation替換爲本身的Instrumentation對象
        mInstrumentation.set(activityThreadObject,CustomInstrumentation);
        Log.d("[app]","Hook Instrumentation成功");

    }
複製代碼
public class CustomInstrumentation  extends Instrumentation{
    private Instrumentation base;

    public CustomInstrumentation(Instrumentation base) {
        this.base = base;
    }

    //重寫建立Activity的方法
    @Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        Log.d("[app]","you are hook!,作本身想要的事情");
        Log.d("[app]","className="+className+" intent="+intent);
        return super.newActivity(cl, className, intent);
    }
}
複製代碼

一樣在application的attachBaseContext注入就好,固然了,Instrumentation還有其餘方法能夠重寫,你們能夠去試一試,下面是運行的結果:

看到沒,監控啓動和建立都實現了,其實這裏面也有好多擴展的,好比啓動Activity的時候,Instrumentation同樣是能夠監控的,你懂的,再次重寫方法,而後實現本身的邏輯,另外,small插件化框架就是Hook了Instrumentation來動態加載Activity的,你們有興趣能夠去看看,除了以上方法,還有不少方法能夠用相似的手段去實現,你們必定要多練習,好記性不如爛筆頭就是這個道理。

使用反射須要注意的地方

從前面能夠看出,使用反射很是方便,並且在一些特定的場合下能夠實現特別的需求,可是使用反射也是須要注意一下幾點的:

●反射最好是使用public修飾符的,其餘修飾符有必定的兼容性風險,好比這個版本有,另外的版本可能沒有

●你們都知道的Android開源代碼引發的兼容性的問題,這是Android系統開源的最大的問題,特別是那些第三方的ROM,要慎用。

●若是大量使用反射,在代碼上須要優化封裝,否則很差管理,寫代碼不只僅是實現功能,還有維護性和可讀性方法也須要增強,demo中能夠直接這樣粗糙些,在項目中仍是須要好好組織封裝下的。

今天的文章就寫到這裏,感謝你們閱讀。

相關文章
相關標籤/搜索