1. Hook点选择
了解了Activity的启动流程后可得知,Activity的启动会走Intrumentation类的execStartActivity和newActivity,而检测是否注册是在execStartActivity之后,真正到达构建Activity的方法newActivity 时对是否有注册无需关注,所以可以考虑Hook点为Intrumentation,可以直接在ActivityThread类中将Insturmentation的对象mInstrumentation 替换成代理对象InsturmentationProxy
2. 具体实施
- 使用已注册的PlaceholderActivity进行占坑,实际启动的是未注册的TargetActivity
2.1 编写InsturmentationProxy类
/**
* InstrumentationProxy代理类对Instrumentation进行Hook
*
*@authorLTP 2023/5/16
*/
public class InstrumentationProxy extends Instrumentation {
private final Instrumentation mInstrumentation;
private final PackageManager mPackageManager;
private static final StringPLACEHOLDER_ACTIVITY_PACKAGE_NAME= "com.btpj.hook.PlaceholderActivity";
public InstrumentationProxy(Instrumentation mInstrumentation, PackageManager mPackageManager) {
this.mInstrumentation = mInstrumentation;
this.mPackageManager = mPackageManager;
}
/**
* 直接在execStartActivity中将要启动的TargetActivity(未注册)替换为占坑的PlaceholderActivity
*/
@SuppressLint("QueryPermissionsNeeded")
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
List<ResolveInfo> resolveInfoList = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);
if (resolveInfoList == null || resolveInfoList.size() == 0) {
// resolveInfoList为空表示启动的Activity未注册
intent.putExtra(HookHelper.TARGET_ACTIVITY_NAME, intent.getComponent().getClassName());
intent.setClassName(who,PLACEHOLDER_ACTIVITY_PACKAGE_NAME);
}
try {
Method execMethod = Instrumentation.class.getDeclaredMethod("execStartActivity", Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);
return (ActivityResult) execMethod.invoke(mInstrumentation, who, contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 创建时当匹配到启动的为PlaceholderActivity时又转化为TargetActivity
*/
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
String targetActivityName = intent.getStringExtra(HookHelper.TARGET_ACTIVITY_NAME);
if (className.equals(PLACEHOLDER_ACTIVITY_PACKAGE_NAME) && !TextUtils.isEmpty(targetActivityName)) {
return super.newActivity(cl, targetActivityName, intent);
}
return super.newActivity(cl, className, intent);
}
}
2.2 创建HookHelper帮助类
/**
* Hook帮助类
* @authorLTP 2023/5/16
*/
public class HookHelper {
public static final StringTARGET_ACTIVITY_NAME= "target_activity_name";
public static void hookInstrumentation(Context context) throws Exception {
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
// 寻找ActivityThread类中的sCurrentActivityThread属性
Field activityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
activityThreadField.setAccessible(true);
Object activityThread = activityThreadField.get(context);
// 寻找ActivityThread类中的mInstrumentation属性
Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
mInstrumentationField.setAccessible(true);
LogUtil.INSTANCE.d("Hook Instrumentation进ActivityThread成功");
// 将mInstrumentation属性赋值为InstrumentationProxy对象
mInstrumentationField.set(activityThread, new InstrumentationProxy((Instrumentation) mInstrumentationField.get(activityThread), context.getPackageManager()));
}
}
2.3 在APP的自定义Application中调用
/**
*@authorLTP 2023/5/18
*/
class App : Application() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
try {
HookHelper.hookInstrumentation(base)
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
2.4 调用启动方法即可
startActivity(TargetActivity.newIntent(this@HookActivity))