原文連接:www.woaitqs.cc/android/201…html
Java 程序的運行須要相應的環境(Java Runtime Environment), 而這其中最有名的就是 JVM,JVM 提供了動態運行字節碼的能力,除了 JVM 幫咱們作連接、加載字節碼相關的事情外,也經過反射提供給咱們動態修改的能力。反射使得咱們可以在運行時,不知道類名、方法名的狀況下查看其中的接口、方法和字段等信息,另外一方面,也能夠動態調用方法、新建對象,甚至篡改字段值。java
那介紹了反射是幹嗎以後,反射能在實際的工做中發揮什麼做用嗎?Android 系統在設計的時候,出於安全和架構的考慮,利用了 Java 權限相關的東西(private,package等等,以及 @hide 這個註解)使得咱們沒法訪問某些字段,或者方法。但在實際開發過程當中,這些隱藏的字段或者方法卻能提供給咱們很是想要的特性。在這種矛盾的狀況下,反射就能知足咱們的需求,像是打開隱藏關卡的一把鑰匙。android
總結起來就是,反射提供了一種與 Class 文件進行動態交互的機制。例如在下面的入口函數中,就能夠看到 HashMapClass 裏全部的方法。安全
public class HashMapClass extends HashMap {
public static void main(String[] args) {
Method[] methods = HashMapClass.class.getMethods();
for (Method method : methods) {
System.out.println("method name is " + method.getName());
}
}
}複製代碼
在進行接下來的反射教程中,首先應該瞭解 Class Object。Java 中全部的類型,包括 int、float 等基本類型,都有與之相關的 Class 對象。若是知道對應的 Class name,能夠經過 Class.forName()
來構造相應的 Class 對象,若是沒有對應的 class,或者沒有加載進來,那麼會拋出 ClassNotFoundException 對象。架構
Class 封裝了一個類所包含的信息,主要的接口以下:app
try {
Class mClass = Class.forName("java.lang.Object");
// 不包含包名前綴的名字
String simpleName = mClass.getSimpleName();
// 類型修飾符, private, protect, static etc.
int modifiers = mClass.getModifiers();
// Modifier 提供的一些用於判讀類型的靜態方法.
Modifier.isPrivate(modifiers);
// 父類的信息
Class superclass = mClass.getSuperclass();
// 構造函數
Constructor[] constructors = mClass.getConstructors();
// 字段類型
Field[] fields = mClass.getFields();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}複製代碼
下面列舉一些反射常見的應用場景,主要從 Student 這個類進行入手。ide
public class Student {
private final String name;
private int grade = 1;
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
private int getGrade() {
return grade;
}
private void goToSchool() {
System.out.println(name + " go to school!");
}
}複製代碼
try {
Class studentClass = Student.class;
// 參數類型爲一個 String 的構造函數
Constructor constructor = studentClass.getConstructor(new Class[]{String.class});
// 實例化 student 對象
Student student = (Student)constructor.newInstance("Li Lei");
System.out.print(student.getName());
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}複製代碼
try {
Student student = new Student("Han MeiMei");
System.out.println("origin grade is " + student.getGrade());
Class studentClass = Student.class;
// 獲取聲明的 grade 字段,這裏要注意 getField 和 getDeclaredField 的區別
Field gradeField = studentClass.getDeclaredField("grade");
// 若是是 private 或者 package 權限的,必定要賦予其訪問權限
gradeField.setAccessible(true);
// 修改 student 對象中的 Grade 字段值
gradeField.set(student, 2);
System.out.println("after reflection grade is " + student.getGrade());
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}複製代碼
try {
Student student = new Student("Han MeiMei");
// 獲取私有方法,一樣注意 getMethod 和 getDeclaredMethod 的區別
Method goMethod = Student.class.getDeclaredMethod("goToSchool", null);
// 賦予訪問權限
goMethod.setAccessible(true);
// 調用 goToSchool 方法。
goMethod.invoke(student, null);
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}複製代碼
學以至用,如今咱們來經過實際的例子,來看看如何利用 Java 的反射特性來完成一些牛逼的功能。設想咱們想經過插件的方式,來啓動一個未註冊的 Activity,這就會涉及到不少問題,其中之一就是如何賦予這些插件 Activity 生命週期。這個例子就是經過反射的方式,來手動地進行 Activity 生命週期的通知。函數
要實現上述的功能,第一步就是要知道 Activity 的生命週期是如何運做的,要對代碼細節有所瞭解。由於反射所操做的對象是具體的 Class 對象,若是不清楚源碼細節,反射將無從提及。this
篇幅所限,具體的原理又較爲複雜,這裏列出連接 Android 插件化原理解析——Activity生命週期管理, 有興趣的同窗能夠自行查看,在這隻進行大致上的說明。spa
Activity 生命週期與 ActivityThread
息息相關,咱們來進行各個突破,先看看 Activity 是怎麼啓動的。當須要啓動 Activity 時,ActivityManagerService 會經過 Binder 機制向 ActivityThread 發送消息,通過鏈式地調用後,會執行到scheduleLaunchActivity
這個方法,咱們看看其內部的實現。
// we use token to identify this activity without having to send the
// activity itself back to the activity manager. (matters more with ipc)
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Configuration curConfig, Configuration overrideConfig, CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state, PersistableBundle persistentState, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
updateProcessState(procState, false);
ActivityClientRecord r = new ActivityClientRecord();
r.token = token;
r.ident = ident;
r.intent = intent;
r.referrer = referrer;
r.voiceInteractor = voiceInteractor;
r.activityInfo = info;
r.compatInfo = compatInfo;
r.state = state;
r.persistentState = persistentState;
r.pendingResults = pendingResults;
r.pendingIntents = pendingNewIntents;
r.startsNotResumed = notResumed;
r.isForward = isForward;
r.profilerInfo = profilerInfo;
r.overrideConfig = overrideConfig;
updatePendingConfiguration(curConfig);
sendMessage(H.LAUNCH_ACTIVITY, r);
}複製代碼
注意最後的 sendMessage
方法,說明內部是採用的 handler 機制來進行通訊的。在這篇文章中 Android 應用進程啓動流程 說起到當應用進程啓動後,會調用 ActivityThread
的 main 方法,並在這個方法中進行相應的消息循環初始化,其後在主線程上的消息傳遞都是經過 ActivityThread
中的 H
這個內部來進行初始化,這裏的 H
就是可能的突破口之一。
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
public static final int PAUSE_ACTIVITY = 101;
public static final int PAUSE_ACTIVITY_FINISHING= 102;
public static final int STOP_ACTIVITY_SHOW = 103;
public static final int STOP_ACTIVITY_HIDE = 104;
public static final int SHOW_WINDOW = 105;
public static final int HIDE_WINDOW = 106;
public static final int RESUME_ACTIVITY = 107;
public static final int SEND_RESULT = 108;
public static final int DESTROY_ACTIVITY = 109;
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
public static final int NEW_INTENT = 112;
public static final int RECEIVER = 113;
public static final int CREATE_SERVICE = 114;
public static final int SERVICE_ARGS = 115;
public static final int STOP_SERVICE = 116;
//......
public void handleMessage(Message msg){
// ...
}
}複製代碼
既然發現通訊是經過 H
這個 Handler 來完成的,那麼再看看 Handler 的實現原理,這裏也有一篇文章供參考, Android Handler機制全解析 。Handler 在內部維護着一個 callback 對象,當有消息發生時,會經過這個 callback 往外發送消息。
/** * Handle system messages here. */
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}複製代碼
若是可以替換 callback 變量,這樣消息就能夠傳遞到替換後的 callback 裏了。這樣是否能達到咱們的目的?
若是咱們反射的對象,擁有多個實例,那麼咱們就須要在不一樣的地方進行處理,顯然這樣會額外增長實現的複雜度,於是反射儘可能在 單例
或者靜態
實例上完成,代碼的複雜度能提高很多。
在前面提到了經過替換 callback 的方式,這樣是否可行,咱們來驗證下。首先 H
是放置在 ActivityThread 這個類裏面的,而 ActivityThread 運行的線程就是主線程,咱們知道每個應用都擁有一個主線程,於是這裏的 ActivityThread 只存在一份,進而也能夠保證 H
的惟一性。
另外一方面,ActivityThread 在內部也維護了 currentActivityThread 這個變量,雖然因爲 API 的訪問限制,不能直接訪問,但也一樣能夠由反射拿到。
至此,能夠證實這種方式理論上是能夠成功的。
首先,咱們自定義出自定義的 callback 對象,這個 callback 做爲 H
中 callback 的代理,這裏須要注意的是 msg 的定義要和底層保持一致,代碼以下:
public class LaunchCallback implements Handler.Callback {
public static final int LAUNCH_ACTIVITY = 100;
private Handler.Callback originCallback;
public LaunchCallback(Handler.Callback originCallback) {
this.originCallback = originCallback;
}
@Override
public boolean handleMessage(Message msg) {
if (msg.what == LAUNCH_ACTIVITY) {
Toast.makeText(
VApp.getApp().getApplicationContext(),
"activity is going to launch! ", Toast.LENGTH_SHORT).show();
}
return originCallback.handleMessage(msg);
}
}複製代碼
經過前文說起的反射方法,將運行的 callback 替換爲自定義的 callback,代碼以下:
public class InjectTool {
public static void dexInject() {
try {
// 經過反射調用 ActivityThread 的靜態方法, 獲取 currentActivityThread
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
// 獲取 currentActivityThread 這個示例中的 mH
Field handlerField = activityThreadClass.getDeclaredField("mH");
handlerField.setAccessible(true);
Handler handler = (Handler) handlerField.get(currentActivityThread);
// 修改 mH 中的 callback 字段
Field callbackField = Handler.class.getDeclaredField("mCallback");
callbackField.setAccessible(true);
Handler.Callback callback = (Handler.Callback) callbackField.get(handler);
callbackField.set(handler, new LaunchCallback(callback));
} catch (IllegalArgumentException | NoSuchMethodException | IllegalAccessException
| InvocationTargetException | ClassNotFoundException | NoSuchFieldException e) {
e.printStackTrace();
}
}
}複製代碼
在 Application 中進行注入,完成修改的落地
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
InjectTool.dexInject();
}複製代碼
實際運行的效果,如圖所示:
activity 其餘的生命週期也能夠一樣處理,這裏就再也不贅述了。
在經過反射實現相關功能的時候,第一件事情就是認真地閱讀源碼,理清其中的脈絡,其後找尋其中的突破點,這些點通常爲 static 方法或者單例對象,最後纔是代碼落地。