美文网首页
startActivity的Hook之路

startActivity的Hook之路

作者: 大大大大大先生 | 来源:发表于2017-09-28 23:18 被阅读69次

    Hook Context.startActivity和Activity.startActivity

    • Context的startActivity其实具体是由ContextImpl去实现的,首先来看下framework的相关源码ContextImpl:
    @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:

    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方法,这里代码太多,只贴出有用的部分:

    private void attach(boolean system) {
            sCurrentActivityThread = this;
            // 余下省略...
    }
    

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

    private static volatile ActivityThread sCurrentActivityThread;
    
    public static ActivityThread currentActivityThread() {
            return sCurrentActivityThread;
        }
    

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

    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里面的一个全局变量:

    mMainThread.getInstrumentation().execStartActivity(
                    getOuterContext(), mMainThread.getApplicationThread(), null,
                    (Activity) null, intent, -1, options);
    

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

    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方法:

    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对象设置成我们自定义的代理对象:

    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之前调用一下以上两个方法就可以了:

    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);
        }
    }
    

    最后运行的结果如下:

    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
    

    相关文章

      网友评论

          本文标题:startActivity的Hook之路

          本文链接:https://www.haomeiwen.com/subject/wexbextx.html