1. 程式人生 > >Android中反射機制解析 API介紹 建立private構造方法類例項 反射內部類 使用demo

Android中反射機制解析 API介紹 建立private構造方法類例項 反射內部類 使用demo

反射

前言

在前面寫一些Android原始碼分析的文章中,比如fork程序,SystemServer啟動服務,ActivityThread載入等都會涉及到反射的大量運用,讓我覺得很有必要針對反射機制寫一篇部落格進行總結

相關知識點

在講解反射前先說明一些知識點以做鋪墊

編譯型語言和解釋型語言

  • 編譯型語言是指先將原始碼編譯生成機器碼,然後再由機器執行機器碼

  • 解釋型語言是指先將原始碼編譯成中間介質,執行時再由直譯器對中間介質進行解釋執行

對於這兩種定義,Java處於兩種之間,同時具有編譯型型和解釋型的特點,要知道我們的Android專案執行過程是:

編譯器先將專案原始碼即Java檔案編譯成位元組碼即class檔案,應用安裝到手機,以後每次執行應用時,Java虛擬機器再將位元組碼解釋成機器碼由機器執行,從這個過程看Java是解釋型語

因為每次執行都要重新解釋,其執行速度必然會比可執行的二進位制位元組碼程式慢很多。為了提高執行速度,引入了 JIT 技術;在執行時, JIT 會把執行頻率很高的位元組碼翻譯成機器碼儲存起來,以後每次執行直接執行機器碼,從這點看,又是編譯型語言

動態型別語言和靜態型別語言

  • 靜態型別語言是指在編譯時資料型別即可確定的語言,要求在變數定義時必須宣告其資料型別,例如C#,C++,Java
  • 動態型別語言是指在執行時才確定資料型別的語言,變數在定義或者使用時無需宣告型別,例如PHP,Ruby,Python,JavaScript,Shell等等

儘管在這樣的定義與分類下Java不是動態語言,它卻有著一個非常突出的動態相關機制:Reflection(反射);Reflection 是Java被視為動態(或準動態)語言的一個關鍵性質。這個機制允許程式在執行時透過Reflection API取得任何一個已知名稱的class的內部資訊,包括其modifiers(諸如public, static 等等)、superclass(例如Object)、實現之interfaces(例如Serializable),也包括fields和methods的所有資訊,並可於執行時改變fields內容或呼叫methods

反射(Reflection)

JAVA反射機制是指在執行態可直接操作任意類或物件的所有屬性和方法的功能,它只是提供給Java一個動態修改功能,但是無法修改程式結構或變數型別

反射機制的用途

  • 在執行時獲取任意一個物件所屬的類:Class<?> clazz = Class.forName(String className)
  • 在執行時構造任意一個類的物件:Object obj = clazz.newInstance()
  • 在執行時獲取任意一個類所具有的成員變數和方法:field.set(Object obj, Object value); field.get(Object obj)
  • 在執行時呼叫任意一個物件的方法,這應該是需求最大的一個功能了,特別是這個方法是private的或者hide的:method.invoke(Object obj, Object… args)

反射還可以獲取類的其他資訊,包含modifiers,superclass, 實現的interfaces等

實現反射相關的類

  • Class:代表一個類
  • Constructor:代表類的構造方法
  • Method:代表類的方法
  • Field:代表類的屬性或者成員變數
  • Array:提供動態建立陣列能力及訪問陣列的元素的靜態方法
  • Modifier:代表類,方法,屬性的描述修飾符

注意:Modifier的取值有public,protected,private,abstract,static,final,transient,volatile,synchronized,native, strictfp,interface;Constructor, Field, Method這三個類都繼承AccessibleObject,該物件有一個非常重要的方法setAccessible(boolean flag), 藉助該方法,能直接呼叫非Public的屬性與方法

Class

我們知道在Java體系中,所有類直接或間接繼承與Object類,而Object類聲明瞭很多重要的需要被子類重寫的方法,其中一個就是getClass,該方法返回要給Class類物件,所以對於任何一個Java物件,都可以通過此方法獲得物件的型別

要知道Java程式在執行時,系統對每一個物件都有一個型別標識,用於記錄物件所屬的類,虛擬機器使用這個型別來選擇相應方法去執行;而儲存所有物件型別資訊的類就是Class類;實際過程是執行程式時,虛擬機器首先會檢測所要載入的類物件的Class物件是否已經載入,如果沒有載入,虛擬機器就會根據類名查詢class檔案,並將其Class物件載入,這樣每個類都有一個Class物件,它們由虛擬機器進行管理

要知道Class類沒有公共的構造方法,所以不能通過new的方法去構造一個Class物件,虛擬機器通常是呼叫ClassLoader的defineClass方法構造

基本的 Java 型別(boolean、byte、char、short、int、long、float 和 double)和關鍵字 void 也都對應一個 Class 物件

Class類API

  • Class<?> forName(String className):返回一個與指定引數相同的Class物件(要求JVM查詢並載入指定的類,也就是說JVM會執行該類的靜態程式碼段)
  • Class<? super T> getSuperclass():獲取呼叫物件的父類
  • Constructor<?>[] getConstructors():獲得類的public型別的構造方法
  • Constructor getConstructor(Class<?>… parameterTypes):獲得類的特定構造方法,parameterTypes 引數指定構造方法的引數型別
  • Constructor<?>[] getDeclaredConstructors():獲取所有構造方法
  • Method[] getMethods():獲得類的public型別的方法
  • Method[] getDeclaredMethods():獲得類的所有方法
  • Method getDeclaredMethod(String name, Class<?>… parameterTypes):根據方法名和引數型別獲取指定方法
  • Field[] getFields():獲得類的public型別的屬性
  • Field getField(String name):獲取指定名稱的public型別的屬性
  • Field[] getDeclaredFields():獲得類的所有屬性
  • Field getDeclaredField(String name):獲取指定名稱屬性
  • String getName():獲得一個實體的名稱(實體可能是類, 介面, 陣列,基本型別, void修飾符),就是forName方法的引數
  • T newInstance():通過類的不帶引數且public的構造方法建立這個類的一個物件
  • String getInnerClassName():獲取內部類名稱

Constructor類 API

  • getModifiers():獲取構造方法的修飾符,比如private,public等
  • getParameterTypes():獲取構造方法中引數的型別
  • newInstance(Object … initargs):傳遞引數,建立例項化物件
  • setAccessible(true):設定允許訪問,禁止Java修飾符訪問檢查

Method類API

  • setAccessible(boolean flag):設定允許訪問,禁止Java修飾符訪問檢查
  • Class<?>[] getParameterTypes():獲取方法引數型別
  • Class<?> getReturnType():獲取方法返回值型別
  • Object invoke(Object receiver, Object… args):執行方法,這也是最重要的一個方法了,接收兩個引數,第一個引數是擁有該方法的物件例項,第二個引數是引數型別

Field類API

  • Object get(Object object):獲取物件object的指定屬性值,還有一堆getXXX方法,是用來獲取基本資料型別的屬性的值
  • void set(Object object, Object value):設定物件object的指定屬性的值為value,還有一堆setXXX方法,用來設定基本資料型別的屬性的值
  • setAccessible(boolean flag):設定允許訪問,禁止Java修飾符訪問檢查

反射的使用

API的使用樣例

我們這裡以String類為例進行API的介紹

		Class c;
		try {
            c = Class.forName("java.lang.String");
            Class sup = c.getSuperclass();
            Log.e(TAG,"onCreate sup="+sup);
            Log.e(TAG,"onCreate c="+c);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

看看列印結果

E: onCreate sup=class java.lang.Object
E: onCreate c=class java.lang.String

獲取構造方法

public void getConstructor(){

        //獲取所有public的構造方法
        Constructor[] constructors = c.getConstructors();
        for (int i=0; i<constructors.length; i++) {
            Log.e(TAG,"getConstructor constructors="+constructors[i]);
        }
        Log.e(TAG,"getConstructor ===============");
        //獲取所有構造方法
        Constructor[] constructors2 = c.getDeclaredConstructors();
        for (int i=0; i<constructors2.length; i++) {
            Log.e(TAG,"getConstructor constructors="+constructors2[i]);
        }
        Log.e(TAG,"getConstructor ===============");
        //獲取指定引數型別的構造方法
        Class cs[] = {java.lang.String.class};
        Constructor constructors3 ;
        try {
            constructors3 = c.getConstructor(cs);
            Log.e(TAG,"getConstructor constructors3="+constructors3);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }


    }
E: getConstructor constructors=public java.lang.String()
E: getConstructor constructors=public java.lang.String(java.lang.String)
E: getConstructor constructors=public java.lang.String(java.lang.StringBuffer)
E: getConstructor constructors=public java.lang.String(java.lang.StringBuilder)
E: getConstructor constructors=public java.lang.String(byte[])
E: getConstructor constructors=public java.lang.String(byte[],int)
E: getConstructor constructors=public java.lang.String(byte[],int,int)
E: getConstructor constructors=public java.lang.String(byte[],int,int,int)
E: getConstructor constructors=public java.lang.String(byte[],int,int,java.lang.String) throws java.io.UnsupportedEncodingException
E: getConstructor constructors=public java.lang.String(byte[],int,int,java.nio.charset.Charset)
E: getConstructor constructors=public java.lang.String(byte[],java.lang.String) throws java.io.UnsupportedEncodingException
E: getConstructor constructors=public java.lang.String(byte[],java.nio.charset.Charset)
E: getConstructor constructors=public java.lang.String(char[])
E: getConstructor constructors=public java.lang.String(char[],int,int)
E: getConstructor constructors=public java.lang.String(int[],int,int)
E: getConstructor ===============
E: getConstructor constructors=public java.lang.String()
E: getConstructor constructors=java.lang.String(int,int,char[])
E: getConstructor constructors=public java.lang.String(java.lang.String)
E: getConstructor constructors=public java.lang.String(java.lang.StringBuffer)
E: getConstructor constructors=public java.lang.String(java.lang.StringBuilder)
E: getConstructor constructors=public java.lang.String(byte[])
E: getConstructor constructors=public java.lang.String(byte[],int)
E: getConstructor constructors=public java.lang.String(byte[],int,int)
E: getConstructor constructors=public java.lang.String(byte[],int,int,int)
E: getConstructor constructors=public java.lang.String(byte[],int,int,java.lang.String) throws java.io.UnsupportedEncodingException
E: getConstructor constructors=public java.lang.String(byte[],int,int,java.nio.charset.Charset)
E: getConstructor constructors=public java.lang.String(byte[],java.lang.String) throws java.io.UnsupportedEncodingException
E: getConstructor constructors=public java.lang.String(byte[],java.nio.charset.Charset)
E: getConstructor constructors=public java.lang.String(char[])
E: getConstructor constructors=public java.lang.String(char[],int,int)
E: getConstructor constructors=public java.lang.String(int[],int,int)
E: getConstructor ===============
E: getConstructor constructors3=public java.lang.String(java.lang.String)

可以看到getDeclaredConstructors比getConstructors返回結果多了一個非public的構造方法

獲取方法

public void getMethods(){

        //獲取所有public方法
        Method[] m = c.getMethods();
        for (int i=0; i<m.length; i++) {
            Log.e(TAG,"getMethods m="+m[i]);
        }
        Log.e(TAG,"getMethods ===============");

        //獲取所有方法
        Method[] m2 = c.getDeclaredMethods();
        for (int i=0; i<m2.length; i++) {
            Log.e(TAG,"getMethods m2="+m2[i]);
        }
        Log.e(TAG,"getMethods ===============");

        //獲取指定方法名和引數型別的方法
        Class cs[] = {java.lang.String.class};
        try {
            Method m3 = c.getDeclaredMethod("getBytes",cs);
            Log.e(TAG,"getMethods m3="+m3);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

這個列印結果太多就不列舉了,指列舉 獲取指定方法名和引數型別的方法 結果

E: getMethods m3=public byte[] java.lang.String.getBytes(java.lang.String) throws java.io.UnsupportedEncodingException

獲取屬性

public void getField(){
        //獲取所有public屬性
        Field[] f = c.getFields();
        for (int i=0; i< (f == null ? 0:f.length); i++) {
            Log.e(TAG,"getField f="+f[i]);
        }
        Log.e(TAG,"getField ===============");

        //獲取所有屬性
        Field[] f2 = c.getDeclaredFields();
        for (int i=0; i<f2.length; i++) {
            Log.e(TAG,"getField f2="+f2[i]);
        }
        Log.e(TAG,"getField ===============");

        try {
            //獲取指定名稱的public屬性
            Field f3 = c.getField("CASE_INSENSITIVE_ORDER");
            Log.e(TAG,"getField f3="+f3);
            Log.e(TAG,"getField ===============");
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

        try {
            //獲取指定名稱的屬性
            Field f4 = c.getDeclaredField("count");
            Log.e(TAG,"getField f4="+f4);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

看結果

E: getField f=public static final java.util.Comparator java.lang.String.CASE_INSENSITIVE_ORDER
E: getField ===============
E: getField f2=private final int java.lang.String.count
E: getField f2=private int java.lang.String.hash
E: getField f2=public static final java.util.Comparator java.lang.String.CASE_INSENSITIVE_ORDER
E: getField f2=private static final java.io.ObjectStreamField[] java.lang.String.serialPersistentFields
E: getField f2=private static final long java.lang.String.serialVersionUID
E: getField ===============
E: getField f3=public static final java.util.Comparator java.lang.String.CASE_INSENSITIVE_ORDER
E: getField ===============
E: getField f4=private final int java.lang.String.count

Demo使用樣例

這裡通過自己寫的demo來進行反射使用練習

場景是定義一個密碼加密類,有兩個構造方法且都是private修飾,有兩個加密方法,一個私有一個公有;現在通過反射獲取私有構造方法來建立類例項,並且呼叫兩個方法

先定義加密類

/**
 * @Description TODO(反射使用演示)
 * @author cxy
 * @Date 2018/11/12 9:14
 */
public class PasswordEncrypt {

    public String TAG = PasswordEncrypt.class.getSimpleName();

    private int version = 1;

    private PasswordEncrypt(){
        Log.e(TAG,"PasswordEncrypt");
    }

    private PasswordEncrypt(int version){
        this.version = version;
        Log.e(TAG,"PasswordEncrypt version="+version);
    }

    private String encodeStr(String pw){
        return Base64.encodeToString(pw.getBytes(),Base64.NO_WRAP);
    }

    public String encryptMD5(String pw){

        if (TextUtils.isEmpty(pw)) throw new NullPointerException("password not be null");
        String result = "";
        try {

            MessageDigest md5 = MessageDigest.getInstance("MD5");
            byte[] buff = md5.digest(pw.getBytes());
            for (byte b : buff) {
                String temp = Integer.toHexString(b & 0xff);
                if (temp.length() == 1) {
                    temp = "0" + temp;
                }
                result += temp;
            }
        } catch (NoSuchAlgorithmException e) {
            Log.e(TAG,"encryptMD5 NoSuchAlgorithmException="+e);
            e.printStackTrace();
        }
        return result;
    }

}

正常來講,這個類是沒辦法構建出例項的,裡面的方法也沒法呼叫,但是通過反射可以破解私有構造方法來建立例項,如下:

獲取Class物件

Class<PasswordEncrypt> classType = null;
try {
    classType = (Class<PasswordEncrypt>) Class.forName("com.mango.reflex.PasswordEncrypt");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}
Log.e(TAG,"classType="+classType);

通過 Class.forName 拿到PasswordEncrypt的Class物件,看日誌

E/MainActivity: classType=class com.mango.reflex.PasswordEncrypt

成功拿到

獲取構造方法

接下來就要通過這個Class物件構建PasswordEncrypt例項了,因為PasswordEncrypt兩個構造方法都是private的,所以不能通過如下方法構建例項

		//這種方法只能構建擁有公有構造方法的物件
		try {
            PasswordEncrypt encry = classType.newInstance();
            String result = encry.encryptMD5("123456");
            Log.e(TAG,"result="+result);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

這樣我們必須通過Class拿到PasswordEncrypt的構造方法,然後設定它的訪問屬性,最後再構建例項

		//拿到所有構造方法,包括private的
        Constructor[] data = classType.getDeclaredConstructors();
        for (Constructor con : data) {
            con.setAccessible(true);
            Log.e(TAG,"con=" + con);
        }

        //根據引數型別拿到指定構造方法
        Constructor constructor = null;
        try {
            constructor = classType.getDeclaredConstructor(int.class);
            constructor.setAccessible(true);
            Log.e(TAG,"constructor=" + constructor);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

看日誌

E/MainActivity: con=private com.mango.reflex.PasswordEncrypt()
E/MainActivity: con=private com.mango.reflex.PasswordEncrypt(int)
E/MainActivity: con=com.mango.reflex.PasswordEncrypt(java.lang.Object[],com.android.tools.fd.runtime.InstantReloadException)
E/MainActivity: constructor=private com.mango.reflex.PasswordEncrypt(int)

可以看到能成功拿到構造方法,接下來通過構造方法來構建例項,然後呼叫encryptMD5方法

構造例項 呼叫public 方法及屬性

	try {
			//通過無參構造方法構建
            PasswordEncrypt encry = (PasswordEncrypt) data[0].newInstance();
            String result = encry.encryptMD5("123456");
            String tag = encry.TAG;
            Log.e(TAG,"result = " + result);
			//通過帶參構造方法構建
            PasswordEncrypt encry2 = (PasswordEncrypt) constructor.newInstance(2);
            String result2 = encry2.encryptMD5("123456");
            Log.e(TAG,"result2 = " + result2);

        } catch (InstantiationException e) {
            Log.e(TAG,"InstantiationException = " + e);
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            Log.e(TAG,"IllegalAccessException = " + e);
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

newInstance方法的引數是一個Object陣列,表示構造方法的引數,有幾個就傳幾個,沒有就不用傳;看日誌結果

11-12 11:09:20.088 2605-2605/com.mango.reflex E/PasswordEncrypt: PasswordEncrypt
11-12 11:09:20.094 2605-2605/com.mango.reflex E/MainActivity: result = e10adc3949ba59abbe56e057f20f883e
11-12 11:09:20.095 2605-2605/com.mango.reflex E/PasswordEncrypt: PasswordEncrypt version=2
11-12 11:09:20.095 2605-2605/com.mango.reflex E/MainActivity: result2 = e10adc3949ba59abbe56e057f20f883e

可以看到構造方法被呼叫,encryptMD5方法也被呼叫了

訪問修改private屬性

構造出的物件只能呼叫public修飾的方法和變數,其它修飾詞修飾的需要通過別的方法呼叫,如下

//根據屬性名獲取指定屬性
Field field = classType.getDeclaredField("version");
//設定對private修飾的屬性的訪問
field.setAccessible(true);
//獲取屬性值
int value = field.getInt(encry);
Log.e(TAG,"value = " + value);

E/MainActivity: value = 1

可以看到獲取到預設值1

接下來我們修改這個值

field.setInt(encry,10);
int valueNew = field.getInt(encry);
Log.e(TAG,"valueNew = " + valueNew);

E/MainActivity: valueNew = 10

呼叫private方法

接下來進行private修飾的方法的訪問(PasswordEncrypt類的encodeStr方法)

Method m = classType.getDeclaredMethod("encodeStr",String.class);
m.setAccessible(true);
String result = (String) m.invoke(encry,"123456");
Log.e(TAG,"result = " + result);

E/MainActivity: result = MTIzNDU2

從這個例子可以看到通過反射你可以做到平時學的Java規則做不到的事,總結下反射的使用步驟

反射獲取內部類

這裡內部類有成員內部類 ,靜態內部類,匿名內部類

public class InnerClass {   

    public InnerClass () { }

    private class InnerA {
        private String f = InnerA.class.getSimpleName();
        public InnerA() { }
    }

    private static class InnerB {
        private String f = InnerB.class.getSimpleName();
        public InnerB() {}
    }

    private Runnable r = new Runnable() {       
        @Override
        public void run() {
            
        }
    };

}  

		Class clazz = InnerClass.class;
        InnerClass container = (InnerClass ) clazz.newInstance();
        Class innerClazz[] = clazz.getDeclaredClasses();
        for (Class cls : innerClazz) {
            int mod = cls.getModifiers();
            String modifier = Modifier.toString(mod);
            if (modifier.contains("static")) {
                //構造靜態內部類例項
                Object obj1 = cls.newInstance();
                Field field1 = cls.getDeclaredField("f");
                field1.setAccessible(true);
            } else {
                // 構造成員內部類例項
                Constructor con2 = cls.getDeclaredConstructor(clazz);
                con2.setAccessible(true);
                Object obj2 = con2.newInstance(container);
                Field field2 = cls.getDeclaredField("f");
                field2.setAccessible(true);
            }
        }
        // 獲取匿名內部類例項
        Field field = clazz.getDeclaredField("r");
        field.setAccessible(true);
        Runnable r = (Runnable) field.get(container);
        r.run();

使用總結

  • 通過Class.forName拿到類的Class物件
  • 如果有公有構造方法,直接通過Class.newInstance()構造例項
  • 如果沒有公有構造方法,通過Class.getConstructor(Class<?>… parameterTypes)獲取指定構造方法物件Constructor,呼叫setAccessible設定執行訪問,再使用Constructor.newInstance(Object … initargs)方法構造物件例項
  • 如果是公有方法和屬性,可直接通過構建的例項訪問
  • 如果是私有屬性,先通過Class類的getDeclaredField(String name)方法獲取屬性物件Field,呼叫setAccessible設定執行訪問,通過Field類的get相關方法獲取屬性值,通過set相關方法修改屬性值
  • 如果是私有方法,先通過Class類的getDeclaredMethod(String name, Class<?>… parameterTypes)方法獲取指定方法物件Method,呼叫setAccessible設定執行訪問,最後呼叫invoke(Object receiver, Object… args)方法執行方法