Java反射以及在Android中的使用

文章內(nèi)容盡可能的詳細(xì)啦撮,方便自己后續(xù)查閱谭网。

一、反射概念

? java反射是指赃春,在運行狀態(tài)中愉择,對于任意一個類,都能知道這個類的所有屬性及方法聘鳞,對于任何一個對象薄辅,都能調(diào)用他的任何一個方法和屬性,這種動態(tài)獲取新的及動態(tài)調(diào)用對象的方法的功能叫做反射.

二抠璃、Class 類 (java.lang.Class)

? Java中萬物皆為對象,每個類也是一個對象脱惰,每個類的java文件在編譯的時候會產(chǎn)生同名的.class文件搏嗡,這個.class文件包含了這個java類的元數(shù)據(jù)信息,包括成員變量拉一,屬性采盒,接口,方法等蔚润,生成.class文件的同時會產(chǎn)生其對應(yīng)的Class對象磅氨,并放在.class文件的末尾。當(dāng)我們new一個新對象或者引用靜態(tài)成員變量時嫡纠,Java虛擬機(JVM)中的類加載器子系統(tǒng)會將對應(yīng)Class對象加載到JVM中烦租,然后JVM再根據(jù)這個類型信息相關(guān)的Class對象創(chuàng)建我們需要實例對象或者提供靜態(tài)變量的引用值,在JVM中都只有一個Class對象除盏,即在內(nèi)存中每個類有且只有一個相對應(yīng)的Class對象叉橱。

三、在Android中的應(yīng)用場景

在Android 中者蠕,處于安全考慮窃祝,google會對系統(tǒng)的某些方法使用@hide或者使用Private修飾,導(dǎo)致我們沒辦法正常調(diào)用此類方法踱侣,但是有些場景我們又需要使用這些方法粪小,這個時候就可以直接通過反射來調(diào)用修改。

比如 Android 中 StorageManager 類中的 getVolumePaths() 方法抡句,該方法為隱藏方法探膊,沒辦法正常調(diào)用,但是在實際使用中我們也可能用上玉转,如果你有系統(tǒng)權(quán)限突想,那你就可以像 Android SD卡及U盤插拔狀態(tài)監(jiān)聽及內(nèi)容讀取 這樣為所欲為,如果沒有權(quán)限,那你就可以通過反射來實現(xiàn)了猾担,Demo放在最后袭灯,認(rèn)真看完,你也會:grin:

image

在Android9.0以后绑嘹,Google對反射也做了限制稽荧,Google對反射的方法做了劃分,并針對不同的等級的隱藏方法做了反射限制工腋。

白名單:SDK
淺灰名單:仍可以訪問的非 SDK 函數(shù)/字段姨丈。
深灰名單:
對于目標(biāo) SDK 低于 API 級別 28 的應(yīng)用,允許使用深灰名單接口擅腰。
對于目標(biāo) SDK 為 API 28 或更高級別的應(yīng)用:行為與黑名單相同
黑名單:受限蟋恬,無論目標(biāo) SDK 如何。 平臺將表現(xiàn)為似乎接口并不存在趁冈。 例如歼争,無論應(yīng)用何時嘗試使用接口,平臺都會引發(fā) NoSuchMethodError/NoSuchFieldException渗勘,即使應(yīng)用想要了解某個特殊類別的字段/函數(shù)名單沐绒,平臺也不會包含接口。

名單列表

四旺坠、具體使用

我們先新建一個Person類和Man類乔遮,需要注意一下他們的繼承關(guān)系及成員變量和方法的修飾符 (正常情況下不會這么寫,我方便后面方法說明取刃,就刻意的給了不同的修飾符

Person.java:

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.1蹋肮、 獲取類的Class對象

前面說到過,java虛擬機(JVM)會根據(jù)這個類型信息相關(guān)的Class對象創(chuàng)建我們需要實例對象或者提供靜態(tài)變量的引用值蝉衣,所以括尸,我們要用到反射,就首先要拿到這個類的Class對象病毡,獲取Class對象有下面三種方法

1濒翻、直接通過類.class,獲取其Class對象
Class manClass = Man.class;
2啦膜、通過Class的forName方法

該方法如果路徑找不到會拋出 ClassNotFoundException 異常

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

通過打印,可以看到三者獲取結(jié)果一致僧家。

image

拿到其Class對象雀摘,可以直接通過 newInstance() 方法,獲取到類的實例對象八拱,并對其操作

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

結(jié)果:

image

你可能有疑惑阵赠,繞來繞去又繞回來了涯塔,干嘛不直接new一個對象,非要繞一大圈,其實我們這里最主要的是拿到其Class對象清蚀,然后用Class對象去執(zhí)行私有方法或設(shè)置私有變量匕荸。

3.2、通過反射獲取構(gòu)造方法

先貼結(jié)論枷邪,可以跟著后面的demo看結(jié)論榛搔,可能就會比較清晰。

構(gòu)造方法只針對本類

方法 說明
getConstructors() 獲取當(dāng)前類公有構(gòu)造方法
getConstructor(Class<?>... parameterTypes) 獲取參數(shù)類型匹配的公有構(gòu)造方法
getDeclaredConstructors() 獲取當(dāng)前類所有構(gòu)造方法东揣,包括私有践惑。
getDeclaredConstructor(Class<?>... parameterTypes) 獲取所有參數(shù)類型匹配的構(gòu)造方法(公有+私有

我這里貼心的再貼一下我們寫的Man類的構(gòu)造方法,可以看到一個公有的無參和一個公有的單參構(gòu)造方法嘶卧,一個私有的雙參構(gòu)造方法尔觉。

public Man() {
}

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

private Man(int phoneNum, String address) {
    this.address = address;
    this.phoneNum = phoneNum;
}
1、Constructor<?>[] getConstructors()

獲取當(dāng)前類中所有public修飾的構(gòu)造方法芥吟。

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

結(jié)果:

image

從打印的結(jié)果可以看到穷娱,我們拿到了一個無參的構(gòu)造方法和一個String參數(shù)的構(gòu)造方法,而這兩個構(gòu)造方法剛好是用Public修飾的运沦。

2、Constructor<?>[] getDeclaredConstructors()

拿到該類的所有構(gòu)造方法配深,不管修飾符是啥携添。

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

結(jié)果:

image

不用說了吧,全部都拿到了Bㄒ丁烈掠!

3、Constructor<T> getConstructor(Class<?>... parameterTypes)

根據(jù)參數(shù)類型Class對象缸托,只能獲取指定公有構(gòu)造方法左敌。

    try {
        // getConstructor(String.class) 傳入對應(yīng)構(gòu)造方法的參數(shù)類型Class對象
        Constructor constructorPublic = manClass.getConstructor(String.class);
        System.out.println("getConstructor(String.class)=" + constructorPublic);
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    }

結(jié)果:

image

該方法只能獲取公有構(gòu)造,如果我們?nèi)カ@取私有的構(gòu)造方法就會就會拋 NoSuchMethodException 異常俐镐。

4矫限、Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

獲取指定參數(shù)Class對象的構(gòu)造方法,無限制佩抹,獲取公有或者私有都可以叼风。

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

結(jié)果:

image
5、創(chuàng)建對象

拿到了構(gòu)造方法我們直接調(diào)用 newInstance() 方法并傳入對應(yīng)參數(shù)可以拿到類對象棍苹。

try {
    // 調(diào)用 newInstance 傳入對應(yīng)參數(shù)无宿,獲取Man實例并進(jìn)行操作。
    Man man = (Man) constructorPublicDeclared.newInstance("Test");
    String address = man.getAddress();
    System.out.println("newInstance   address=" + address);
    } catch (InvocationTargetException e) {
       e.printStackTrace();
 }

  // 結(jié)果
  newInstance   address=Test

3.2 獲取方法

Method 類

方法 說明
getMethods() 獲取類本身及其父類所有公有方法
getMethod(String name, Class<?>... parameterTypes) 獲取類本身及其父類通過方法名參數(shù)類型指定的公有方法
getDeclaredMethods() 獲取類本身所有方法
getDeclaredMethod(String name, Class<?>... parameterTypes) 通過類本身通過方法名及參數(shù)類型獲取本類指定的方法枢里,無限制
1孽鸡、Method[] getMethods()

獲取當(dāng)前類及其父類Public方法

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

結(jié)果:

image

可以看到獲取的全是Public修飾的方法蹂午,不光獲取了自身類,其父類Person類的公有方法一樣打印出來了彬碱,你們可能會說Object類是什么鬼豆胸?Object類位于java.lang包中絮缅,是所有java類的父類未状。

2、Method[] getDeclaredMethods()

獲取本類的所有方法于毙。

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

結(jié)果:

image
3皮迟、Method getMethod(String name, Class<?>... parameterTypes)

獲取本類及父類指定方法名和參數(shù)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();
  }

結(jié)果:

image

如果你不信邪,去獲取私有方法伏尼,會報錯 NoSuchMethodException

4忿檩、Method getDeclaredMethod(String name, Class<?>... parameterTypes)

獲取本類指定方法名和參數(shù)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();
        }

結(jié)果:

image
5爆阶、方法調(diào)用

拿到方法后燥透,方法調(diào)用使用:

Object invoke(Object obj, Object... args)

就拿上面的例子

// 私有方法賦予權(quán)限
setAddress.setAccessible(true);
setAddress.invoke(manClass.newInstance(), "重慶市");

setAccessible 當(dāng)我們需要對非公有方法進(jìn)行操作的時候,需要先調(diào)用此方法賦予權(quán)限辨图,不然也會拋異常

3.3 獲取成員變量

Field 類

方法 說明
getFields() 獲取類本身及其父類所有公有成員變量
getField(String name) 獲取類本身及其父類指定的公有成員變量
getDeclaredFields() 獲取類本身所有成員變量(私有班套,公有,保護(hù))
getDeclaredField(String name) 獲取類本身指定名字的成員變量
1故河、Field[] getFields()

獲取本類及父類所有公有變量

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

結(jié)果:

image
2吱韭、Field[] getDeclaredFields()

獲取本類所有成員變量

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

結(jié)果:

image
3、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();
        }

結(jié)果:

image
4鱼的、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();
        }

結(jié)果:

image
5、成員變量賦值
// 私有成員變量要賦予權(quán)限
address.setAccessible(true);
address.set(manClass.newInstance(), "重慶市");
phoneNum.set(manClass.newInstance(), 023);

五凑阶、實戰(zhàn)

啰嗦這么久猿规,直接拿文章開頭說的那個方法動手,再看一下這個隱藏方法宙橱。

StorageManager.java ——> getVolumePaths

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

image

雖然這個方法是 Public 修飾的姨俩,但是使用了@hide注解,我們直接使用 StorageManager 對象是找不到這個方法的养匈,如下:

image

5.1哼勇、使用反射獲取

1、拿到 StorageManagerClass對象
StorageManager sm = (StorageManager)
                this.getSystemService(Context.STORAGE_SERVICE);
Class<? extends StorageManager> storageManagerClass = sm.getClass();
2呕乎、反射獲取 getVolumePaths方法
// getVolumePaths 是用public修飾积担,所以這里getMethod和getDeclaredMethod都可以
// getVolumePaths 方法沒有參數(shù),可以不填
Method method = storageManagerClass.getMethod("getVolumePaths");
3 猬仁、方法調(diào)用

如果方法非public修飾帝璧,還需要 使用 setAccessible(true) 賦予權(quán)限

String[] paths = (String[]) method.invoke(sm, null);

完整代碼:

 /**
     * 反射調(diào)用  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);
        }
    }

結(jié)果:

image

六先誉、結(jié)論

其實咋一看,反射還是很簡單的烁,主要區(qū)分私有和公有褐耳,以及獲取的是本類的還是本類加父類,當(dāng)然渴庆,還有更多方法铃芦,比如你可以通過獲取的方法,獲取它的修飾符襟雷,變量等等刃滓,方法類似,本文內(nèi)容還是比較淺耸弄,敲一遍基本就知道是咋回事了咧虎,看的話可能有點繞,建議自己動手敲一遍计呈,還有其其它獲取反射中的知識點你也可以去拓展一下砰诵,反射在Android中使用還是挺廣,后續(xù)可能會說到熱修復(fù)知識點捌显,其中也涉及到反射知識茁彭。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市扶歪,隨后出現(xiàn)的幾起案子尉间,更是在濱河造成了極大的恐慌,老刑警劉巖击罪,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異贪薪,居然都是意外死亡媳禁,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門画切,熙熙樓的掌柜王于貴愁眉苦臉地迎上來竣稽,“玉大人,你說我怎么就攤上這事霍弹『帘穑” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵典格,是天一觀的道長岛宦。 經(jīng)常有香客問我,道長耍缴,這世上最難降的妖魔是什么砾肺? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任挽霉,我火速辦了婚禮,結(jié)果婚禮上变汪,老公的妹妹穿的比我還像新娘侠坎。我一直安慰自己,他們只是感情好裙盾,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布实胸。 她就那樣靜靜地躺著,像睡著了一般番官。 火紅的嫁衣襯著肌膚如雪庐完。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天鲤拿,我揣著相機與錄音假褪,去河邊找鬼。 笑死近顷,一個胖子當(dāng)著我的面吹牛生音,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播窒升,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼缀遍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了饱须?” 一聲冷哼從身側(cè)響起域醇,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蓉媳,沒想到半個月后譬挚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡酪呻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年减宣,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片玩荠。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡漆腌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出阶冈,到底是詐尸還是另有隱情闷尿,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布女坑,位于F島的核電站填具,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏匆骗。R本人自食惡果不足惜灌旧,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一绑咱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧枢泰,春花似錦描融、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至毛甲,卻和暖如春年叮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背玻募。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工只损, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人七咧。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓跃惫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親艾栋。 傳聞我的和親對象是個殘疾皇子爆存,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容