反射的定義以及組成
關于反射,一般書上的定義是這樣的:JAVA反射機制是在運行狀態(tài)中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意方法和屬性;這種動態(tài)獲取信息以及動態(tài)調用對象方法的功能稱為java語言的反射機制,這幾句解釋說明了反射的作用,動態(tài)的跟類進行交互,比如獲取隱藏屬性,修改屬性,獲取對象,創(chuàng)建對象或者方法等等,總之就一句話:
反射是一種具有與類進行動態(tài)交互能力的一種機制為什么要強調動態(tài)交互呢?因為一般情況下都是動態(tài)加載,也就是在運行的時候才會加載,而不是在編譯的時候,在需要的時候才進行加載獲取,或者說你可以在任何時候加載一個不存在的類到內存中,然后進行各種交互,或者獲取一個沒有公開的類的所有信息,換句話說,開發(fā)者可以隨時隨意的利用反射的這種機制動態(tài)進行一些特殊的事情。
反射的組成
由于反射最終也必須有類參與,因此反射的組成一般有下面幾個方面組成:
1.java.lang.Class.java:類對象;
2.java.lang.reflect.Constructor.java:類的構造器對象;
3.java.lang.reflect.Method.java:類的方法對象;
4.java.lang.reflect.Field.java:類的屬性對象;
下面一張圖說明了關系:
根據虛擬機的工作原理,一般情況下,類需要經過:加載->驗證->準備->解析->初始化->使用->卸載這個過程,如果需要反射的類沒有在內存中,那么首先會經過加載這個過程,并在在內存中生成一個class對象,有了這個class對象的引用,就可以發(fā)揮開發(fā)者的想象力,做自己想做的事情了。
反射的作用
前面只是說了反射是一種具有與Java類進行動態(tài)交互能力的一種機制,在Java和Android開發(fā)中,一般情況下下面幾種場景會用到反射機制.
● 需要訪問隱藏屬性或者調用方法改變程序原來的邏輯,這個在開發(fā)中很常見的,由于一些原因,系統(tǒng)并沒有開放一些接口出來,這個時候利用反射是一個有效的解決方法
● 自定義注解,注解就是在運行時利用反射機制來獲取的。
●在開發(fā)中動態(tài)加載類,比如在Android中的動態(tài)加載解決65k問題等等,模塊化和插件化都離不開反射,離開了反射寸步難行。
反射的工作原理
我們知道,每個java文件最終都會被編譯成一個.class文件,這些Class對象承載了這個類的所有信息,包括父類、接口、構造函數(shù)、方法、屬性等,這些class文件在程序運行時會被ClassLoader加載到虛擬機中。當一個類被加載以后,Java虛擬機就會在內存中自動產生一個Class對象,而我們一般情況下用new來創(chuàng)建對象,實際上本質都是一樣的,只是這些底層原理對我們開發(fā)者透明罷了,我們前面說了,有了class對象的引用,就相當于有了Method,Field,Constructor的一切信息,在Java中,有了對象的引用就有了一切,剩下怎么發(fā)揮是開發(fā)者自己的想象力所能決定的了。
反射的簡單事例
前面說了這么多理論,下面簡單實踐一下
public class Student {
private int age;//年齡
private String name;//姓名
private String address;//地址
private static String sTest;
public Student() {
throw new IllegalAccessError("Access to default Constructor Error!");
}
private Student(int age, String name, String address) {
this.age = age;
this.name = name;
this.address = address;
sTest = "測試反射";
}
private int getAge() {
return age;
}
private void setAge(int age) {
this.age = age;
}
private String getName() {
return name;
}
private void setName(String name) {
this.name = name;
}
private String getAddress() {
return address;
}
private void setAddress(String address) {
this.address = address;
}
private static String getTest() {
return sTest;
}
}
在這里為了練習,刻意用了private來修飾成員變量和方法 下面代碼用構造器,方法和屬性和靜態(tài)方法分別來獲取一下,
public class StudentClient {
public static void main(String[] args) throws Exception{
Class<?> clazz=Class.forName("ClassLoader.Student");
Constructor constructors=clazz.getDeclaredConstructor(int.class,String.class,String.class);
constructors.setAccessible(true);
//利用構造器生成對象
Object mStudent=constructors.newInstance(27,"小文","北京市海定區(qū)XX號");
System.out.println(mStudent.toString());
//獲取隱藏的int屬性
Field mAgeField=clazz.getDeclaredField("age");
mAgeField.setAccessible(true);
int age= (int) mAgeField.get(mStudent);
System.out.println("年齡為:"+age);
//調用隱藏的方法
Method getAddressMethod=clazz.getDeclaredMethod("getAge");
getAddressMethod.setAccessible(true);
int newage= (int) getAddressMethod.invoke(mStudent);
System.out.println("年齡為:"+newage);
//調用靜態(tài)方法
Method getTestMethod=clazz.getDeclaredMethod("getTest");
getTestMethod.setAccessible(true);
String result= (String) getTestMethod.invoke(null);
System.out.println("調用靜態(tài)方法:"+result);
}
}
結果如下:
大家都看得懂,應該可以理解,有同學說不是有很多getDeclared和get的方法嗎, 實際上都差不多的,只不過用的范圍不一樣而已,getDeclared獲取的是僅限于本類的所有的不受訪問限制的,而get獲取的是包括父類的但僅限于public修飾符的,F(xiàn)ield和Method也是一樣的道理,這個大家注意一下就好,最后一個需要注意的是調用靜態(tài)方法和調用實例方法有點區(qū)別,調用實例方法一定需要一個類的實例,而調用靜態(tài)方法不需要實例的引用,其實這是JVM的在執(zhí)行方法上的有所區(qū)別,JVM在執(zhí)行方法的時候會創(chuàng)建一個堆棧,堆棧里面保存了局部變量表以及其他的一些必要的信息,其中局部變量表里面也包含了局部參數(shù),而局部參數(shù)里面保存了當前方法的形參,如果是調用實例方法的話,那么形參的第一個參數(shù)就是當前的類的引用了,而調用的是靜態(tài)方法的話,那么第一個參數(shù)是為null的,這一點無法通過任何手段去繞過,換句話說調用實例方法一定需要一個類的引用,關于這一點,讀者可以自己去查閱有關JVM的書籍。
當然了,反射的作用絕不止這些,在數(shù)組,泛型,設計模式等方面依然發(fā)揮了巨大的作用,但原理并沒有脫離上面說的,讀者可以多查看相關源碼學習,源碼就是最好的學習資源。
反射在Android框架層的應用
這是本文需要重點說明的,眾所周知,Android中的FrameWork是用Java語言編寫的,自然離不開一些反射的影子,而利用反射更是可以達到我們一些常規(guī)方法難于達到的目的,再者反射也是Java層中進行Hook的重要手段,目前的插件化更是大量利用反射。
首先提出需求:如何監(jiān)控Activity的創(chuàng)建和啟動過程? 有同學說了,我在Activity里面重寫生命周期方法不就可以了嗎?實際上這個是達不到需求的,因為很簡單,這些生命周期方法的調用是在創(chuàng)建和啟動之后很久的事情了,里面的生命周期方法相對于整個Activity來說是比較后面的事情,要想解決這個問題,必須要知道Activity是怎么來,中間經過了哪個流程,最后去了哪里,只有明白了這些,才能知道在哪個階段做哪些事情,我們知道,Activity的啟動是一個IPC過程,也就是Binder機制,里面經過了本地進程->AMS進程-->再回到本地進程,下面是實例圖:
圖畫的有些粗糙,大家將就看吧,從上面可以看到,Activity從本地到遠程AMS以后,遠程AMS只是做了權限以及屬性的檢查,然后再回到本地進程,這才開始真正的創(chuàng)建和檢查,我們才代碼來分析一下,涉及到的類有Handler以及ActivityThread和Instrumentation類,首先從遠端進程回到本地進程之后,系統(tǒng)的Handler類H會發(fā)送一個消息:LAUNCH_ACTIVITY,代碼如下:省略了一些非必要代碼,不然篇幅太長,下面的代碼都是在ActivityThread.java里面的
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
隨后調用了handleLaunchActivity方法,handleLaunchActivity方法里面又調用了 performLaunchActivity方法,代碼如下:
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
mSomeActivitiesChanged = true;
if (r.profilerInfo != null) {
mProfiler.setProfiler(r.profilerInfo);
mProfiler.startProfiling();
}
// Make sure we are running with the most recent config.
handleConfigurationChanged(null, null);
if (localLOGV) Slog.v(
TAG, "Handling launch of " + r);
// Initialize before creating the activity
WindowManagerGlobal.initialize();
Activity a = performLaunchActivity(r, customIntent);
....
}
在performLaunchActivity里面終于創(chuàng)建了Activity了,進入performLaunchActivity里面看看有一段非常核心的代碼:
Activity activity = null;
try {
//
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
//通過mInstrumentation.newActivity()方法創(chuàng)建了Activity,mInstrumentation是Instrumentation類的實例
,對象的類為:Instrumentation.java
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
我們現(xiàn)在已經知道了Activity的創(chuàng)建了,是由Instrumentation的newActivity()方法實現(xiàn),我們看一下方法:
public Activity newActivity(Class<?> clazz, Context context,
IBinder token, Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
Object lastNonConfigurationInstance) throws InstantiationException,
IllegalAccessException {
Activity activity = (Activity)clazz.newInstance();
ActivityThread aThread = null;
activity.attach(context, aThread, this, token, 0, application, intent,
info, title, parent, id,
(Activity.NonConfigurationInstances)lastNonConfigurationInstance,
new Configuration(), null, null, null);
return activity;
}
看到沒,作為四大組件的Activity其實也是一個普通對象,也是由反射創(chuàng)建的,只不過由于加入了生命周期方法,才有組件這個活生生的對象存在, 所以說Android中反射無處不在,分析完了啟動和創(chuàng)建的過程,回到剛才那個需求來說,如何監(jiān)控Activity的啟動和創(chuàng)建呢?讀者可以先自己想一下,首先啟動是由Handler來發(fā)送消息,具體的在里面handlerMessage方法實現(xiàn)的, 這也是Handler里面的處理代碼的順序,如下代碼:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
大不了我們自己弄一個自定義的Handler.Callback接口,然后替換掉那個H類里面的處理接口,這樣就可以監(jiān)控Activity的啟動了,好方法,我們來寫一下代碼:
public static void hookHandler(Context context) throws Exception {
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
//獲取主線程對象
Object activityThread = currentActivityThreadMethod.invoke(null);
//獲取mH字段
Field mH = activityThreadClass.getDeclaredField("mH");
mH.setAccessible(true);
//獲取Handler
Handler handler = (Handler) mH.get(activityThread);
//獲取原始的mCallBack字段
Field mCallBack = Handler.class.getDeclaredField("mCallback");
mCallBack.setAccessible(true);
//這里設置了我們自己實現(xiàn)了接口的CallBack對象
mCallBack.set(handler, new UserHandler(handler));
}
public class UserHandler implements Callback {
//這個100一般情況下最好也反射獲取,當然了你也可以直接寫死,跟系統(tǒng)的保持一致就好了
public static final int LAUNCH_ACTIVITY = 100;
private Handler origin;
public UserHandler( Handler mHandler) {
this.origin = mHandler;
}
@Override
public boolean handleMessage(Message msg) {
if (msg.what == LAUNCH_ACTIVITY) {
//這樣每次啟動的時候可以做些額外的事情
Log.d("[app]","做你想要的事情");
}
origin.handleMessage(msg);
return false;
}
}
好了,Activity的啟動監(jiān)控就這樣了,一般寫在application里面的attachBaseContext()方法里面,因為這個方法時機最早。 好了,下面來說說Activity的創(chuàng)建的監(jiān)控,前面我們知道了,Instrumentation的newActivity方法負責創(chuàng)建了Activity,那么突破口也就是在這里了,創(chuàng)建為我們自定義的Instrumentation,然后反射替換掉就好,同時重寫newActivity方法,可以做些事情,比如記錄時間之類,下面是代碼:
public static void hookInstrumentation() throws Exception{
Class<?> activityThread=Class.forName("android.app.ActivityThread");
Method currentActivityThread=activityThread.getDeclaredMethod("currentActivityThread");
currentActivityThread.setAccessible(true);
//獲取主線程對象
Object activityThreadObject=currentActivityThread.invoke(null);
//獲取Instrumentation字段
Field mInstrumentation=activityThread.getDeclaredField("mInstrumentation");
mInstrumentation.setAccessible(true);
Instrumentation instrumentation= (Instrumentation) mInstrumentation.get(activityThreadObject);
CustomInstrumentation customInstrumentation=new CustomInstrumentation(instrumentation);
//替換掉原來的,就是把系統(tǒng)的instrumentation替換為自己的Instrumentation對象
mInstrumentation.set(activityThreadObject,CustomInstrumentation);
Log.d("[app]","Hook Instrumentation成功");
}
public class CustomInstrumentation extends Instrumentation{
private Instrumentation base;
public CustomInstrumentation(Instrumentation base) {
this.base = base;
}
//重寫創(chuàng)建Activity的方法
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Log.d("[app]","you are hook!,做自己想要的事情");
Log.d("[app]","className="+className+" intent="+intent);
return super.newActivity(cl, className, intent);
}
}
同樣在application的attachBaseContext注入就好,當然了,Instrumentation還有其他方法可以重寫,大家可以去試一試,下面是運行的結果:
看到沒,監(jiān)控啟動和創(chuàng)建都實現(xiàn)了,其實這里面也有好多擴展的,比如啟動Activity的時候,Instrumentation一樣是可以監(jiān)控的,你懂的,再次重寫方法,然后實現(xiàn)自己的邏輯,另外,small插件化框架就是Hook了Instrumentation來動態(tài)加載Activity的,大家有興趣可以去看看,除了以上方法,還有很多方法可以用類似的手段去實現(xiàn),大家一定要多練習,好記性不如爛筆頭就是這個道理。
使用反射需要注意的地方
從前面可以看出,使用反射非常方便,而且在一些特定的場合下可以實現(xiàn)特別的需求,但是使用反射也是需要注意一下幾點的:
●反射最好是使用public修飾符的,其他修飾符有一定的兼容性風險,比如這個版本有,另外的版本可能沒有
●大家都知道的Android開源代碼引起的兼容性的問題,這是Android系統(tǒng)開源的最大的問題,特別是那些第三方的ROM,要慎用。
●如果大量使用反射,在代碼上需要優(yōu)化封裝,不然不好管理,寫代碼不僅僅是實現(xiàn)功能,還有維護性和可讀性方法也需要加強,demo中可以直接這樣粗糙些,在項目中還是需要好好組織封裝下的。
今天的文章就寫到這里,感謝大家閱讀。