Java反射機制實現與原理

A__yes發表於2016-05-14

本文介紹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下編譯啊,否則出現問題,別追究我的責任啦!
Android反射機制實現與原理 - Nelson - NelsonReflectionTest.java
package crazypebble.reflectiontest;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.Serializable;

public class ReflectionTest extends Object implements ActionListener,Serializable{
// 成員變數
private int bInt;
public Integer bInteger = new Integer(4);
public String strB = “crazypebble”;
private String strA;

// 建構函式
public ReflectionTest() {

}

protected ReflectionTest(int id, String name) { 

}

// 成員方法
public int abc(int id, String name) {
    System.out.println("crazypebble ---> " + id + "-" + name);
    return 0;
}

protected static void edf() {

}

@Override
public void actionPerformed(ActionEvent e) {
    // TODO Auto-generated method stub

}

}

二、反射機制中需要使用到的類
  我把需要使用的類列在下表中,其中對我們特別有用的類,通過著重標記顯示出來,並將在後面的使用中逐步解釋:
Android反射機制實現與原理 - Nelson - Nelson

三、Class類
  首先向大家說明一點,Class本身就是一個類,Class是該類的名稱。看下面這個類的定義:
  public class MyButton extends Button {…}
  注意到上面的class的首字母是小寫,它表示的是一種類型別,但是我們的Class是一個類,相當於上面定義的MyButton類。所以,千萬不要把這裡的Class做為一個類型別來理解。明白這一點,我們繼續。
  Class類是整個Java反射機制的源頭,Class類本身表示Java物件的型別,我們可通過一個Object物件的getClass()方法取得一個物件的型別,此函式返回的就是一個Class類。獲取Class物件的方法有很多種:
Android反射機制實現與原理 - Nelson - Nelson
  在平時的使用,要注意對這幾種方法的靈活運用,尤其是對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語言是一種物件導向的語言,具有多型的性質,那麼我們可以通過構造方法的引數列表的不同,來呼叫不同的構造方法去建立類的例項。同樣,獲取不同的構造方法的資訊,也需要提供與之對應的引數型別資訊;因此,就產生了以上四種不同的獲取構造方法的方式。
Android反射機制實現與原理 - Nelson - Nelsonget_Reflection_Constructors()
/**
* 獲取反射類中的構造方法
* 輸出列印格式:”Modifier修飾域 構造方法名(引數型別列表)”
*/
public static void get_Reflection_Constructors(ReflectionTest r) {

    Class temp = r.getClass();
    String className = temp.getName();        // 獲取指定類的類名

    try {
        Constructor[] theConstructors = temp.getDeclaredConstructors();           // 獲取指定類的公有構造方法

        for (int i = 0; i < theConstructors.length; i++) {
            int mod = theConstructors[i].getModifiers();    // 輸出修飾域和方法名稱
            System.out.print(Modifier.toString(mod) + " " + className + "(");

            Class[] parameterTypes = theConstructors[i].getParameterTypes();       // 獲取指定構造方法的引數的集合
            for (int j = 0; j < parameterTypes.length; j++) {    // 輸出列印引數列表
                System.out.print(parameterTypes[j].getName());
                if (parameterTypes.length > j+1) {
                    System.out.print(", ");
                }
            }
            System.out.println(")");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

2、獲取類的成員方法
  與獲取構造方法的方式相同,存在四種獲取成員方法的方式。
    Method getMethod(String name, Class[] params) 根據方法名和引數,返回一個具體的具有public屬性的方法
    Method[] getMethods() 返回所有具有public屬性的方法陣列
    Method getDeclaredMethod(String name, Class[] params) 根據方法名和引數,返回一個具體的方法(不分public和非public屬性)
    Method[] getDeclaredMethods() 返回該類中的所有的方法陣列(不分public和非public屬性)
Android反射機制實現與原理 - Nelson - Nelsonget_Reflection_Method()
/**
* 獲取反射類的方法
* 列印輸出格式:”RetType FuncName(paramTypeList)”
*/
public static void get_Reflection_Method(ReflectionTest r) {

    Class temp = r.getClass();
    String className = temp.getName();

    /*
     * Note: 方法getDeclaredMethods()只能獲取到由當前類定義的所有方法,不能獲取從父類繼承的方法
     * 方法getMethods() 不僅能獲取到當前類定義的public方法,也能得到從父類繼承和已經實現介面的public方法
     * 請查閱開發文件對這兩個方法的詳細描述。
     */
    //Method[] methods = temp.getDeclaredMethods();
    Method[] methods = temp.getMethods();

    for (int i = 0; i < methods.length; i++) {

        // 列印輸出方法的修飾域
        int mod = methods[i].getModifiers();
        System.out.print(Modifier.toString(mod) + " ");

        // 輸出方法的返回型別
        System.out.print(methods[i].getReturnType().getName());    

        // 獲取輸出的方法名
        System.out.print(" " + methods[i].getName() + "(");

        // 列印輸出方法的引數列表
        Class[] parameterTypes = methods[i].getParameterTypes();
        for (int j = 0; j < parameterTypes.length; j++) {
            System.out.print(parameterTypes[j].getName());
            if (parameterTypes.length > j+1) {
                System.out.print(", ");
            }
        }
        System.out.println(")");
    }
}

  在獲取類的成員方法時,有一個地方值得大家注意,就是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方法,並定義如下成員方法:
    Android反射機制實現與原理 - Nelson - Nelson
  通過這兩個語句執行後的結果不同:
  a、Method[] methods = temp.getDeclaredMethods()執行後結果如下:
    Android反射機制實現與原理 - Nelson - Nelson
  b、Method[] methods = temp.getMethods()執行後,結果如下:
     Android反射機制實現與原理 - Nelson - Nelson
3、獲取類的成員變數(成員屬性)
  存在四種獲取成員屬性的方法
    Field getField(String name) 根據變數名,返回一個具體的具有public屬性的成員變數
    Field[] getFields() 返回具有public屬性的成員變數的陣列
    Field getDeclaredField(String name) 根據變數名,返回一個成員變數(不分public和非public屬性)
    Field[] getDelcaredField() 返回所有成員變數組成的陣列(不分public和非public屬性)
Android反射機制實現與原理 - Nelson - Nelsonget_Reflection_Field_Value()
/**
* 獲取反射類中的屬性和屬性值
* 輸出列印格式:”Modifier Type : Name = Value”
* Note: 對於未初始化的指標型別的屬性,將不輸出結果
*/
public static void get_Reflection_Field_Value(ReflectionTest r) {

    Class temp = r.getClass();    // 獲取Class類的物件的方法之一

    try {
        System.out.println("public 屬性");
        Field[] fb = temp.getFields();
        for (int i = 0; i < fb.length; i++) {

            Class cl = fb[i].getType();    // 屬性的型別

            int md = fb[i].getModifiers();    // 屬性的修飾域

            Field f = temp.getField(fb[i].getName());    // 屬性的值
            f.setAccessible(true);
            Object value = (Object)f.get(r);

            // 判斷屬性是否被初始化
            if (value == null) {
                System.out.println(Modifier.toString(md) + " " + cl + " : "      + fb[i].getName());
            }
            else {
                System.out.println(Modifier.toString(md) + " " + cl + " : "      + fb[i].getName() + " = " + value.toString());
            }
        }

        System.out.println("public & 非public 屬性");
        Field[] fa = temp.getDeclaredFields();
        for (int i = 0; i < fa.length; i++) {

            Class cl = fa[i].getType();    // 屬性的型別

            int md = fa[i].getModifiers();    // 屬性的修飾域

            Field f = temp.getDeclaredField(fa[i].getName());    // 屬性的值
            f.setAccessible(true);    // Very Important
            Object value = (Object) f.get(r);

            if (value == null) {
                System.out.println(Modifier.toString(md) + " " + cl + " : "      + fa[i].getName());
            }
            else {
                System.out.println(Modifier.toString(md) + " " + cl + " : "  + fa[i].getName() + " = " + value.toString());
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

4、獲取類、屬性、方法的修飾域
  類Class、Method、Constructor、Field都有一個public方法int getModifiers()。該方法返回一個int型別的數,表示被修飾物件( Class、 Method、 Constructor、 Field )的修飾型別的組合值。
  在開發文件中,可以查閱到,Modifier類中定義了若干特定的修飾域,每個修飾域都是一個固定的int數值,列表如下:
    Android反射機制實現與原理 - Nelson - Nelson
  該類不僅提供了若干用於判斷是否擁有某中修飾域的方法boolean isXXXXX(int modifiers),還提供一個String toString(int modifier)方法,用於將一個表示修飾域組合值的int數轉換成描述修飾域的字串。
    Android反射機制實現與原理 - Nelson - Nelson

五、如何呼叫類中的private方法
  在介紹之前,先放一個程式碼吧,這段程式碼是參考其他文章的程式碼拷貝過來的,程式碼不算長,但是動態呼叫類的成員方法的過程講解的通俗易懂。
Android反射機制實現與原理 - Nelson - NelsonLoadMethod.java
package crazypebble.reflectiontest;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class LoadMethod {

/**
 * 在執行時載入指定的類,並呼叫指定的方法
 * @param cName            Java的類名
 * @param MethodName    方法名
 * @param types            方法的引數型別
 * @param params        方法的引數值
 * @return
 */
public Object Load(String cName, String MethodName, String[] types, String[] params) {

    Object retObject = null;

    try {
        // 載入指定的類
        Class cls = Class.forName(cName);    // 獲取Class類的物件的方法之二

        // 利用newInstance()方法,獲取構造方法的例項
        // Class的newInstance方法只提供預設無參構造例項
        // Constructor的newInstance方法提供帶參的構造例項
        Constructor ct = cls.getConstructor(null);
        Object obj = ct.newInstance(null);    
        //Object obj = cls.newInstance();

        // 構建 方法的引數型別
        Class paramTypes[] = this.getMethodTypesClass(types);

        // 在指定類中獲取指定的方法
        Method meth = cls.getMethod(MethodName, paramTypes);

        // 構建 方法的引數值
        Object argList[] = this.getMethodParamObject(types, params);

        // 呼叫指定的方法並獲取返回值為Object型別
        retObject = meth.invoke(obj, argList);

    } catch (Exception e) {
        System.err.println(e);
    }

    return retObject;
}

/**
 * 獲取引數型別,返回值儲存在Class[]中
 */
public Class[] getMethodTypesClass(String[] types) {
    Class[] cs = new Class[types.length];

    for (int i = 0; i < cs.length; i++) {
        if (types[i] != null || !types[i].trim().equals("")) {
            if (types[i].equals("int") || types[i].equals("Integer")) {
                cs[i] = Integer.TYPE;
            } 
            else if (types[i].equals("float") || types[i].equals("Float")) {
                cs[i] = Float.TYPE;
            }
            else if (types[i].equals("double") || types[i].equals("Double")) {
                cs[i] = Double.TYPE;
            }
            else if (types[i].equals("boolean") || types[i].equals("Boolean")) {
                cs[i] = Boolean.TYPE;
            }
            else {
                cs[i] = String.class;
            }
        }
    }
    return cs;
}

/**
 * 獲取引數Object[]
 */
public Object[] getMethodParamObject(String[] types, String[] params) {

    Object[] retObjects = new Object[params.length];

    for (int i = 0; i < retObjects.length; i++) {
        if(!params[i].trim().equals("")||params[i]!=null){  
            if(types[i].equals("int")||types[i].equals("Integer")){  
                retObjects[i]= new Integer(params[i]);  
            }
            else if(types[i].equals("float")||types[i].equals("Float")){  
                retObjects[i]= new Float(params[i]);  
            }
            else if(types[i].equals("double")||types[i].equals("Double")){  
                retObjects[i]= new Double(params[i]);  
            }
            else if(types[i].equals("boolean")||types[i].equals("Boolean")){  
                retObjects[i]=new Boolean(params[i]);  
            }
            else{  
                retObjects[i] = params[i];  
            }  
        } 
    }

    return retObjects;
}

}

  要呼叫一個類的方法,首先需要一個該類的例項(當然,如果該類是static,就不需要例項了,至於原因,你懂得!)。
1、建立一個類的例項
  在得到一個類的Class物件之後,我們可以利用類Constructor去例項化該物件。Constructor支援泛型,也就是它本身應該是Constructor。這個類有一個public成員函式:T newInstance(Object… args),其中args為對應的引數,我們通過Constructor的這個方法來建立類的物件例項。
  在程式碼LoadMethod.java和LoadMethodEx.java中,分別給出了兩種例項化Class類的方法:一種是利用Constructor類呼叫newInstance()方法;另一種就是利用Class類本身的newInstance()方法建立一個例項。兩種方法實現的效果是一樣的。
// 利用newInstance()方法,獲取構造方法的例項

// Class的newInstance方法,僅提供預設無參的例項化方法,類似於無參的構造方法

// Constructor的newInstance方法,提供了帶引數的例項化方法,類似於含參的構造方法

Constructor ct = cls.getConstructor(null);

Object obj = ct.newInstance(null);

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類進行了一定的優化處理。先附上程式碼:
Android反射機制實現與原理 - Nelson - NelsonLoadMethodEx.java
package crazypebble.reflectiontest;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class LoadMethodEx {

/**
 * 在執行時載入指定的類,並呼叫指定的方法
 * @param cName            Java的類名
 * @param MethodName    方法名
 * @param params        方法的引數值
 * @return
 */
public Object Load(String cName, String MethodName, Object[] params) {

    Object retObject = null;

    try {
        // 載入指定的類
        Class cls = Class.forName(cName);    // 獲取Class類的物件的方法之二

        // 利用newInstance()方法,獲取構造方法的例項
        // Class的newInstance方法只提供預設無參構造例項
        // Constructor的newInstance方法提供帶參的構造例項
        Constructor ct = cls.getConstructor(null);
        Object obj = ct.newInstance(null);    
        //Object obj = cls.newInstance();

        // 根據方法名獲取指定方法的引數型別列表
        Class paramTypes[] = this.getParamTypes(cls, MethodName);

        // 獲取指定方法
        Method meth = cls.getMethod(MethodName, paramTypes);
        meth.setAccessible(true);

        // 呼叫指定的方法並獲取返回值為Object型別
        retObject = meth.invoke(obj, params);

    } catch (Exception e) {
        System.err.println(e);
    }

    return retObject;
}

/**
 * 獲取引數型別,返回值儲存在Class[]中
 */
public Class[] getParamTypes(Class cls, String mName) {
    Class[] cs = null;

    /*
     * Note: 由於我們一般通過反射機制呼叫的方法,是非public方法
     * 所以在此處使用了getDeclaredMethods()方法
     */
    Method[] mtd = cls.getDeclaredMethods();    
    for (int i = 0; i < mtd.length; i++) {
        if (!mtd[i].getName().equals(mName)) {    // 不是我們需要的引數,則進入下一次迴圈
            continue;
        }

        cs = mtd[i].getParameterTypes();
    }
    return cs;
}

}

 我們通過前面幾節的一系列分析,只要我們知道了一個類的類名(包括其包的路徑),那我們就可以通過Class類的一系列方法,得到該類的成員變數、構造方法、成員方法、以及成員方法的引數型別和返回型別、還有修飾域等資訊。

  如果我們已經知道某個類名和需要動態呼叫的方法名,怎樣才能不用傳入方法的引數型別就可以呼叫該方法呢?
在已知類名的情況下,我們可以列印輸出該類的所有資訊,當然包括類的成員方法;然後通過給定的方法名,對列印輸出的方法名進行篩選,找到我們需要的方法;再通過該方法的Method物件,得到該方法的引數型別、引數數量、和返回型別。那麼我們在外部動態呼叫該方法時,就不需要關心該類需要傳入的引數型別了,只需要傳入類名、方法名、引數值的資訊即可。筆者實現了一個類LoadMethodEx,先從兩個類的同一個方法需要的引數方面做一個對比:
    Android反射機制實現與原理 - Nelson - Nelson
    Android反射機制實現與原理 - Nelson - Nelson
  1、LoadMethodEx類,少了一個引數(方法引數型別列表),本文直接從類LoadMethod內部獲取該引數型別列表,不需要使用者傳入該資訊,好處其實也不言而喻了。
  2、方法的引數值:類LoadMethod是將所有的方法引數都做為一個String來傳入,在傳入再進行解析;而本文則直接使用Object型別做為引數型別,因為invoke(Object obj, Object…args)方法本身所需要的引數型別就是Object,避免了不必要的引數型別變換。
  在呼叫LoadMethod的Load()方法時,使用者只需要知道類名、方法名,並且將已經初始化的引數先向上轉型為Object,然後傳遞給Load()方法即可。方法的返回值為Object,這個肯定是由使用者根據自己的需要,再轉換成自己所需的型別。
Android反射機制實現與原理 - Nelson - Nelson執行結果
屬性:
public 屬性
public class java.lang.Integer : bInteger = 4
public class java.lang.String : strB = crazypebble
public & 非public 屬性
private int : bInt = 0
public class java.lang.Integer : bInteger = 4
public class java.lang.String : strB = crazypebble
private class java.lang.String : strA

構造方法:
public crazypebble.reflectiontest.ReflectionTest()
protected crazypebble.reflectiontest.ReflectionTest(int, java.lang.String)

父類/介面:
父類: java.lang.Object
介面0: java.awt.event.ActionListener
介面1: java.io.Serializable

成員方法:
public int abc(int, java.lang.String)
public void actionPerformed(java.awt.event.ActionEvent)
public final native void wait(long)
public final void wait()
public final void wait(long, int)
public boolean equals(java.lang.Object)
public java.lang.String toString()
public native int hashCode()
public final native java.lang.Class getClass()
public final native void notify()
public final native void notifyAll()

反射機制呼叫方法:LoadMethod
crazypebble —> 1-hello, android-1!

反射機制呼叫方法:LoadMethodEx
crazypebble —> 2-hello, android-2?
返回結果:0

七、總結
  關於反射機制,其實還有一個比較敏感的話題,就是反射機制帶來我們的安全性問題。由於我在這方面研究的不是很深入,所以講不好。大家有空可以跟蹤一下在本文最後提供的兩個連結,裡面有一些介紹。
  我們介紹了Java的反射機制,但是在Android平臺下,反射機制具體有沒有什麼用途呢?答案是肯定的。推薦大家看一篇文章《利用Java反射技術阻止通過按鈕關閉對話方塊》,這篇文章為CSDN推薦為精品文章,所以還是很值得一看的。我特地從CSDN轉載過來供大家一起學習。
  原連結:http://blog.csdn.net/nokiaguy/archive/2010/07/27/5770263.aspx
  轉載連結:http://www.cnblogs.com/crazypebble/archive/2011/04/13/2014297.html
程式實現的原始碼:
Android反射機制實現與原理 - Nelson - NelsonAndroidReflection
package crazypebble.androidreflection;

import java.lang.reflect.Field;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {
/* Called when the activity is first created. /
private static Button btnHandler = null;
private static Button btnShowing = null;
AlertDialog alertDialog = null;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    btnHandler = (Button)findViewById(R.id.btn_mHandler);
    btnHandler.setOnClickListener(new ButtonListener());

    btnShowing = (Button)findViewById(R.id.btn_mShowing);
    btnShowing.setOnClickListener(new ButtonListener());

    alertDialog = new AlertDialog.Builder(this)
            .setTitle("abc")
            .setMessage("Content")
            .setIcon(R.drawable.icon)
            .setPositiveButton("確定", new PositiveClickListener())
            .setNegativeButton("取消", new NegativeClickListener())
            .create();
}

private class ButtonListener implements OnClickListener {

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.btn_mHandler:
            modify_mHandler();
            alertDialog.show();
            break;
        case R.id.btn_mShowing:
            alertDialog.show();
            break;
        default:
            break;
        }
    }
}

private class PositiveClickListener implements android.content.DialogInterface.OnClickListener {

    @Override
    public void onClick(DialogInterface dialog, int which) {
        // 方法二時啟用
        modify_dismissDialog(false);
    }
}

private class NegativeClickListener implements android.content.DialogInterface.OnClickListener {

    @Override
    public void onClick(DialogInterface dialog, int which) {
        // 方法一時啟用
        //dialog.dismiss();

        // 方法二時啟用
        modify_dismissDialog(true);
    }
}

/*
 * 第一種方法:修改AlertController類的private成員變數mHandler的值
 */
public void modify_mHandler() {
    try {
        Field field = alertDialog.getClass().getDeclaredField("mAlert");
        field.setAccessible(true);
        // 獲取mAlert變數的值
        Object obj = field.get(alertDialog);
        field = obj.getClass().getDeclaredField("mHandler");
        field.setAccessible(true);
        // 修改mHandler變數的值,使用新的ButtonHandler類
        field.set(obj, new MyButtonHandler(alertDialog));

    } catch (Exception e) {
        e.printStackTrace();
    }
}

/*
 * 第二種方法:修改dismissDialog()方法
 */
public void modify_dismissDialog(boolean flag) {
    try {
        Field field = alertDialog.getClass().getSuperclass().getDeclaredField("mShowing");
        field.setAccessible(true);
        // 將mShowing變數設為false,表示對話方塊已經關閉
        field.set(alertDialog, flag);
        alertDialog.dismiss();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

}

Android反射機制實現與原理 - Nelson - NelsonMyButtonHandler.java
package crazypebble.androidreflection;

import java.lang.ref.WeakReference;

import android.content.DialogInterface;
import android.os.Handler;
import android.os.Message;

public class MyButtonHandler extends Handler{

// Button clicks have Message.what as the BUTTON{1,2,3} constant
private static final int MSG_DISMISS_DIALOG = 1;

private WeakReference<DialogInterface> mDialog;

public MyButtonHandler(DialogInterface dialog) {
    mDialog = new WeakReference<DialogInterface>(dialog);
}

@Override
public void handleMessage(Message msg) {
    switch (msg.what) {

        case DialogInterface.BUTTON_POSITIVE:
        case DialogInterface.BUTTON_NEGATIVE:
        case DialogInterface.BUTTON_NEUTRAL:
            ((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);
            break;
    }
}

}
  看完上面這篇文章之後,希望大家明確一點的是:反射機制通過void setAccessible(boolean flag)方法可以得到一個類的private的方法和屬性,使用這些private的方法和屬性,已經可以做一些超越限制的事情了。所以在使程,還需謹慎啊!

相關文章