前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >startActivity的Hook之路

startActivity的Hook之路

作者头像
大大大大大先生
发布2018-09-04 15:18:50
8530
发布2018-09-04 15:18:50
举报

Hook Context.startActivity和Activity.startActivity

  • Context的startActivity其实具体是由ContextImpl去实现的,首先来看下framework的相关源码ContextImpl:
代码语言:javascript
复制
@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);
    }

由上可知,最终是通过ActivityThread里面的mInstrumentation对象来执行execStartActivity,而ActivityThread是在Android APP启动执行Java main方法的时候创建的ActivityThread:

代码语言:javascript
复制
public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
}

上面的ActivityThread thread = new ActivityThread();thread.attach(false);这两句就是创建了一个ActivityThread对象,再看attach方法,这里代码太多,只贴出有用的部分:

代码语言:javascript
复制
private void attach(boolean system) {
        sCurrentActivityThread = this;
        // 余下省略...
}

sCurrentActivityThread就是ActivityThread对象,一个APP进程只对应一个ActivityThread对象,同时这个对象可由Activity 自身提供的一个静态公有方法来获取到,这个方法在应用层是没办法调用到的,但是可以动过反射method来获取到这个方法并invoke,因为这是一个静态的方法:

代码语言:javascript
复制
private static volatile ActivityThread sCurrentActivityThread;

public static ActivityThread currentActivityThread() {
        return sCurrentActivityThread;
    }

当然,你也可以通过反射Field来获取到sCurrentActivityThread对象:

代码语言:javascript
复制
private static Object getActivityThread() {
        Object target = null;
        try {
            Class<?> activityThreadCls = Class.forName("android.app.ActivityThread");
            Method method = activityThreadCls.getDeclaredMethod("currentActivityThread");
            method.setAccessible(true);
//            Field sCurrentActivityThreadField = activityThreadCls.getDeclaredField("sCurrentActivityThread");
//            sCurrentActivityThreadField.setAccessible(true);
//            target = sCurrentActivityThreadField.get(null);
            target = method.invoke(null);
            return target;
        } catch (Exception e) {
            throw new RuntimeException(e.toString());
        }
    }

现在我们来思考一下,想要在startActivity之前做一些事情,然后在startActivity之后再做一些事情其实可以hook startActivity 里面的execStartActivity这个点,因为一个APP进程只对应一个ActivityThread,而且这个对象还是static类型的,比较好通过反射直接获取到具体对象sCurrentActivityThreadField.get(null),这里传null即可,如果不是静态类型的对象,那反射的话这里还要填入具体的对象,由于execStartActivity这个方法是Instrumentation类里面的,而这个类的对象是ActivityThread里面的一个全局变量:

代码语言:javascript
复制
mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);

你可通过反射方法来获取这个对象,也可通过反射Field来获取到这个对象,然后把这个对象设置成我们自己的代理对象,代理对象其实就是代理Instrumentation,这里用的是静态代理:

代码语言:javascript
复制
public class BaseInstrumentationProxy extends Instrumentation {

    private static final String TAG = "BaseInstrumentationProxy";

    private Instrumentation mBase;

    public final void setInstrumentation(Instrumentation mBase) {
        this.mBase = mBase;
    }

    protected void before() {

    }

    protected void after() {

    }

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        if (mBase == null) {
            throw new RuntimeException("mBase is null");
        }

        before();
        try {
            Method execStartActivity = Instrumentation.class.getDeclaredMethod(
                    "execStartActivity",
                    Context.class, IBinder.class, IBinder.class, Activity.class,
                    Intent.class, int.class, Bundle.class);
            execStartActivity.setAccessible(true);
            ActivityResult activityResult = (ActivityResult) execStartActivity.invoke(mBase, who, contextThread, token, target, intent, requestCode, options);
            after();
            return activityResult;
        } catch (Exception e) {
            throw new RuntimeException("no support hook:" + e.toString());
        }
    }
}

这个代理对象其实就是在execStartActivity之前做一些自定义的操作,然后在execStartActivity之后做一些自定义的操作,而它实际上是通过反射来执行真正的Instrumentation对象的execStartActivity方法:

代码语言:javascript
复制
public static void hookStartActivityFormContext(BaseInstrumentationProxy baseInstrumentationProxy) {
        Object activityThread = InstrumentationHooker.getActivityThread();
        try {
            Field mInstrumentationField = activityThread.getClass().getDeclaredField("mInstrumentation");
            mInstrumentationField.setAccessible(true);
            baseInstrumentationProxy.setInstrumentation((Instrumentation) mInstrumentationField.get(activityThread));
            mInstrumentationField.set(activityThread, baseInstrumentationProxy);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

这里注意一点,ContextImpl是调用ActivityThread对象里面的Instrumentation来执行execStartActivity,而Activity是调用自身的Instrumentation来执行execStartActivity,实际上Activity里面的Instrumentation是在Activity创建的时候由ActivityThread对象里面的Instrumentation传进来的,Activity里面的Instrumentation对象引用也是指向ActivityThread里面的Instrumentation对象,但是我们现在是把ActivityThread对象引用替换成我们自定义的代理对象,而Activity里面的Instrumentation依然还是原来的,并没有被替换,所以如果要Hook Activity的startActivity是要把Activity里面的Instrumentation对象设置成我们自定义的代理对象:

代码语言:javascript
复制
public static void hookStartActivityFormContext(BaseInstrumentationProxy baseInstrumentationProxy) {
        Object activityThread = InstrumentationHooker.getActivityThread();
        try {
            Field mInstrumentationField = activityThread.getClass().getDeclaredField("mInstrumentation");
            mInstrumentationField.setAccessible(true);
            baseInstrumentationProxy.setInstrumentation((Instrumentation) mInstrumentationField.get(activityThread));
            mInstrumentationField.set(activityThread, baseInstrumentationProxy);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void hookStartActivityFromActivity(Activity activity, BaseInstrumentationProxy baseInstrumentationProxy) {
        Field mInstrumentationField = null;
        try {
            mInstrumentationField =Activity.class.getDeclaredField("mInstrumentation");
            mInstrumentationField.setAccessible(true);
            baseInstrumentationProxy.setInstrumentation((Instrumentation) mInstrumentationField.get(activity));
            mInstrumentationField.set(activity, baseInstrumentationProxy);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

这样就hook成功了,然后在startActivity之前调用一下以上两个方法就可以了:

代码语言:javascript
复制
public class MainActivity extends Activity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        InstrumentationHooker.hookStartActivityFromActivity(this, new BaseInstrumentationProxy(){
            @Override
            protected void before() {
                Log.d(TAG, "hookStartActivityFromActivity before");
            }

            @Override
            protected void after() {
                Log.d(TAG, "hookStartActivityFromActivity after");
            }
        });

        Intent intent = new Intent(this, SecondActivity.class);
        startActivity(intent);
    }
}

public class SecondActivity extends Activity {

    private static final String TAG = "SecondActivity";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        InstrumentationHooker.hookStartActivityFormContext(new BaseInstrumentationProxy(){
            @Override
            protected void before() {
                Log.d(TAG, "hookStartActivityFromActivity before");
            }

            @Override
            protected void after() {
                Log.d(TAG, "hookStartActivityFromActivity after");
            }
        });

        Intent intent = new Intent(this, ThirdActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        getApplicationContext().startActivity(intent);
    }
}

最后运行的结果如下:

代码语言:javascript
复制
09-28 23:15:52.843 22148-22148/com.lhd.hooktest D/MainActivity: hookStartActivityFromActivity before
09-28 23:15:52.849 22148-22148/com.lhd.hooktest D/MainActivity: hookStartActivityFromActivity after

09-28 23:15:52.935 22148-22148/com.lhd.hooktest D/SecondActivity: hookStartActivityFormContext before
09-28 23:15:52.943 22148-22148/com.lhd.hooktest D/SecondActivity: hookStartActivityFormContext after
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.09.28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Hook Context.startActivity和Activity.startActivity
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档