本文介紹Android反射機制實現與原理,在介紹之前,要和Java進行比較,所以先看下Java中的反射相關知識: 一、反射的概念及在Java中的類反射 反射主要是指程式可以訪問、檢測和修改它本身狀態或行為的一種能力。在電腦科學領域,反射是一類應用,它們能夠自描述和自控制。這類應用通過某種機制來實現 ...
本文介紹Android反射機制實現與原理,在介紹之前,要和Java進行比較,所以先看下Java中的反射相關知識:
一、反射的概念及在Java中的類反射
反射主要是指程式可以訪問、檢測和修改它本身狀態或行為的一種能力。在電腦科學領域,反射是一類應用,它們能夠自描述和自控制。這類應用通過某種機制來實現對自己行為的描述和檢測,並能根據自身行為的狀態和結果,調整或修改應用所描述行為的狀態和相關的語義。
在Java中的反射機制,被稱為Reflection(大家看到這個單詞,第一個想法應該就是去開發文檔中搜一下了)。它允許運行中的Java程式對自身進行檢查,並能直接操作程式的內部屬性或方法。Reflection機制允許程式在正在執行的過程中,利用Reflection APIs取得任何已知名稱的類的內部信息,包括:package、 type parameters、 superclass、 implemented interfaces、 inner classes、 outer classes、 fields、 constructors、 methods、 modifiers等,並可以在執行的過程中,動態生成Instances、變更fields內容或喚起methods。
好,瞭解這些,那我們就知道了,我們可以利用反射機制在Java程式中,動態的去調用一些protected甚至是private的方法或類,這樣可以很大程度上滿足我們的一些比較特殊需求。你當然會問,反射機制在Android平臺下有何用處呢?
我們在進行Android程式的開發時,為了方便調試程式,並快速定位程式的錯誤點,會從網上下載到對應版本的Android SDK的源碼(這裡給大家提供一個2.3.3版本的下載鏈接)。你會發現很多類或方法中經常加上了“@hide”註釋標記,它的作用是使這個方法或類在生成SDK時不可見,那麼我們的程式可能無法編譯通過,而且在最終發佈的時候,就可能存在一些問題。
那麼,對於這個問題,第一種方法就是自己去掉Android源碼中的"@hide"標記,然後重新編譯生成一個SDK。另一種方法就是使用Java反射機制了,可以利用這種反射機制訪問存在訪問許可權的方法或修改其域。
廢話半天,該入正題了,在進入正題之前,先給上一個反射測試類的代碼,該代碼中定義了我們需要進行反射的類,該類並沒有實際的用途,僅供做為測試類。提示:本文提供的代碼,並不是Android平臺下的代碼,而是一個普通的Java程式,僅僅是對Java反射機制的Demo程式,所以大家不要放在Android下編譯啊,否則出現問題,別追究我的責任啦!
1 ReflectionTest.java 2 package crazypebble.reflectiontest; 3 4 import java.awt.event.ActionEvent; 5 import java.awt.event.ActionListener; 6 import java.io.Serializable; 7 8 public class ReflectionTest extends Object implements ActionListener,Serializable{ 9 // 成員變數 10 private int bInt; 11 public Integer bInteger = new Integer(4); 12 public String strB = "crazypebble"; 13 private String strA; 14 15 // 構造函數 16 public ReflectionTest() { 17 18 } 19 20 protected ReflectionTest(int id, String name) { 21 22 } 23 24 // 成員方法 25 public int abc(int id, String name) { 26 System.out.println("crazypebble ---> " + id + "-" + name); 27 return 0; 28 } 29 30 protected static void edf() { 31 32 } 33 34 @Override 35 public void actionPerformed(ActionEvent e) { 36 // TODO Auto-generated method stub 37 38 } 39 40 }
二、反射機制中需要使用到的類
我把需要使用的類列在下表中,其中對我們特別有用的類,通過著重標記顯示出來,並將在後面的使用中逐步解釋:
三、Class類
首先向大家說明一點,Class本身就是一個類,Class是該類的名稱。看下麵這個類的定義:
public class MyButton extends Button {...}
註意到上面的class的首字母是小寫,它表示的是一種類類型,但是我們的Class是一個類,相當於上面定義的MyButton類。所以,千萬不要把這裡的Class做為一個類類型來理解。明白這一點,我們繼續。
Class類是整個Java反射機制的源頭,Class類本身表示Java對象的類型,我們可通過一個Object對象的getClass()方法取得一個對象的類型,此函數返回的就是一個Class類。獲取Class對象的方法有很多種:
在平時的使用,要註意對這幾種方法的靈活運用,尤其是對Class.forName()方法的使用。因為在很多開發中,會直接通過類的名稱取得Class類的對象。
四、獲取類的相關信息
1、獲取構造方法
Class類提供了四個public方法,用於獲取某個類的構造方法。
Constructor getConstructor(Class[] params) 根據構造函數的參數,返回一個具體的具有public屬性的構造函數
Constructor getConstructors() 返回所有具有public屬性的構造函數數組
Constructor getDeclaredConstructor(Class[] params) 根據構造函數的參數,返回一個具體的構造函數(不分public和非public屬性)
Constructor getDeclaredConstructors() 返回該類中所有的構造函數數組(不分public和非public屬性)
由於Java語言是一種面向對象的語言,具有多態的性質,那麼我們可以通過構造方法的參數列表的不同,來調用不同的構造方法去創建類的實例。同樣,獲取不同的構造方法的信息,也需要提供與之對應的參數類型信息;因此,就產生了以上四種不同的獲取構造方法的方式。
1 get_Reflection_Constructors() 2 /** 3 * 獲取反射類中的構造方法 4 * 輸出列印格式:"Modifier修飾域 構造方法名(參數類型列表)" 5 */ 6 public static void get_Reflection_Constructors(ReflectionTest r) { 7 8 Class temp = r.getClass(); 9 String className = temp.getName(); // 獲取指定類的類名 10 11 try { 12 Constructor[] theConstructors = temp.getDeclaredConstructors(); // 獲取指定類的公有構造方法 13 14 for (int i = 0; i < theConstructors.length; i++) { 15 int mod = theConstructors[i].getModifiers(); // 輸出修飾域和方法名稱 16 System.out.print(Modifier.toString(mod) + " " + className + "("); 17 18 Class[] parameterTypes = theConstructors[i].getParameterTypes(); // 獲取指定構造方法的參數的集合 19 for (int j = 0; j < parameterTypes.length; j++) { // 輸出列印參數列表 20 System.out.print(parameterTypes[j].getName()); 21 if (parameterTypes.length > j+1) { 22 System.out.print(", "); 23 } 24 } 25 System.out.println(")"); 26 } 27 } catch (Exception e) { 28 e.printStackTrace(); 29 } 30 }
2、獲取類的成員方法
與獲取構造方法的方式相同,存在四種獲取成員方法的方式。
Method getMethod(String name, Class[] params) 根據方法名和參數,返回一個具體的具有public屬性的方法
Method[] getMethods() 返回所有具有public屬性的方法數組
Method getDeclaredMethod(String name, Class[] params) 根據方法名和參數,返回一個具體的方法(不分public和非public屬性)
Method[] getDeclaredMethods() 返回該類中的所有的方法數組(不分public和非public屬性)
1 get_Reflection_Method() 2 /** 3 * 獲取反射類的方法 4 * 列印輸出格式:"RetType FuncName(paramTypeList)" 5 */ 6 public static void get_Reflection_Method(ReflectionTest r) { 7 8 Class temp = r.getClass(); 9 String className = temp.getName(); 10 11 /* 12 * Note: 方法getDeclaredMethods()只能獲取到由當前類定義的所有方法,不能獲取從父類繼承的方法 13 * 方法getMethods() 不僅能獲取到當前類定義的public方法,也能得到從父類繼承和已經實現介面的public方法 14 * 請查閱開發文檔對這兩個方法的詳細描述。 15 */ 16 //Method[] methods = temp.getDeclaredMethods(); 17 Method[] methods = temp.getMethods(); 18 19 for (int i = 0; i < methods.length; i++) { 20 21 // 列印輸出方法的修飾域 22 int mod = methods[i].getModifiers(); 23 System.out.print(Modifier.toString(mod) + " "); 24 25 // 輸出方法的返回類型 26 System.out.print(methods[i].getReturnType().getName()); 27 28 // 獲取輸出的方法名 29 System.out.print(" " + methods[i].getName() + "("); 30 31 // 列印輸出方法的參數列表 32 Class[] parameterTypes = methods[i].getParameterTypes(); 33 for (int j = 0; j < parameterTypes.length; j++) { 34 System.out.print(parameterTypes[j].getName()); 35 if (parameterTypes.length > j+1) { 36 System.out.print(", "); 37 } 38 } 39 System.out.println(")"); 40 } 41 }
在獲取類的成員方法時,有一個地方值得大家註意,就是getMethods()方法和getDeclaredMethods()方法。
getMethods():用於獲取類的所有的public修飾域的成員方法,包括從父類繼承的public方法和實現介面的public方法;
getDeclaredMethods():用於獲取在當前類中定義的所有的成員方法和實現的介面方法,不包括從父類繼承的方法。
大家可以查考一下開發文檔的解釋:
getMethods() - Returns an array containing Method objects for all public methods for the class C represented by this Class. Methods may be declared in C, the interfaces it implements or in the superclasses of C. The elements in the returned array are in no particular order.
getDeclaredMethods() - Returns a Method object which represents the method matching the specified name and parameter types that is declared by the class represented by this Class.
因此在示例代碼的方法get_Reflection_Method(...)中,ReflectionTest類繼承了Object類,實現了actionPerformed方法,並定義如下成員方法:
通過這兩個語句執行後的結果不同:
a、Method[] methods = temp.getDeclaredMethods()執行後結果如下:
b、Method[] methods = temp.getMethods()執行後,結果如下:
3、獲取類的成員變數(成員屬性)
存在四種獲取成員屬性的方法
Field getField(String name) 根據變數名,返回一個具體的具有public屬性的成員變數
Field[] getFields() 返回具有public屬性的成員變數的數組
Field getDeclaredField(String name) 根據變數名,返回一個成員變數(不分public和非public屬性)
Field[] getDelcaredField() 返回所有成員變數組成的數組(不分public和非public屬性)
1 get_Reflection_Field_Value() 2 /** 3 * 獲取反射類中的屬性和屬性值 4 * 輸出列印格式:"Modifier Type : Name = Value" 5 * Note: 對於未初始化的指針類型的屬性,將不輸出結果 6 */ 7 public static void get_Reflection_Field_Value(ReflectionTest r) { 8 9 Class temp = r.getClass(); // 獲取Class類的對象的方法之一 10 11 try { 12 System.out.println("public 屬性"); 13 Field[] fb = temp.getFields(); 14 for (int i = 0; i < fb.length; i++) { 15 16 Class cl = fb[i].getType(); // 屬性的類型 17 18 int md = fb[i].getModifiers(); // 屬性的修飾域 19 20 Field f = temp.getField(fb[i].getName()); // 屬性的值 21 f.setAccessible(true); 22 Object value = (Object)f.get(r); 23 24 // 判斷屬性是否被初始化 25 if (value == null) { 26 System.out.println(Modifier.toString(md) + " " + cl + " : " + fb[i].getName()); 27 } 28 else { 29 System.out.println(Modifier.toString(md) + " " + cl + " : " + fb[i].getName() + " = " + value.toString()); 30 } 31 } 32 33 System.out.println("public & 非public 屬性"); 34 Field[] fa = temp.getDeclaredFields(); 35 for (int i = 0; i < fa.length; i++) { 36 37 Class cl = fa[i].getType(); // 屬性的類型 38 39 int md = fa[i].getModifiers(); // 屬性的修飾域 40 41 Field f = temp.getDeclaredField(fa[i].getName()); // 屬性的值 42 f.setAccessible(true); // Very Important 43 Object value = (Object) f.get(r); 44 45 if (value == null) { 46 System.out.println(Modifier.toString(md) + " " + cl + " : " + fa[i].getName()); 47 } 48 else { 49 System.out.println(Modifier.toString(md) + " " + cl + " : " + fa[i].getName() + " = " + value.toString()); 50 } 51 } 52 } catch (Exception e) { 53 e.printStackTrace(); 54 } 55 }
4、獲取類、屬性、方法的修飾域
類Class、Method、Constructor、Field都有一個public方法int getModifiers()。該方法返回一個int類型的數,表示被修飾對象( Class、 Method、 Constructor、 Field )的修飾類型的組合值。
在開發文檔中,可以查閱到,Modifier類中定義了若幹特定的修飾域,每個修飾域都是一個固定的int數值,列表如下:
該類不僅提供了若幹用於判斷是否擁有某中修飾域的方法boolean isXXXXX(int modifiers),還提供一個String toString(int modifier)方法,用於將一個表示修飾域組合值的int數轉換成描述修飾域的字元串。
五、如何調用類中的private方法
在介紹之前,先放一個代碼吧,這段代碼是參考其他文章的代碼拷貝過來的,代碼不算長,但是動態調用類的成員方法的過程講解的通俗易懂。
1 LoadMethod.java 2 package crazypebble.reflectiontest; 3 4 import java.lang.reflect.Constructor; 5 import java.lang.reflect.Method; 6 7 8 public class LoadMethod { 9 10 /** 11 * 在運行時載入指定的類,並調用指定的方法 12 * @param cName Java的類名 13 * @param MethodName 方法名 14 * @param types 方法的參數類型 15 * @param params 方法的參數值 16 * @return 17 */ 18 public Object Load(String cName, String MethodName, String[] types, String[] params) { 19 20 Object retObject = null; 21 22 try { 23 // 載入指定的類 24 Class cls = Class.forName(cName); // 獲取Class類的對象的方法之二 25 26 // 利用newInstance()方法,獲取構造方法的實例 27 // Class的newInstance方法只提供預設無參構造實例 28 // Constructor的newInstance方法提供帶參的構造實例 29 Constructor ct = cls.getConstructor(null); 30 Object obj = ct.newInstance(null); 31 //Object obj = cls.newInstance(); 32 33 // 構建 方法的參數類型 34 Class paramTypes[] = this.getMethodTypesClass(types); 35 36 // 在指定類中獲取指定的方法 37 Method meth = cls.getMethod(MethodName, paramTypes); 38 39 // 構建 方法的參數值 40 Object argList[] = this.getMethodParamObject(types, params); 41 42 // 調用指定的方法並獲取返回值為Object類型 43 retObject = meth.invoke(obj, argList); 44 45 } catch (Exception e) { 46 System.err.println(e); 47 } 48 49 return retObject; 50 } 51 52 /** 53 * 獲取參數類型,返回值保存在Class[]中 54 */ 55 public Class[] getMethodTypesClass(String[] types) { 56 Class[] cs = new Class[types.length]; 57 58 for (int i = 0; i < cs.length; i++) { 59 if (types[i] != null || !types[i].trim().equals("")) { 60 if (types[i].equals("int") || types[i].equals("Integer")) { 61 cs[i] = Integer.TYPE; 62 } 63 else if (types[i].equals("float") || types[i].equals("Float")) { 64 cs[i] = Float.TYPE; 65 } 66 else if (types[i].equals("double") || types[i].equals("Double")) { 67 cs[i] = Double.TYPE; 68 } 69 else if (types[i].equals("boolean") || types[i].equals("Boolean")) { 70 cs[i] = Boolean.TYPE; 71 } 72 else { 73 cs[i] = String.class; 74 } 75 } 76 } 77 return cs; 78 } 79 80 /** 81 * 獲取參數Object[] 82 */ 83 public Object[] getMethodParamObject(String[] types, String[] params) { 84 85 Object[] retObjects = new Object[params.length]; 86 87 for (int i = 0; i < retObjects.length; i++) { 88 if(!params[i].trim().equals("")||params[i]!=null){ 89 if(types[i].equals("int")||types[i].equals("Integer")){ 90 retObjects[i]= new Integer(params[i]); 91 } 92 else if(types[i].equals("float")||types[i].equals("Float")){ 93 retObjects[i]= new Float(params[i]); 94 } 95 else if(types[i].equals("double")||types[i].equals("Double")){ 96 retObjects[i]= new Double(params[i]); 97 } 98 else if(types[i].equals("boolean")||types[i].equals("Boolean")){ 99 retObjects[i]=new Boolean(params[i]); 100 } 101 else{ 102 retObjects[i] = params[i]; 103 } 104 } 105 } 106 107 return retObjects; 108 } 109 }
要調用一個類的方法,首先需要一個該類的實例(當然,如果該類是static,就不需要實例了,至於原因,你懂得!)。
1、創建一個類的實例
在得到一個類的Class對象之後,我們可以利用類Constructor去實例化該對象。Constructor支持泛型,也就是它本身應該是Constructor<T>。這個類有一個public成員函數:T newInstance(Object... args),其中args為對應的參數,我們通過Constructor的這個方法來創建類的對象實例。
在代碼LoadMethod.java和LoadMethodEx.java中,分別給出了兩種實例化Class類的方法:一種是利用Constructor類調用newInstance()方法;另一種就是利用Class類本身的newInstance()方法創建一個實例。兩種方法實現的效果是一樣的。
1 // 利用newInstance()方法,獲取構造方法的實例 2 3 // Class的newInstance方法,僅提供預設無參的實例化方法,類似於無參的構造方法 4 5 // Constructor的newInstance方法,提供了帶參數的實例化方法,類似於含參的構造方法 6 7 Constructor ct = cls.getConstructor(null); 8 9 Object obj = ct.newInstance(null); 10 11 Object obj = cls.newInstance();
2、行為
Method類中包含著類的成員方法的信息。在Method類中有一個public成員函數:Object invoke(Object receiver, Object... args),參數receiver指明瞭調用對象,參數args指明瞭該方法所需要接收的參數。由於我們是在運行時動態的調用類的方法,無法提前知道該類的參數類型和返回值類型,所以傳入的參數的類型是Object,返回的類型也是Object。(因為Object類是所有其他類的父類)
如果某一個方法是Java類的靜態方法,那麼Object receiver參數可以傳入null,因為靜態方法從不屬於對象。
3、屬性
對類的成員變數進行讀寫,在Field類中有兩個public方法:
Object get(Object object),該方法可用於獲取某成員變數的值
Void set(Object object, Object value),該方法設置某成員變數的值
其中,Object參數是需要傳入的對象;如果成員變數是靜態屬性,在object可傳入null。
六、對LoadMethod.java的優化處理
在上一節中給出的LoadMethod.java中,類LoadMethod對固定參數類型的方法進行了調用,並且參數類型是通過一個String[]數組傳入,然後經過方法 getMethodTypesClass() 解析之後,才得到了參數的具體的類型。同時在getMethodTypesClass()和getMethodParamObject()方法中,通過對傳入的字元串參數進行過濾後,再處理那些可以匹配中的參數類型,其他不能匹配的參數都做為String對象來處理。如果我們調用的方法所需要的參數不是簡單類型的變數,而是自定義的類對象,或者List列表,再如果我們只知道類名和方法名,不知道方法的參數類型,那我們該如何處理這些情況呢?
因此,我對LoadMethod類進行了一定的優化處理。先附上代碼:
1 LoadMethodEx.java 2 package crazypebble.reflectiontest; 3 4 import java.lang.reflect.Constructor; 5 import java.lang.reflect.Method; 6 7 8 public class LoadMethodEx { 9 10 /** 11 * 在運行時載入指定的類,並調用指定的方法 12 * @param cName Java的類名 13 * @param MethodName 方法名 14 * @param params 方法的參數值 15 * @return 16 */ 17 public Object Load(String cName, String MethodName, Object[] params) { 18 19 Object retObject = null; 20 21 try { 22 // 載入指定的類 23 Class cls = Class.forName(cName); // 獲取Class類的對象的方法之二 24 25 // 利用newInstance()方法,獲取構造方法的實例 26 // Class的newInstance方法只提供預設無參構造實例 27 // Constructor的newInstance方法提供帶參的構造實例 28 Constructor ct = cls.getConstructor(null); 29 Object obj = ct.newInstance(null); 30 //Object obj = cls.newInstance(); 31 32 // 根據方法名獲取指定方法的參數類型列表 33 Class paramTypes[] = this.getParamTypes(cls, MethodName); 34 35 // 獲取指定方法 36 Method meth = cls.getMethod(MethodName, paramTypes); 37 meth.setAccessible(true); 38 39 // 調用指定的方法並獲取返回值為Object類型 40 retObject = meth.invoke(obj, params); 41 42 } catch (Exception e) { 43 System.err.println(e); 44 } 45 46 return retObject; 47 } 48 49 /** 50 * 獲取參數類型,返回值保存在Class[]中 51 */ 52 public Class[] getParamTypes(Class cls, String mName) { 53 Class[] cs = null; 54 55 /* 56 * Note: 由於我們一般通過反射機制調用的方法,是非public方法 57 * 所以在此處使用了getDeclaredMethods()方法 58 */ 59 Method[] mtd = cls.getDeclaredMethods(); 60 for (int i = 0; i < mtd.length; i++) { 61 if (!mtd[i].getName().equals(mName)) { // 不是我們需要的參數,則進入下一次迴圈 62 continue; 63 } 64 65 cs = mtd[i].getParameterTypes(); 66 } 67 return cs; 68 } 69 }
我們通過前面幾節的一系列分析,只要我們知道了一個類的類名(包括其包的路徑),那我們就可以通過Class類的一系列方法,得到該類的成員變數、構造方法、成員方法、以及成員方法的參數類型和返回類型、還有修飾域等信息。
如果我們已經知道某個類名和需要動態調用的方法名,怎樣才能不用傳入方法的參數類型就可以調用該方法呢?
在已知類名的情況下,我們可以列印輸出該類的所有信息,當然包括類的成員方法;然後通過給定的方法名,對列印輸出的方法名進行篩選,找到我們需要的方法;再通過該方法的Method對象,得到該方法的參數類型、參數數量、和返回類型。那麼我們在外部動態調用該方法時,就不需要關心該類需要傳入的參數類型了,只需要傳入類名、方法名、參數值的信息即可。筆者實現了一個類LoadMethodEx,先從兩個類的同一個方法需要的參數方面做一個對比:
1、LoadMethodEx類,少了一個參數(方法參數類型列表),本文直接從類LoadMethod內部獲取該參數類型列表,不需要用戶傳入該信息,好處其實也不言而喻了。
2、方法的參數值:類LoadMethod是將所有的方法參數都做為一個String來傳入,在傳入再進行解析;而本文則直接使用Object類型做為參數類型,因為invoke(Object obj, Object...args)方法本身所需要的參數類型就是Object,避免了不必要的參數類型變換。
在調用LoadMethod的Load()方法時,用戶只需要知道類名、方法名,並且將已經初始化的參數先向上轉型為Object,然後傳遞給Load()方法即可。方法的返回值為Object,這個肯定是由用戶根據自己的需要,再轉換成自己所需的類型。