文章內(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:
在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é)果一致僧家。
拿到其Class對象雀摘,可以直接通過 newInstance()
方法,獲取到類的實例對象八拱,并對其操作
Man man = (Man) manClass.newInstance();
man.setPhoneNum(123);
int phoneNum = man.getPhoneNum();
System.out.println("getPhoneNum=" + phoneNum);
結(jié)果:
你可能有疑惑阵赠,繞來繞去又繞回來了涯塔,干嘛不直接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é)果:
從打印的結(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é)果:
不用說了吧,全部都拿到了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é)果:
該方法只能獲取公有構(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é)果:
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é)果:
可以看到獲取的全是Public修飾的方法蹂午,不光獲取了自身類,其父類Person類的公有方法一樣打印出來了彬碱,你們可能會說Object類是什么鬼豆胸?Object類位于java.lang包中絮缅,是所有java類的父類未状。
2、Method[] getDeclaredMethods()
獲取本類的所有方法于毙。
Method[] declaredMethods = manClass.getDeclaredMethods();
for (Method declareMethod : declaredMethods) {
System.out.println("getDeclaredMethods--->" + declareMethod);
}
結(jié)果:
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é)果:
如果你不信邪,去獲取私有方法伏尼,會報錯 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é)果:
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é)果:
2吱韭、Field[] getDeclaredFields()
獲取本類所有成員變量
Field[] fields = manClass.getDeclaredFields();
for (Field field : fields) {
System.out.println("getDeclaredFields--->" + field);
}
結(jié)果:
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é)果:
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é)果:
5、成員變量賦值
// 私有成員變量要賦予權(quán)限
address.setAccessible(true);
address.set(manClass.newInstance(), "重慶市");
phoneNum.set(manClass.newInstance(), 023);
五凑阶、實戰(zhàn)
啰嗦這么久猿规,直接拿文章開頭說的那個方法動手,再看一下這個隱藏方法宙橱。
StorageManager.java ——> getVolumePaths
getVolumePaths()
: 返回全部存儲卡路徑, 包括已掛載的和未掛載的
雖然這個方法是 Public
修飾的姨俩,但是使用了@hide
注解,我們直接使用 StorageManager
對象是找不到這個方法的养匈,如下:
5.1哼勇、使用反射獲取
1、拿到 StorageManager
的Class
對象
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é)果:
六先誉、結(jié)論
其實咋一看,反射還是很簡單的烁,主要區(qū)分私有和公有褐耳,以及獲取的是本類的還是本類加父類,當(dāng)然渴庆,還有更多方法铃芦,比如你可以通過獲取的方法,獲取它的修飾符襟雷,變量等等刃滓,方法類似,本文內(nèi)容還是比較淺耸弄,敲一遍基本就知道是咋回事了咧虎,看的話可能有點繞,建議自己動手敲一遍计呈,還有其其它獲取反射中的知識點你也可以去拓展一下砰诵,反射在Android中使用還是挺廣,后續(xù)可能會說到熱修復(fù)知識點捌显,其中也涉及到反射知識茁彭。