詳解Andorid開發中反射機制是怎麼一回事
1. 背景
在andorid開發中,經常遇見在某些工具類中沒有Context上下文對象時,一些系統服務的代理對象無法創建出來,舉個例子:比如在源碼(framework/base/graphics/java/android/graphics)路徑下的Canvas.java Bitmap.javaPicture.java Paint.java 類就沒有上下文對象。當時有需要調用AMS的API獲取當前界面activity的名稱作為判斷條件去修改這些工具類中的參數,但是這幾個工具類中又沒有上下文對象,怎麼辦呢?
一般情況下,獲取頂層activity名稱的方法如下:
//1. 獲取 AMS 的代理對象 ActivityManager ActivityManager am = (ActivityManager) context .getSystemService(Context.ACTIVITY_SERVICE); //2. 調用相關API 獲取頂層activity的名稱 String activityName = am.getRunningTasks(1).get(0).topActivity.getClassName();
上述代碼是在有上下文對象context的情況下,直接獲取。
而在上面提及到的Picture.java等類中, 根本就沒有上下文,怎麼搞? 這個時候我們通過java反射來實現,接下來,本篇文章就來講解一下java反射機制,並利用反射實現獲取頂層activity。
2. java反射
2.1 什麼是反射
反射(Reflection)是程序的自我分析能力,通過反射可以確定類中有哪些方法、有哪些構造方法以及有哪些成員變量,在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性;這種動態獲取信息以及動態調用對象方法的功能稱為java語言的反射機制。
2.2 什麼情況下要用反射
首先java是面向對象編程的,萬物皆對象, 當我們拿到這個類對象後,就可以訪問類中的成員變量,調用類中的方法瞭,但是呢?對於private變量和方法,它的訪問權限作用域隻在本類中,在外部類或者繼承類中,就算你創建瞭該類對象,也是無法直接調用的。來看下這個例子:
普通的一個User 類
public class User { private String name = "墨子"; //私有變量 public int age = 18; //私有方法 private int test() { System.out.println("私有成員方法"); return 1; } protected String test2() { System.out.println("protected成員方法"); return "protected"; } public static void main(String[] args) { //如果在本類中創建瞭對象,則所有的方法和變量都可以直接訪問 User user = new User(); System.out.println(user.name); //訪問私有變量 System.out.println(user.test());//調用私有方法 } }
打印結果如下:
墨子
私有成員方法
1
如果在外部內中創建該類對象,還能直接訪問私有方法和變量嗎? 測試一下:
public class PrivateTest { public static void main(String[] args) { User user = new User(); //外部內中創建的對象直接私有變量 報錯 //System.out.println(user.name); //外部內中創建的對象,直接調用private方法,也報錯 //System.out.println(user.test()); //但是可以直接訪問 protected public的方法和變量 System.out.println(user.age); System.out.println(user.test2()); } }
報錯的兩行代碼註釋掉瞭,可以把代碼復制過去驗證一下。
當然瞭,在andorid開發中,還有其他場景會使用到反射, 很多類或方法中經常加上瞭“@hide”註釋標記,這些API是不允許在第三方應用直接調用的比如:SystemProperties類在android.os下,但這個類是隱藏的,沒有系統權限(android:sharedUserId="android.uid.system")的app無法直接使用,隻能通過java反射來調用。
還有 Andorid中動態代理模式 , Android插件化(Hook)這些都會用到反射,這裡不一一說明,因為每個技術點都需要去深挖才能理解透徹。
2.3 反射的優缺點
優點:
1.可以調用私有方法、變量
2.可以調用隱藏api接口
3.能夠運行時動態獲取類的實例,提高代碼靈活性
缺點:
1.安全問題:反射可以獲取到類對應的所有方法和屬性,如果存在外部調用的情況,可能出現超出預期的調用,從而導致安全風險, 而且也破壞java面向對象編程的封裝這一特性。
2.性能問題:無論是通過字符串獲取Class、Method還是Field,都需要JVM的動態鏈接機制動態的進行解析和匹配,勢必造成性能開銷。每一次的反射調用都會造成Java安全機制進行額外的安全性驗證,造成性能開銷。反射代碼使得許多JVM的運行時優化無法進行。
3. Java反射機制API
Java反射機制API主要是 java.lang.Class類 和 java.lang.reflect包。
類 | 說明 |
java.lang.Class | 代表整個字節碼。代表一個類型,代表整個類。 |
java.lang.reflect.Constructor | 代表字節碼中的構造方法字節碼。代表類中的構造方法。 |
java.lang.reflect.Method | 代表字節碼中的方法字節碼。代表類中的方法。 |
java.lang.reflect.Field | 代表字節碼中的屬性字節碼。代表類中的成員變量(靜態變量+實例變量) |
3.1 獲取Class對象
方式 | 說明 |
對象.getClass(); | |
類名.class | |
Class.forName(“類的全路徑”); | 推薦此種寫法 |
package com.example.javademo.reflect; public class ClassObject { public static void main(String[] args) throws ClassNotFoundException { //第一種: 對象.getClass(); People people = new People(); Class clazz1 = people.getClass(); System.out.println(clazz1); //第二種: 類名.class Class clazz2 = People.class; System.out.println(clazz2); //第三種: Class.forName(包名.類名) Class clazz3 = Class.forName("com.example.javademo.reflect.People"); System.out.println(clazz3); } }
第一種:直接new出對象,然後通過對象獲取class對象, 但是你想想類對象都可以直接new出來,在外部內中除瞭private 變量和方法不能直接訪問外,都可以直接調用,我還反射幹嘛?寫那麼多代碼豈不是為難自己,沒事找事,不推薦!
第二種:通過類名.class 獲取字節碼class對象,需要導入包,依賴性比較強,不然會編譯報錯
第三種:Class的靜態方法,安全性強, 參數為:類包名+類名
運行結果如下:
class com.example.javademo.reflect.People
class com.example.javademo.reflect.People
class com.example.javademo.reflect.People
三個都是同一個Class對象,在運行期間,一個類,隻有一個Class對象產生
3.2 反射調用類構造方法
方法名解釋說明
方法名 | 解釋說明 |
public Constructor[] getConstructors() | 所有"公有的"構造方法 |
public Constructor getConstructor(Class… parameterTypes) | 獲取指定參數的共有構造方法 |
public Constructor[] getDeclaredConstructors() | 獲取所有的構造方法(包括私有、受保護、默認、公有) |
public Constructor getDeclaredConstructor(Class… parameterTypes) | :獲取"指定參數的構造方法"可以是私有的,或受保護、默認、公有; |
newInstance(Object… initargs) | 使用此 Constructor 對象表示的構造方法來創建該構造方法的聲明類的新實例,並用指定的初始化參數初始化該實例。 它的返回值是T類型,所以newInstance是創建瞭一個構造方法的聲名類的新實例對象,相當於People p = new People(); |
我們看下Demo
package com.example.javademo.reflect; public class People { private String name; private int age; public String sex; private long height; public People() { System.out.println("調用瞭public無參構造方法 執行完畢"); } public People(String name) { this.name = name; System.out.println("調用public有參構造方法 String:name = " + name); } private People(int age, String sex) { this.age = age; this.sex = sex; System.out.println("調用private有參數構造方法 age :"+ age + " sex:" + sex); } People(long height) { this.height = height; System.out.println("調用default有參數構造方法 height :"+ height ); } }
反射測試類:
package com.example.javademo.reflect; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class ReflectPeople { public static void main(String[] args) { try { //獲取People class對象 Class peopleclass = Class.forName("com.example.javademo.reflect.People"); System.out.println("**********************獲取反射後類對象*********************************"); System.out.println("反射類對象: " + peopleclass); //獲取所有構造方法 System.out.println("**********************打印所有構造方法*********************************"); Constructor[] conArray = peopleclass.getDeclaredConstructors(); for(Constructor c : conArray){ System.out.println(c); } System.out.println("**************第一種:通過反射調用默認無參構造方法並創建people類實例對象***************************"); //返回一個 Constructor對象, 該對象反映Constructor對象表示的類的指定的公共類函數。 Object constructorPublic = peopleclass.getConstructor(); System.out.println("無參構造方法的對象:" + peopleclass.getConstructor()); //使用此 Constructor對象表示的構造函數,使用指定的初始化參數來創建和初始化構造函數的聲明類的新實例。 //newInstance()方法內部實際上調用瞭無參數構造方法,必須保證無參構造存在才可以 Object peopleobject1 = ((Constructor) constructorPublic).newInstance(); System.out.println("創建people1對象新實例: " + (People)peopleobject1); System.out.println("**************第二種:通過反射調用public有參構造方法並創建People類實例對象***************************"); Object peopleobject2 = peopleclass.getConstructor(String.class).newInstance("甲方"); System.out.println("創建people2對象新實例: " + (People)peopleobject2); System.out.println("**************第三種:通過反射調用private有參構造方法並創建People類實例對象***************************"); //註意,如果訪問protect private相關的構造方法,需要用getDeclaredConstructor這個API //返回一個 Constructor對象,該對象反映 Constructor對象表示的類或接口的指定類函數。 Constructor constructorPrivate = peopleclass.getDeclaredConstructor(int.class, String.class); //訪問私有構造方法,這句話一定要寫,不然就會報錯java.lang.IllegalAccessException constructorPrivate.setAccessible(true); System.out.println("有參private私有構造方法的對象:" + peopleclass.getDeclaredConstructor(int.class, String.class)); Object peopleobject3 = constructorPrivate.newInstance(20, "men"); System.out.println("創建people3對象新實例: " + peopleobject3); System.out.println("**************第四種:通過反射調用default有參構造方法並創建People類實例對象***************************"); Constructor constructorDefault = peopleclass.getDeclaredConstructor(long.class); //訪問public protect default構造方法,這句話可以不用寫 //constructorDefault.setAccessible(true); System.out.println("有參default構造方法的對象:" + peopleclass.getDeclaredConstructor(long.class)); Object peopleobject4 = constructorDefault.newInstance(185); System.out.println("創建people4對象新實例: " + peopleobject4); } catch (ClassNotFoundException | NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }
打印log:
**********************獲取反射後類對象*********************************
反射類對象: class com.example.javademo.reflect.People
**********************打印所有構造方法*********************************
com.example.javademo.reflect.People(long)
private com.example.javademo.reflect.People(int,java.lang.String)
public com.example.javademo.reflect.People(java.lang.String)
public com.example.javademo.reflect.People()
**************第一種:通過反射調用默認無參構造方法並創建people類實例對象***************************
無參構造方法的對象:public com.example.javademo.reflect.People()
調用瞭public無參構造方法 執行完畢
創建people1對象新實例: com.example.javademo.reflect.People@2a139a55
**************第二種:通過反射調用public有參構造方法並創建People類實例對象***************************
調用public有參構造方法 String:name = 甲方
創建people2對象新實例: com.example.javademo.reflect.People@15db9742
**************第三種:通過反射調用private有參構造方法並創建People類實例對象***************************
有參private私有構造方法的對象:private com.example.javademo.reflect.People(int,java.lang.String)
調用private有參數構造方法 age :20 sex:men
創建people3對象新實例: com.example.javademo.reflect.People@6d06d69c
**************第四種:通過反射調用default有參構造方法並創建People類實例對象***************************
有參default構造方法的對象:com.example.javademo.reflect.People(long)
調用default有參數構造方法 height :185
創建people4對象新實例: com.example.javademo.reflect.People@7852e922
通過上面的例子,總結:
1. 反射Class對象, 反射Constructor對象, 反射類實例對象 是3個不同的對象,如果剛開始不熟悉API,建議分開創建;
2.getConstructor() 和 getDeclaredConstructor(類<?>… parameterTypes)方法的區別 前者是獲取public類型構造方法的Constructor對象,後者是獲取全類型(public protect default private)類型的構造方法的Constructor對象
3.反射私有構造方法的時候,一定要setAccessible(true),不然會報錯java.lang.IllegalAccessException錯誤 * true表示:禁止java語言使用時安全檢查,暴力訪問
4. 調用Constructor類中newInstance()這個方法時,註意newInstance沒有帶參數(不帶參數可以不寫,或寫成null 都可以行) 此方法內部實際上調用瞭無參數構造方法,必須保證無參構造存在才可以,否則會拋出java.lang.InstantiationException異常。
3.3 反射調用類中方法
方法名 | 說明 |
public Method[] getMethods() | 獲取所有"公有方法";(包含瞭父類的方法也包含Object類) |
public Method getMethod(String name,Class<?>… parameterTypes) | 獲取指定參數的公共成員方法 類對象。 |
public Method[] getDeclaredMethods() | 獲取所有的成員方法,包括私有的(不包括繼承的) |
public Method getDeclaredMethod(String name,Class<?>… parameterTypes) | 獲取指定參數的方法對象 |
public Object invoke(Object obj,Object… args) | 在具有指定參數的 方法對象上調用此 方法 對象表示的底層方法。 |
我們看看這個例子
package com.example.javademo.reflect; public class MethodTest { public MethodTest() { System.out.println("調用 MethodTest默認的共有構造方法"); } String fruit = "蘋果"; int milliliter = 0; char game = 'X'; String mood = "開心"; public void eat(String fruit) { this.fruit = fruit; System.out.println("調用public成員方法 eat 吃的水果為 :" + fruit); } int drink(int milliliter) { this.milliliter = milliliter; System.out.println("調用default成員方法 drink 喝水毫升量 :" + milliliter); return milliliter; } protected void play(char game) { this.game = game; System.out.println("調用protected成員方法 play 玩遊戲 :" + game); } private String happy(String mood) { this.mood = mood; System.out.println("調用private成員方法 happy 今日心情:" + mood); return mood; } }
反射測試類
package com.example.javademo.reflect; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ReflectMethod { public static void main(String[] args) { try { System.out.println("**********************獲取反射後類對象*********************************"); //獲取方法類的class對象 Class methodClass = Class.forName("com.example.javademo.reflect.MethodTest"); System.out.println("反射類對象 :" + methodClass); System.out.println("**********************獲取反射類中所有的成員方法**************************"); //返回一個"方法數組對象":反射類或接口中所有聲明的方法,包括公共,保護,默認(包)訪問和私有方法,但不包括繼承的方法。 Method[] methods = methodClass.getDeclaredMethods(); for (Method m : methods) { System.out.println(m); } System.out.println("***************第一種*******調用反射類中public成員方法**************************"); //返回一個方法對象,它表示此表示的類或接口的指定聲明的方法對象。 //第一個參數為方法名,第二個可變參數 : 為該方法傳入的參數 Method methodPublic = methodClass.getDeclaredMethod("eat", String.class); //PUBLIC :1 PRIVATE:2 PROTECTED:4 PACKAGE(default):8 System.out.println("打印該public方法的修飾符 :" + methodPublic.getModifiers()); System.out.println("打印該public方法對象 表示方法的名稱: "+methodPublic.getName()); System.out.println("打印該public方法對象 可執行文件的形式參數的數量 :" + methodPublic.getParameterCount()); //實例化一個MethodTest類對象,每個類有一個默認的無參構造方法,可以寫或不寫 //但是如果類中有帶參數的構造方法,那無參構造方法是必須要寫出來的,不然new對象的時候,就會報錯 MethodTest objMethodTest = (MethodTest)methodClass.getDeclaredConstructor().newInstance(); System.out.println("MethodTest類對象 :" + objMethodTest); //invoke方法 第一個參數: 從底層方法被調用的對象 如果底層方法是靜態的,則第一個參數obj對象傳遞null。 //第二個參數: 該方法調用的參數 //如果方法正常完成,則返回的值將返回給調用者. Object objectreturn1 = methodPublic.invoke(objMethodTest, "菠蘿"); //eat方法返回值為 null System.out.println("eat方法 返回值為 :" + objectreturn1); System.out.println("***************第二種*******調用反射類中private成員方法**************************"); Method methodPrivate = methodClass.getDeclaredMethod("happy", String.class); System.out.println("打印該private方法的修飾符 :" + methodPrivate.getModifiers()); System.out.println("打印該private方法對象 表示方法的名稱: "+methodPrivate.getName()); System.out.println("打印該public方法對象 可執行文件的形式參數的數量 :" + methodPrivate.getParameterCount()); //如果是訪問私有方法,則要加上這句話,禁止java語言使用時安全檢查 methodPrivate.setAccessible(true); Object objectreturn2 = methodPrivate.invoke(objMethodTest, "超級nice"); System.out.println("happy方法 返回值為:"+objectreturn2); System.out.println("***************第三種*******調用反射類中protected成員方法**************************"); Method methodProtected = methodClass.getDeclaredMethod("play", char.class); System.out.println("打印該protected方法的修飾符 :" + methodProtected.getModifiers()); System.out.println("打印該protected方法對象 表示方法的名稱: "+methodProtected.getName()); System.out.println("打印該protected方法對象 可執行文件的形式參數的數量 :" + methodProtected.getParameterCount()); Object objectreturn3 = methodProtected.invoke(objMethodTest, 'Y'); System.out.println("play方法 返回值為:"+objectreturn3); System.out.println("***************第四種*******調用反射類中default成員方法**************************"); Method methodDefault = methodClass.getDeclaredMethod("drink", int.class); System.out.println("打印該protected方法的修飾符 :" + methodDefault.getModifiers()); System.out.println("打印該protected方法對象 表示方法的名稱: "+methodDefault.getName()); System.out.println("打印該protected方法對象 可執行文件的形式參數的數量 :" + methodDefault.getParameterCount()); Object objectreturn4 = methodDefault.invoke(objMethodTest, 180); System.out.println("drink方法 返回值為:"+objectreturn4); } catch (ClassNotFoundException | NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }
打印log:
**********************獲取反射後類對象*********************************
反射類對象 :class com.example.javademo.reflect.MethodTest
**********************獲取反射類中所有的成員方法**************************
public void com.example.javademo.reflect.MethodTest.eat(java.lang.String)
private java.lang.String com.example.javademo.reflect.MethodTest.happy(java.lang.String)
protected void com.example.javademo.reflect.MethodTest.play(char)
int com.example.javademo.reflect.MethodTest.drink(int)
***************第一種*******調用反射類中public成員方法**************************
打印該public方法的修飾符 :1
打印該public方法對象 表示方法的名稱: eat
打印該public方法對象 可執行文件的形式參數的數量 :1
調用 MethodTest默認的共有構造方法
MethodTest類對象 :com.example.javademo.reflect.MethodTest@15db9742
調用public成員方法 eat 吃的水果為 :菠蘿
eat方法 返回值為 :null
***************第二種*******調用反射類中private成員方法**************************
打印該private方法的修飾符 :2
打印該private方法對象 表示方法的名稱: happy
打印該public方法對象 可執行文件的形式參數的數量 :1
調用private成員方法 happy 今日心情:超級nice
happy方法 返回值為:超級nice
***************第三種*******調用反射類中protected成員方法**************************
打印該protected方法的修飾符 :4
打印該protected方法對象 表示方法的名稱: play
打印該protected方法對象 可執行文件的形式參數的數量 :1
調用protected成員方法 play 玩遊戲 :Y
play方法 返回值為:null
***************第四種*******調用反射類中default成員方法**************************
打印該protected方法的修飾符 :0
打印該protected方法對象 表示方法的名稱: drink
打印該protected方法對象 可執行文件的形式參數的數量 :1
調用default成員方法 drink 喝水毫升量 :180
drink方法 返回值為:180
代碼中基本都做瞭註釋,通過上面的例子,總結:
1. 反射class對象, 反射Method對象 反射類實例對象 是3個不同的對象,要區分開
2. 在反射調用private方法時,一定要禁止java語言使用時安全檢查,要setAccessible(true),其他修飾符方法可以不用加這句話
3. (反射Method對象).invoke(Object obj, Object …args) 方法參數的理解 :
1. 第一個參數為 通過用反射方法構造出來的 反射類實例對象
2. 第二個可變參數為: 該方法需要傳入的參數
3. 如果方法正常完成,則將返回值返回
3.4 反射類中的成員變量
方法 | 說明 |
Field[] getFields() | 獲取所有的"公有字段" |
public Field getField(String fieldName) | 獲取指定參數的共有字段 |
.Field[] getDeclaredFields() | 獲取所有字段,包括:私有、受保護、默認、公有; |
public Field getDeclaredField(String fieldName) | 獲取某個字段(包括私有的) |
set(Object obj, Object value) | 將指定Field 對象 設置為指定的新值 |
我們通過Demo講解:
package com.example.javademo.reflect; /** * desc : 供反射調用的FieldTest類 * version: 1.0 */ public class FieldTest { public FieldTest() { System.out.println("調用FieldTest 默認無參構造方法"); } public int age = 18; private String name = "張三"; String sex = "男"; protected String phoneNum = "123456789"; public int getAge() { return age; } public String getName() { return name; } public String getSex() { return sex; } public String getPhoneNum() { return phoneNum; } public void setAge(int age) { this.age = age; } public void setName(String name) { this.name = name; } public void setSex(String sex) { this.sex = sex; } public void setPhoneNum(String phoneNum) { this.phoneNum = phoneNum; } @Override public String toString() { return "FieldTest{" + "age=" + age + ", name='" + name + '\'' + ", sex='" + sex + '\'' + ", phoneNum='" + phoneNum + '\'' + '}'; }
反射測試類:
package com.example.javademo.reflect; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * desc : 反射FiledTest類中各字段(Field)的實現類 * version: 1.0 */ public class ReflectField { public static void main(String[] args) { //用正常的new對象的方式,給字段賦值,打印對象值, 測試用 /* FieldTest objcetFieldTest = new FieldTest(); objcetFieldTest.setAge(15); objcetFieldTest.setName("xiaomao"); objcetFieldTest.setPhoneNum("12345678"); objcetFieldTest.setSex("男"); System.out.println(objcetFieldTest.toString());*/ //通過反射的方式,修改字段值 try { //通過反射獲取Class對象 Class FieldTestClass = Class.forName("com.example.javademo.reflect.FieldTest"); //用反射的方式來修改字段(成員變量)的值 System.out.println("************獲取所有的字段(包括共有、私有、受保護、默認的)********************"); Field[] fields = FieldTestClass.getDeclaredFields(); for (Field f : fields) { System.out.println(f); } //用反射的方式來構造FieldTestl類對象 FieldTest objFieldTest = (FieldTest) FieldTestClass.getDeclaredConstructor().newInstance(); System.out.println("*************反射獲取public字段並調用***********************************"); //返回一個 Field對象,它表示的類或接口的指定已聲明字段對象。 Field fieldPublic = FieldTestClass.getDeclaredField("age"); System.out.println("該字段的修飾符 :" + fieldPublic.getModifiers()); System.out.println("該字段的名稱 :" + fieldPublic.getName()); /* * 獲取字段的值:有 getInt(Object obj) getLong(Object obj) get(Object obj)等方法 * * obj :為通過反射方法 構造出來的類對象 * */ //獲取int類型 實例字段的默認值 System.out.println("通過反射方式獲取age默認值 : " + fieldPublic.getInt(objFieldTest)); /* 設置字段的值: Field public void set(Object obj,Object value): 參數說明: 1.obj: 通過反射方法 構造出來的類對象 2.value:要為字段設置的值; */ fieldPublic.set(objFieldTest, 20); //通過反射的方式調用 getAge() 方法 Method method = FieldTestClass.getDeclaredMethod("getAge"); Object objectReturn = method.invoke(objFieldTest); System.out.println("通過反射方式set新值之後, age的值 :" + objectReturn); System.out.println("*************反射獲取private字段並調用***********************************"); //在項目實際中,因為私有變量和方法,無法在外部類去調用它們,所以反射在這點上就派上用場瞭 Field fieldPrivate = FieldTestClass.getDeclaredField("name"); //private字段一定要調用setAccessible(true) 禁止java語言使用時安全檢查 fieldPrivate.setAccessible(true); System.out.println("通過反射方式獲取age默認值 : " + fieldPrivate.get(objFieldTest)); //修改private變量name的值 fieldPrivate.set(objFieldTest, "李四"); //通過反射的方式調用 getName() 方法 Method objectMethod = FieldTestClass.getDeclaredMethod("getName"); Object objectReturn1 = objectMethod.invoke(objFieldTest); System.out.println("通過反射方式set新值之後, name的值 :" + objectReturn1); } catch (ClassNotFoundException | NoSuchMethodException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } }
打印log:
************獲取所有的字段(包括共有、私有、受保護、默認的)********************
public int com.example.javademo.reflect.FieldTest.age
private java.lang.String com.example.javademo.reflect.FieldTest.name
java.lang.String com.example.javademo.reflect.FieldTest.sex
protected java.lang.String com.example.javademo.reflect.FieldTest.phoneNum
調用FieldTest 默認無參構造方法
*************反射獲取public字段並調用***********************************
該字段的修飾符 :1
該字段的名稱 :age
通過反射方式獲取age默認值 : 18
通過反射方式set新值之後, age的值 :20
*************反射獲取private字段並調用***********************************
通過反射方式獲取age默認值 : 張三
通過反射方式set新值之後, name的值 :李四
通過例子,總結如下:
1. 反射class對象, 反射Field對象 反射類實例對象 是3個不同的對象,要區分開
2.在反射調用private字段時,一定要禁止java語言使用時安全檢查,要setAccessible(true)
3. Field public void set(Object obj,Object value):
參數說明:
1.obj: 通過反射方法 構造出來的類對象
2.value:要為字段設置的值;
3.5 反射類中靜態方法和變量
怎麼修改static修飾的變量呢?靜態變量和方法是在類的實例化之前就進行瞭初始化(類的初始化階段),靜態變量和方法是從屬於類本身的,跟new出來的具體對象無關,所以我們獲取變量就不需要傳入對象,直接傳入null即可。
Demo如下:
package com.example.javademo.reflect; public class StaticFieldTest { public StaticFieldTest() { System.out.println("調用 StaticFieldTest 無參構造方法"); } //靜態變量 public static int age = 15; public static void setAge(int age) { StaticFieldTest.age = age; } //靜態方法 public static int getAge() { return age; } }
反射測試類:
package com.example.javademo.reflect; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ReflectStatic { public static void main(String[] args) { try { //獲取 StaticFieldTest class對象 Class staticClazz = Class.forName("com.example.javademo.reflect.StaticFieldTest"); //獲取靜態Field對象 Field staticField = staticClazz.getDeclaredField("age"); //因為是pulic修飾符,這句話也可以不用寫 staticField.setAccessible(true); //通過set方法,修改值, 靜態變量 是從屬於類,可以不用傳入類實例對象,直接傳入null staticField.set(null, 25); //驗證修改後的值 Method agetet = staticClazz.getDeclaredMethod("getAge"); // 靜態方法 是從屬於類, 可以不用傳入類實例對象,直接傳入null int agetest = (int) agetet.invoke(null); System.out.println("**********************打印修改後的age值**************************"); System.out.println(agetest); } catch (ClassNotFoundException | NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } }
打印結果:
**********************打印修改後的age值**************************
25
總結如下:
1. 對於static 變量和方法,因為它是從屬於類本身,在類的實例化之前就進行瞭初始化,當傳入obj對象參數的時候,直接傳入null即可。
4. 反射在Android中的應用
4.1反射實現獲取頂層activity的名稱
如前言背景中的問題描述,有瞭上面的理論知識做為基礎,實現代碼如下:
/** * @description: 通過反射方式獲取 頂層activity名稱 * @param: null * @return: boolean 如果是目標activity 則返回true */ public boolean isTopTargetActivity() { try { //通過包名和類名反射獲取 class 對象 Class activityThreadClass = Class.forName("android.app.ActivityThread"); //第二種: 反射創建 activiyThread對象 反射調用靜態方法,第一個參數obj對象傳遞 null Object activityThreadObj = activityThreadClass.getMethod("currentActivityThread").invoke(null); Log.e("test", "====activityThreadObj: "+activityThreadObj); //獲取 mActivities Field對象 final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>(); Field activitiesField = activityThreadClass.getDeclaredField("mActivities"); //禁止Java語言訪問使用時進行檢查 activitiesField.setAccessible(true); //返回該所表示的字段的值 Field Map activities = (Map) activitiesField.get(activityThreadObj); for (Object activityClientRecord : activities.values()) { //獲取 ActivityClientRecord class對象 Class activityRecordClass = activityClientRecord.getClass(); //獲取 boolean paused 字段 對象 Field pausedField = activityRecordClass.getDeclaredField("paused"); //允許暴力訪問,禁止java語言運行時安全檢查 pausedField.setAccessible(true); //Activity onResume的判斷條件 if (!pausedField.getBoolean(activityClientRecord)) { //獲取 Activity activity field對象 Field activityField = activityRecordClass.getDeclaredField("activity"); activityField.setAccessible(true); //獲取當前顯示的activity的對象 Activity activity = (Activity) activityField.get(activityClientRecord); //獲取當前activity的名稱 String className = activity.getClass().toString(); Log.e("test", "====通過反射獲取的className===="+className); if ("cn.com.test.activity.MainActivity".equals(className)) { return true; } } } } catch (ClassNotFoundException e) { Log.e("test", "======ClassNotFoundException===="+e.getMessage()); } catch (InvocationTargetException e) { Log.e("test", "======InvocationTargetException===="+e.getMessage()); } catch (NoSuchMethodException e) { Log.e("test", "======NoSuchMethodException===="+e.getMessage()); } catch (NoSuchFieldException e) { Log.e("test", "======NoSuchFieldException===="+e.getMessage()); } catch (IllegalAccessException e) { Log.e("test", "======IllegalAccessException===="+e.getMessage()); } return false; }
4.2 反射調用SystemProperties中set get方法
再者,比如第三方應用是無法直接調用SystemProperties中 get set 方法,我們也可以通過反射來實現, 這個工具類已經實現好,可以拿去直接用:
public class SystemPropertiesUtils { private static final String TAG = "SystemPropertiesUtils"; private static Class<?> mClassType = null; private static Method mGetMethod = null; private static Method mGetIntMethod = null; private static Method mGetBooleanMethod = null; private static Class<?> getSystemPropertiesClass() throws ClassNotFoundException { if (mClassType == null) { mClassType = Class.forName("android.os.SystemProperties"); } return mClassType; } private static Method getMethod() throws Exception { if (mGetMethod == null) { Class clazz = getSystemPropertiesClass(); mGetMethod = clazz.getDeclaredMethod("get", String.class); } return mGetMethod; } private static Method getIntMethod() throws Exception { if (mGetIntMethod == null) { Class clazz = getSystemPropertiesClass(); mGetIntMethod = clazz.getDeclaredMethod("getInt", String.class, int.class); } return mGetIntMethod; } private static Method getBooleanMethod() throws Exception { if (mGetBooleanMethod == null) { Class clazz = getSystemPropertiesClass(); mGetBooleanMethod = clazz.getDeclaredMethod("getBoolean", String.class, boolean.class); } return mGetBooleanMethod; } public static String get(String key, String def) { try { String value = (String) getMethod().invoke(null, key); if (!TextUtils.isEmpty(value)) { return value; } } catch (Exception e) { Log.d(TAG, "Unable to read system properties"); } return def; } public static int getInt(String key, int def) { int value = def; try { value = (int) getIntMethod().invoke(null, key, def); } catch (Exception e) { Log.d(TAG, "Unable to read system properties"); } return value; } public static boolean getBoolean(String key, boolean def) { boolean value = def; try { value = (Boolean) getBooleanMethod().invoke(null, key, def); } catch (Exception e) { Log.d(TAG, "Unable to read system properties"); } return value; } }
4.3 通過反射創建Bitmap縮略圖
public static Bitmap createVideoThumbnail(String filePath) { // MediaMetadataRetriever is available on API Level 8 // but is hidden until API Level 10 Class<?> clazz = null; Object instance = null; try { clazz = Class.forName("android.media.MediaMetadataRetriever"); instance = clazz.newInstance(); Method method = clazz.getMethod("setDataSource", String.class); method.invoke(instance, filePath); // The method name changes between API Level 9 and 10. if (Build.VERSION.SDK_INT <= 9) { return (Bitmap) clazz.getMethod("captureFrame").invoke(instance); } else { byte[] data = (byte[]) clazz.getMethod("getEmbeddedPicture").invoke(instance); if (data != null) { Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); if (bitmap != null) return bitmap; } return (Bitmap) clazz.getMethod("getFrameAtTime").invoke(instance); } } catch (IllegalArgumentException ex) { // Assume this is a corrupt video file } catch (RuntimeException ex) { // Assume this is a corrupt video file. } catch (InstantiationException e) { Log.e(TAG, "createVideoThumbnail", e); } catch (InvocationTargetException e) { Log.e(TAG, "createVideoThumbnail", e); } catch (ClassNotFoundException e) { Log.e(TAG, "createVideoThumbnail", e); } catch (NoSuchMethodException e) { Log.e(TAG, "createVideoThumbnail", e); } catch (IllegalAccessException e) { Log.e(TAG, "createVideoThumbnail", e); } finally { try { if (instance != null) { clazz.getMethod("release").invoke(instance); } } catch (Exception ignored) { } } return null; }
MediaMetadataRetriever.java 類中有個public無參構造方法, 可以通過反射方式clazz.getDeclaredConstructor().newInstance() 拿到這個 類對象
接下來,就是獲取Method Field 對象 再通過invoke set 方法去修改方法 變量的值。
5. 總結
反射在Andorid中開發應用的比較多,Class對象, Constructor 對象,Method 對象 Field對象各自的常用API要理解並熟練運用, 結合源碼多閱讀多仿寫,相信你也可以carry住反射。
到此這篇關於詳解Andorid開發中反射機制是怎麼一回事的文章就介紹到這瞭,更多相關Andorid反射機制內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 簡單易懂Java反射的setAccessible()方法
- Java中反射的學習筆記分享
- 新手瞭解java 反射基礎知識
- Java如何通過反射獲取Constructor、Field、Method對象
- Java中反射的"暴破"機制(SetAccessible方法)詳解