Java反射以及在Android中的使用

文章內容儘量的詳細,方便本身後續查閱。javascript

1、反射概念

​ java反射是指,在運行狀態中,對於任意一個類,都能知道這個類的全部屬性及方法,對於任何一個對象,都能調用他的任何一個方法和屬性,這種動態獲取新的及動態調用對象的方法的功能叫作反射.java

2、Class 類 (java.lang.Class)

​ Java中萬物皆爲對象,每一個類也是一個對象,每一個類的java文件在編譯的時候會產生同名的.class文件,這個.class文件包含了這個java類的元數據信息,包括成員變量,屬性,接口,方法等,生成.class文件的同時會產生其對應的Class對象,並放在.class文件的末尾。當咱們new一個新對象或者引用靜態成員變量時,Java虛擬機(JVM)中的類加載器子系統會將對應Class對象加載到JVM中,而後JVM再根據這個類型信息相關的Class對象建立咱們須要實例對象或者提供靜態變量的引用值,在JVM中都只有一個Class對象,即在內存中每一個類有且只有一個相對應的Class對象。android

3、在Android中的應用場景

在Android 中,處於安全考慮,google會對系統的某些方法使用@hide或者使用Private修飾,致使咱們沒辦法正常調用此類方法,可是有些場景咱們又須要使用這些方法,這個時候就能夠直接經過反射來調用修改。web

好比 Android 中 StorageManager 類中的 getVolumePaths() 方法,該方法爲隱藏方法,沒辦法正常調用,可是在實際使用中咱們也可能用上,若是你有系統權限,那你就能夠像 Android SD卡及U盤插拔狀態監聽及內容讀取 這樣隨心所欲,若是沒有權限,那你就能夠經過反射來實現了,Demo放在最後,認真看完,你也會:grin:api

在Android9.0之後,Google對反射也作了限制,Google對反射的方法作了劃分,並針對不一樣的等級的隱藏方法作了反射限制。安全

白名單:SDK
淺灰名單:仍能夠訪問的非 SDK 函數/字段。
深灰名單:
對於目標 SDK 低於 API 級別 28 的應用,容許使用深灰名單接口。
對於目標 SDK 爲 API 28 或更高級別的應用:行爲與黑名單相同
黑名單:受限,不管目標 SDK 如何。 平臺將表現爲彷佛接口並不存在。 例如,不管應用什麼時候嘗試使用接口,平臺都會引起 NoSuchMethodError/NoSuchFieldException,即便應用想要了解某個特殊類別的字段/函數名單,平臺也不會包含接口。app

名單列表ide

4、具體使用

咱們先新建一個Person類和Man類,須要注意一下他們的繼承關係及成員變量和方法的修飾符 (正常狀況下不會這麼寫,我方便後面方法說明,就刻意的給了不一樣的修飾符函數

Person.java:post

public class Person {

    public int age;
    private String name;

    public Person() {
    }

    private Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    private Person(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
複製代碼

Man.java:

public class Man extends Person {

    private String address;
    public int phoneNum;

    public Man() {
    }

    public Man(String address) {
        this.address = address;
    }

    private Man(int phoneNum, String address) {
        this.address = address;
        this.phoneNum = phoneNum;
    }

    public String getAddress() {
        return address;
    }

    private void setAddress(String address) {
        this.address = address;
    }

    public int getPhoneNum() {
        return phoneNum;
    }

    private void setPhoneNum(int phoneNum) {
        this.phoneNum = phoneNum;
    }
}
複製代碼

3.一、 獲取類的Class對象

前面說到過,java虛擬機(JVM)會根據這個類型信息相關的Class對象建立咱們須要實例對象或者提供靜態變量的引用值,因此,咱們要用到反射,就首先要拿到這個類的Class對象,獲取Class對象有下面三種方法

一、直接經過類.class,獲取其Class對象
Class manClass = Man.class;
複製代碼
二、經過Class的`forName`方法

該方法若是路徑找不到會拋出 ClassNotFoundException 異常

try {
     // forName 中要傳入完整路徑
     manClass1 = Class.forName("com.evan.reflection.Man");
   } catch (ClassNotFoundException e) {
      e.printStackTrace();
  }
複製代碼
三、經過類實例對象 `getClass()` 方法
Man main = new Man();
Class<? extends Man> manClass2 = main.getClass();
複製代碼

經過打印,能夠看到三者獲取結果一致。

拿到其Class對象,能夠直接經過 newInstance() 方法,獲取到類的實例對象,並對其操做

Man man = (Man) manClass.newInstance();
man.setPhoneNum(123);
int phoneNum = man.getPhoneNum();
System.out.println("getPhoneNum=" + phoneNum);
複製代碼

結果:

你可能有疑惑,繞來繞去又繞回來了,幹嗎不直接new一個對象,非要繞一大圈,其實咱們這裏最主要的是拿到其Class對象,而後用Class對象去執行私有方法或設置私有變量。

3.二、經過反射獲取構造方法

先貼結論,能夠跟着後面的demo看結論,可能就會比較清晰。

構造方法只針對本類

方法 說明
getConstructors() 獲取當前類公有構造方法
getConstructor(Class… parameterTypes) 獲取參數類型匹配的公有構造方法
getDeclaredConstructors() 獲取當前類全部構造方法,包括私有。
getDeclaredConstructor(Class… parameterTypes) 獲取全部參數類型匹配的構造方法(公有+私有

我這裏貼心的再貼一下咱們寫的Man類的構造方法,能夠看到一個公有的無參和一個公有的單參構造方法,一個私有的雙參構造方法。

public Man() {
}

public Man(String address) {
    this.address = address;
}

private Man(int phoneNum, String address) {
    this.address = address;
    this.phoneNum = phoneNum;
}
複製代碼
一、Constructor[] getConstructors()

獲取當前類中全部public修飾的構造方法。

    Constructor[] constructors = manClass.getConstructors();
        for (Constructor c : constructors) {
         System.out.println("getConstructors--" + c);
    }

複製代碼

結果:

從打印的結果能夠看到,咱們拿到了一個無參的構造方法和一個String參數的構造方法,而這兩個構造方法恰好是用Public修飾的。

二、Constructor[] getDeclaredConstructors()

拿到該類的全部構造方法,無論修飾符是啥。

    Constructor[] declaredConstructors = manClass.getDeclaredConstructors();
        for (Constructor c : declaredConstructors) {
            System.out.println("getDeclaredConstructors--" + c);
        }

複製代碼

結果:

不用說了吧,所有都拿到了!!

三、Constructor getConstructor(Class… parameterTypes)

根據參數類型Class對象,只能獲取指定公有構造方法。

    try {
        // getConstructor(String.class) 傳入對應構造方法的參數類型Class對象
        Constructor constructorPublic = manClass.getConstructor(String.class);
        System.out.println("getConstructor(String.class)=" + constructorPublic);
      } catch (NoSuchMethodException e) {
           e.printStackTrace();
    }
複製代碼

結果:

該方法只能獲取公有構造,若是咱們去獲取私有的構造方法就會就會拋 NoSuchMethodException 異常。

四、Constructor getDeclaredConstructor(Class… parameterTypes)

獲取指定參數Class對象的構造方法,無限制,獲取公有或者私有均可以。

  try {
         constructorPublicDeclared = manClass.getDeclaredConstructor(String.class);
         Constructor constructorPrivateDeclared2 = manClass.getDeclaredConstructor(int.classString.class);
         System.out.println("getDeclaredConstructor(String.class)--------" + constructorPublicDeclared);
         System.out.println("getDeclaredConstructor(int.class, String.class)--------" + constructorPrivateDeclared2);
     } catch (NoSuchMethodException e) {
             e.printStackTrace();
      }
複製代碼

結果:

五、建立對象

拿到了構造方法咱們直接調用 newInstance() 方法並傳入對應參數能夠拿到類對象。

try {
    // 調用 newInstance 傳入對應參數,獲取Man實例並進行操做。
    Man man = (Man) constructorPublicDeclared.newInstance("Test");
    String address = man.getAddress();
    System.out.println("newInstance   address=" + address);
    } catch (InvocationTargetException e) {
       e.printStackTrace();
 }

  // 結果
  newInstance   address=Test
複製代碼

3.2 獲取方法

Method 類

方法 說明
getMethods() 獲取類自己及其父類全部公有方法
getMethod(String name, Class… parameterTypes) 獲取類自己及其父類經過方法名參數類型指定的公有方法
getDeclaredMethods() 獲取類自己全部方法
getDeclaredMethod(String name, Class… parameterTypes) 經過類自己經過方法名及參數類型獲取本類指定的方法,無限制
一、Method[] getMethods()

獲取當前類及其父類Public方法

        Method[] methods = manClass.getMethods();
        for (Method method : methods) {
            System.out.println("getMethods--->" + method);
        }
複製代碼

結果:

能夠看到獲取的全是Public修飾的方法,不光獲取了自身類,其父類Person類的公有方法同樣打印出來了,大家可能會說Object類是什麼鬼?Object類位於java.lang包中,是全部java類的父類。

二、Method[] getDeclaredMethods()

獲取本類的全部方法。

        Method[] declaredMethods = manClass.getDeclaredMethods();
        for (Method declareMethod : declaredMethods) {
            System.out.println("getDeclaredMethods--->" + declareMethod);
        }
複製代碼

結果:

三、Method getMethod(String name, Class… parameterTypes)

獲取本類及父類指定方法名和參數Class對象的方法,好比獲取其父類的setName方法和本身的getPhoneNum方法

public void setName(String name)

public int getPhoneNum()

try {
    // 獲取Person父類SetName方法
     Method setName = manClass.getMethod("setName", String.class);
    // 獲取本身 getPhoneNum 方法
     Method getPhoneNum = manClass.getMethod("getPhoneNum");
    System.out.println("getMethod(String name, Class<?>... parameterTypes)--->" + setName);
    System.out.println("getMethod(String name, Class<?>... parameterTypes)--->" + getPhoneNum);
  } catch (NoSuchMethodException e) {
      e.printStackTrace();
  }
複製代碼

結果:

若是你不信邪,去獲取私有方法,會報錯 NoSuchMethodException

四、Method getDeclaredMethod(String name, Class… parameterTypes)

獲取本類指定方法名和參數Class對象的方法,無限制

        try {
            Method setAddress = manClass.getDeclaredMethod("setAddress", String.class);
            Method getAddress = manClass.getDeclaredMethod("getAddress");
            System.out.println("getDeclaredMethod--->" + setAddress);
            System.out.println("getDeclaredMethod--->" + getAddress);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
複製代碼

結果:

五、方法調用

拿到方法後,方法調用使用:

Object invoke(Object obj, Object... args)
複製代碼

就拿上面的例子

// 私有方法賦予權限
setAddress.setAccessible(true);
setAddress.invoke(manClass.newInstance(), "重慶市");
複製代碼

setAccessible 當咱們須要對非公有方法進行操做的時候,須要先調用此方法賦予權限,否則也會拋異常

3.3 獲取成員變量

Field 類

方法 說明
getFields() 獲取類自己及其父類全部公有成員變量
getField(String name) 獲取類自己及其父類指定的公有成員變量
getDeclaredFields() 獲取類自己全部成員變量(私有,公有,保護)
getDeclaredField(String name) 獲取類自己指定名字的成員變量
一、Field[] getFields()

獲取本類及父類全部公有變量

        Field[] fields = manClass.getFields();
        for (Field field : fields) {
            System.out.println("getFields--->" + field);
        }
複製代碼

結果:

二、Field[] getDeclaredFields()

獲取本類全部成員變量

        Field[] fields = manClass.getDeclaredFields();
        for (Field field : fields) {
            System.out.println("getDeclaredFields--->" + field);
        }
複製代碼

結果:

三、Field getField(String name)

獲取本類或者父類指定的成員變量

        try {
            Field age = manClass.getField("age");
            Field phoneNum = manClass.getField("phoneNum");
            System.out.println("getField--->" + age);
            System.out.println("getField--->" + phoneNum);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
複製代碼

結果:

四、Field getDeclaredField(String name)

獲取本類指定的成員變量,無限制

        try {
            Field address = manClass.getDeclaredField("address");
            Field phoneNum = manClass.getDeclaredField("phoneNum");
            System.out.println("getField--->" + address);
            System.out.println("getField--->" + phoneNum);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
複製代碼

結果:

五、成員變量賦值
// 私有成員變量要賦予權限
address.setAccessible(true);
address.set(manClass.newInstance(), "重慶市");
phoneNum.set(manClass.newInstance(), 023);
複製代碼

5、實戰

囉嗦這麼久,直接拿文章開頭說的那個方法動手,再看一下這個隱藏方法。

StorageManager.java ——> getVolumePaths

getVolumePaths() : 返回所有存儲卡路徑, 包括已掛載的和未掛載的

雖然這個方法是 Public 修飾的,可是使用了@hide註解,咱們直接使用 StorageManager 對象是找不到這個方法的,以下:

5.一、使用反射獲取

一、拿到 `StorageManager` 的`Class`對象
StorageManager sm = (StorageManager)
                this.getSystemService(Context.STORAGE_SERVICE);
Class<? extends StorageManager> storageManagerClass = sm.getClass();
複製代碼
二、反射獲取 `getVolumePaths`方法
// getVolumePaths 是用public修飾,因此這裏getMethod和getDeclaredMethod均可以
// getVolumePaths 方法沒有參數,能夠不填
Method method = storageManagerClass.getMethod("getVolumePaths");
複製代碼
3 、方法調用

若是方法非public修飾,還須要 使用 setAccessible(true) 賦予權限

String[] paths = (String[]) method.invoke(sm, null);
複製代碼

完整代碼:

 /**
     * 反射調用  StorageManager ——>  getVolumePaths 方法
     */

    public void getVolumePaths() {
        StorageManager sm = (StorageManager)
                this.getSystemService(Context.STORAGE_SERVICE);
        Class<? extends StorageManager> storageManagerClass = sm.getClass();
        try {
            // 反射拿到getVolumePaths方法
            Method method =
                    storageManagerClass.getDeclaredMethod("getVolumePaths");
            String[] paths = (String[]) method.invoke(sm, null);
            for (String path : paths) {
                Log.d(TAG, "getVolumePaths: path=" + path);
            }
        } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            e.printStackTrace();
            Log.e(TAG, "getVolumePaths: " + e.getLocalizedMessage(), e);
        }
    }
複製代碼

結果:

6、結論

其實咋一看,反射仍是很簡單,主要區分私有和公有,以及獲取的是本類的仍是本類加父類,固然,還有更多方法,好比你能夠經過獲取的方法,獲取它的修飾符,變量等等,方法相似,本文內容仍是比較淺,敲一遍基本就知道是咋回事了,看的話可能有點繞,建議本身動手敲一遍,還有其其它獲取反射中的知識點你也能夠去拓展一下,反射在Android中使用仍是挺廣,後續可能會說到熱修復知識點,其中也涉及到反射知識。

相關文章
相關標籤/搜索