Activity插件化之Hook Instrumentation

Hook Instrumentation实现要比Hook IActivityManager实现要简洁一些,示例代码会和Hook IActivityManager实现有重复,重复的部分这里不再赘述。 
Hook Instrumentation实现同样也需要用到占坑Activity,与Hook IActivityManager实现不同的是,用占坑Activity替换插件Activity以及还原插件Activity的地方不同。

frameworks/base/core/java/android/app/Activity.java
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
        if (mParent == null) {
            options = transferSpringboardActivityOptions(options);
            Instrumentation.ActivityResult ar =
              //启动Activity
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
          ...
        } else {
         ...
        }
    }
 startActivityForResult方法中调用了Instrumentation的execStartActivity方法来激活Activity的生命周期。 
如图3所示,ActivityThread启动Activity的过程中会调用ActivityThread的performLaunchActivity方法,如下所示。 
frameworks/base/core/java/android/app/ActivityThread.java

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {       
        ...
        //创建要启动Activity的上下文环境
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            //用类加载器来创建Activity的实例
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);//1
          ...
        } catch (Exception e) {
          ...
        }
      ...
        return activity;
    }
注释1处调用了mInstrumentation的newActivity方法,其内部会用类加载器来创建Activity的实例。看到这里我们可以得到方案,就是在Instrumentation的execStartActivity方法中用占坑SubActivity来通过AMS的验证,在Instrumentation的newActivity方法中还原TargetActivity,这两部操作都和Instrumentation有关,因此我们可以用自定义的Instrumentation来替换掉mInstrumentation。首先我们自定义一个Instrumentation,在execStartActivity方法中将启动的TargetActivity替换为SubActivity,如下所示。 
InstrumentationProxy.java

/**
* @author ex-chenmengjia001
* @date 2019/1/23
*/
public class InstrumentationProxy extends Instrumentation {
private Instrumentation mInstrumentation;
private PackageManager mPackageManager;

public InstrumentationProxy(Instrumentation instrumentation, PackageManager packageManager) {
mInstrumentation = instrumentation;
mPackageManager = packageManager;
}

public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
List<ResolveInfo> infos = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);
if (infos == null || infos.size() == 0) {
intent.putExtra(HookerHelper.TARGET_INTENsT_NAME, intent.getComponent().getClassName());
intent.setClassName(who, "com.example.cmj.pluginization.StubActivity");
}
try {
Method execStartActivity = Instrumentation.class.getDeclaredMethod("execStartActivity", Context.class, IBinder.class
, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);
return (ActivityResult) execStartActivity.invoke(mInstrumentation, who, contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
String intentName = intent.getStringExtra(HookerHelper.TARGET_INTENsT_NAME);
if (!TextUtils.isEmpty(intentName)) {
return super.newActivity(cl, intentName, intent);
}
return super.newActivity(cl, className, intent);
}
}

首先查找要启动的Activity是否已经在AndroidManifest.xml中注册了,如果没有就在注释1处将要启动的Activity(TargetActivity)的ClassName保存起来用于后面还原TargetActivity,接着在注释2处替换要启动的Activity为StubActivity,最后通过反射调用execStartActivity方法,这样就可以用StubActivity通过AMS的验证。在InstrumentationProxy 的newActivity方法还原TargetActivity.

newActivity方法中创建了此前保存的TargetActivity,完成了还原TargetActivity。 
编写hookInstrumentation方法,用InstrumentationProxy替换mInstrumentation: 
HookHelper.java

public static void hookInstrumentation(Context context)throws Exception{
Class<?> contextImpClass = Class.forName("android.app.ContextImpl");
Field mMainThread = FieldUtil.getField(contextImpClass, "mMainThread");
Object activityTHread = mMainThread.get(context);
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Field mInstrumentation = FieldUtil.getField(activityThreadClass, "mInstrumentation");
FieldUtil.setField(activityThreadClass,activityTHread,"mInstrumentation",new InstrumentationProxy((Instrumentation) mInstrumentation.get(activityTHread),context.getPackageManager()));
}

获取ContextImpl类的ActivityThread类型的mMainThread字段,注释2出获取当前上下文环境的ActivityThread对象。 获取ActivityThread类中的mInstrumentation字段,最后用InstrumentationProxy来替换mInstrumentation。 在MyApplication的attachBaseContext方法中调用HookHelper的hookInstrumentation方法,运行程序,当我们点击启动插件按钮,发现启动的是插件TargetActivity。

 需要在MyApplication中调用HookerHelper.hookInstrumentation(base);public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
try {
HookerHelper.hookInstrumentation(base);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
 * @author ex-chenmengjia001
 * @date 2019/1/22
 */
public class FieldUtil {
    public static Object getField(Class clazz, Object target, String name) throws Exception {
        Field declaredField = clazz.getDeclaredField(name);
        declaredField.setAccessible(true);
        return declaredField.get(target);
    }

    public static Field getField(Class clazz, String name) throws Exception {
        Field declaredField = clazz.getDeclaredField(name);
        declaredField.setAccessible(true);
        return declaredField;
    }

    public static void setField(Class clazz, Object target, String name, Object value) throws Exception {
        Field declaredField = clazz.getDeclaredField(name);
        declaredField.setAccessible(true);
        declaredField.set(target, value);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值