目的
希望程序員每次調用startActivity()的時候,可以多打印一條日誌。
實現原理
startActivity() 執行過程中,會調用到ActivityThread的成員對象mInstrumentation的 execStartActivity()函數,於是我們可以自定義一個EvilInstrumentation(extends Instrumentation )類,重寫execStartActivity()函數,追加實現自己的邏輯,然後通過反射把ActivityThread的成員對象mInstrumentation替換爲我們自己寫的EvilInstrumentation的對象,達到偷樑換柱的目的。
代碼實現
App.java
package com.qunar.yuzhiyun.hook;
import android.app.Application;
import android.app.Instrumentation;
import android.content.Context;
import java.lang.reflect.Field;
/**
* Created by yuzhiyun on 17/8/11.
*/
public class App extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// 先獲取到當前的ActivityThread對象
Class activityThreadClass = null;
try {
activityThreadClass = Class.forName("android.app.ActivityThread");
Field currentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
currentActivityThreadField.setAccessible(true);
//傳給get和set方法的參數必須是擁有該字段的實例。如果字段是靜態字段(public static …),則傳null作爲get和set方法的參數
Object currentActivityThread = currentActivityThreadField.get(null);
// 拿到原始的 mInstrumentation字段
Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
mInstrumentationField.setAccessible(true);
Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
// 創建代理對象
Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation);
// 偷樑換柱
mInstrumentationField.set(currentActivityThread, evilInstrumentation);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
EvilInstrumentation.java
package com.qunar.yuzhiyun.hook;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import java.lang.reflect.Method;
/**
* Created by yuzhiyun on 17/8/11.
*/
public class EvilInstrumentation extends Instrumentation {
private static final String TAG = "EvilInstrumentation";
// ActivityThread中原始的對象, 保存起來
Instrumentation mBase;
public EvilInstrumentation(Instrumentation base) {
mBase = base;
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
// Hook之前, XXX到此一遊!
Log.d(TAG, "\n執行了startActivity, 參數如下: \n" + "who = [" + who + "], " +
"\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " +
"\ntarget = [" + target + "], \nintent = [" + intent +
"], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]");
// 開始調用原始的方法, 調不調用隨你,但是不調用的話, 所有的startActivity都失效了.
// 由於這個方法是隱藏的,因此需要使用反射調用;首先找到這個方法
try {
Method execStartActivity = Instrumentation.class.getDeclaredMethod(
"execStartActivity",
Context.class, IBinder.class, IBinder.class, Activity.class,
Intent.class, int.class, Bundle.class);
execStartActivity.setAccessible(true);
return (ActivityResult) execStartActivity.invoke(mBase, who,
contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
// 某該死的rom修改了 需要手動適配
throw new RuntimeException("do not support!!! pls adapt it");
}
}
}