概述
第二種方式思路非常清晰,直接Hook Instrumentation。
由activity啓動流程知道,startActivity會交給Activity的mInstrumentation.execStartActivity()來處理。
最終會調用ActivityThread裏面的performLaunchActivity()方法。performLaunchActivity()方法進而調用 mInstrumentation.newActivity()會創建啓動Activity。
所以步驟很簡單。
因爲有Application.startActivity()和Activity.startActivity()。等會說Application.startActivity(),先說Activity.startActivity()。
- hook Activity的mInstrumentation變量
- hook ActivityThread的mInstrumentation變量
- 在我們的Instrumentation類裏面攔截execStartActivity方法,在裏面將Intent替換成我們的佔坑Activity。
- 在我們的Instrumentation類裏面攔截newActivity方法,將Intent替換回來,再完成Activity的創建。
代碼
首先是我們的HookedInstrumentation 類,我們通過反射得到原有的Instrumentation 放入我們的類中,在execStartActivity方法中替換Intent,完成AMS的驗證。newActivity方法中再將Intent替換回來。這裏需要注意一下,因爲在newActivity中傳入了我們自己的ClassLoader,關於ReflectUtil.setField(ContextThemeWrapper.class, activity, Constants.FIELD_RESOURCES, pluginApp.mResources);這個代碼先不要管,下篇會說怎樣加載插件APK的資源。
public class HookedInstrumentation extends Instrumentation {
public static final String TAG = "Lpp";
protected Instrumentation mBase;
private PluginManager mPluginManager;
public HookedInstrumentation(Instrumentation base, PluginManager pluginManager) {
mBase = base;
mPluginManager = pluginManager;
}
/**
* 覆蓋掉原始Instrumentation類的對應方法,用於插件內部跳轉Activity時適配
*
* @Override
*/
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
mPluginManager.hookToStubActivity(intent);
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) {
e.printStackTrace();
throw new RuntimeException("do not support!!!" + e.getMessage());
}
}
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
if (mPluginManager.hookToPluginActivity(intent)) {
String targetClassName = intent.getComponent().getClassName();
PluginApp pluginApp = mPluginManager.getLoadedPluginApk();
Activity activity = mBase.newActivity(pluginApp.mClassLoader, targetClassName, intent);
Log.d(TAG, "newActivity: " + activity);
Log.d(TAG, "newActivity getApplicationContext: " + activity.getApplication());
ReflectUtil.setField(ContextThemeWrapper.class, activity, Constants.FIELD_RESOURCES, pluginApp.mResources);
return activity;
}
return super.newActivity(cl, className, intent);
}
}
完成我們的Instrumentation類後,接下來就是反射設置給ActivityThread和Activity了。
Activity
public void hookCurrentActivityInstrumentation(Activity activity) {
ReflectUtil.setActivityInstrumentation(activity, sInstance);
}
public static void setActivityInstrumentation(Activity activity, PluginManager manager) {
try {
sActivityInstrumentation = (Instrumentation) sActivityInstrumentationField.get(activity);
HookedInstrumentation instrumentation = new HookedInstrumentation(sActivityInstrumentation, manager);
sActivityInstrumentationField.set(activity, instrumentation);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
ActivityThread
public void hookInstrumentation() {
try {
Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation();
final HookedInstrumentation instrumentation =
new HookedInstrumentation(baseInstrumentation, this);
Object activityThread = ReflectUtil.getActivityThread();
ReflectUtil.setInstrumentation(activityThread, instrumentation);
} catch (Exception e) {
e.printStackTrace();
}
}
下面是初始化的代碼,得到ActivityThread等等
public static final String METHOD_currentActivityThread = "currentActivityThread";
public static final String CLASS_ActivityThread = "android.app.ActivityThread";
public static final String FIELD_mInstrumentation = "mInstrumentation";
public static final String TAG = "Lpp";
private static Instrumentation sInstrumentation;
private static Instrumentation sActivityInstrumentation;
private static Field sActivityThreadInstrumentationField;
private static Field sActivityInstrumentationField;
private static Object sActivityThread;
public static boolean init() {
//獲取當前的ActivityThread對象
Class<?> activityThreadClass = null;
try {
activityThreadClass = Class.forName(CLASS_ActivityThread);
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod(METHOD_currentActivityThread);
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
//拿到在ActivityThread類裏面的原始mInstrumentation對象
Field instrumentationField = activityThreadClass.getDeclaredField(FIELD_mInstrumentation);
instrumentationField.setAccessible(true);
sActivityThreadInstrumentationField = instrumentationField;
sInstrumentation = (Instrumentation) instrumentationField.get(currentActivityThread);
sActivityThread = currentActivityThread;
sActivityInstrumentationField = Activity.class.getDeclaredField(FIELD_mInstrumentation);
sActivityInstrumentationField.setAccessible(true);
return true;
} catch (ClassNotFoundException
| NoSuchMethodException
| IllegalAccessException
| InvocationTargetException
| NoSuchFieldException e) {
e.printStackTrace();
}
return false;
}
補充
上面說的是通過Activity.startActivity()完成跳轉時的情況。
當我們使用Application.startActivity()完成跳轉時,只需要hook ActivityThread裏面的Instrumentation就可以了。
讓我們來看爲什麼。
getApplication().startActivity();
public final Application getApplication() {
return mApplication;
}
Application繼承的是ContextWrapper,ContextWrapper又繼承自Context,所以我們在ContextWrapper裏面找startActivity方法。
public class ContextWrapper extends Context {
Context mBase;
······
@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}
}
調用了mBase的startActivity,但是mBase是Context類型的,Context是一個抽象類,所以我們尋找他的實現類。
他的實現類是ContextImpl 。尋找他的startActivity方法。
class ContextImpl extends Context {
······
final ActivityThread mMainThread;
@Override
public void startActivity(Intent intent) {
warnIfCallingFromSystemProcess();
startActivity(intent, null);
}
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
// Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
// generally not allowed, except if the caller specifies the task id the activity should
// be launched in.
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
&& options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}
}
可以很清楚的看到,他從mMainThread中得到了Instrumentation對象,並調用了他的execStartActivity方法。而這個mMainThread就是ActivityThread 。
所以說通過Application啓動活動時,只需要hook ActivityThread裏面的Instrumentation就可以了