美文网首页
Android插件化原理--Hook Activity

Android插件化原理--Hook Activity

作者: HAPPYers | 来源:发表于2019-08-12 23:30 被阅读0次

    POINT

    • 代理
    • 反射
    • Hook

    原理

    为了hook startActivity,我们先分析源码。(这里我先在app的build.gradle中把targetSdkVersioncompileSdkVersion改为26,而不是29,为了便于查看源码)
    首先写一个startActivity函数,然后跟入

    public void startActivity(Intent intent) {
            this.startActivity(intent, null);
        }
    

    再稍微跟入后

    public void startActivity(Intent intent, @Nullable Bundle options) {
            if (options != null) {
                startActivityForResult(intent, -1, options);
            } else {
                // Note we want to go through this call for compatibility with
                // applications that may have overridden the method.
                startActivityForResult(intent, -1);
            }
        }
    

    因为前面的options是null,所以可知调用了startActivityForResult(intent, -1);语句。
    再看startActivityForResult

    public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {
            startActivityForResult(intent, requestCode, null);
        }
    
    public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
                @Nullable Bundle options) {
            if (mParent == null) {
                options = transferSpringboardActivityOptions(options);
                Instrumentation.ActivityResult ar =
                    mInstrumentation.execStartActivity(
                        this, mMainThread.getApplicationThread(), mToken, this,
                        intent, requestCode, options);
                if (ar != null) {
                    mMainThread.sendActivityResult(
                        mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                        ar.getResultData());
                }
                if (requestCode >= 0) {
                    // If this start is requesting a result, we can avoid making
                    // the activity visible until the result is received.  Setting
                    // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
                    // activity hidden during this time, to avoid flickering.
                    // This can only be done when a result is requested because
                    // that guarantees we will get information back when the
                    // activity is finished, no matter what happens to it.
                    mStartedActivity = true;
                }
    
                cancelInputsAndStartExitTransition(options);
                // TODO Consider clearing/flushing other event sources and events for child windows.
            } else {
                if (options != null) {
                    mParent.startActivityFromChild(this, intent, requestCode, options);
                } else {
                    // Note we want to go through this method for compatibility with
                    // existing applications that may have overridden it.
                    mParent.startActivityFromChild(this, intent, requestCode);
                }
            }
        }
    

    在跟入源码中显示红色的一块,我们可以看到startActivity真正调用的是mInstrumentation.execStartActivity()方法,而mInstrumentation 是 Activity 的一个私有变量。
    按照前面的代理思想,我们只要新建一个继承 Instrumentation 的新类,然后重写 execStartActivity() ,用代理包装后,通过反射把这个新类替换原来的mInstrumentation即可。

    实现

    新建Application类,用以在MainActivity执行前做好proxy处理
    先在AndroidManifest中声明我们的application

      <application
    ...
            android:name=".MyApplication"
            >
    ...
        </application>
    

    创建我们的MyApplication

    public class MyApplication extends Application {
    
        private static final String TAG = "HAPPY";
        private static final String ACTIVITY_THREAD="android.app.ActivityThread";
        private static final String CURRENT_ACTIVITY_THREAD="sCurrentActivityThread";
        private static final String INSTRUMENT="mInstrumentation";
        @Override
        public void onCreate() {
    
            super.onCreate();
            Log.d(TAG, "onCreate From Application ");
            try {
                attachContext();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void attachContext() throws Exception {
            // get current ActivityThread Object
            Class<?> activityThreadClass = Class.forName(ACTIVITY_THREAD);
            Field currentActivityThreadField = activityThreadClass.getDeclaredField(CURRENT_ACTIVITY_THREAD);
            //Field currentActivityThreadField = activityThreadClass.getDeclaredField(CURRENT_ACTIVITY_THREAD);
            currentActivityThreadField.setAccessible(true);
            Object currentActivityThread = currentActivityThreadField.get(null);
    
            // get original mInstrumentation field
            Field mInstrumentationField = activityThreadClass.getDeclaredField(INSTRUMENT);
            //Field mInstrumentationField = activityThreadClass.getField(INSTRUMENT);
            mInstrumentationField.setAccessible(true);
            Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
    
            // create Proxy
            Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation);
    
            // set original currentActivityThread as evilInstrumentation(our handler)
            mInstrumentationField.set(currentActivityThread, evilInstrumentation);
            Log.d(TAG, "attachContext: Hook finish!");
        }
    }
    

    然后创建我们需要被代理的Instrumentation,用以替换原来的mInstrumentation

    
    public class EvilInstrumentation extends Instrumentation {
        //private static final String TAG = "EvilInstrumentation";
        private static final String TAG="HAPPY";
    
        // ActivityThread Instrumentation Object
        private Instrumentation mBase;
    
        EvilInstrumentation(Instrumentation base) {
            mBase = base;
        }
    
        public ActivityResult execStartActivity(
                Context who, IBinder contextThread, IBinder token, Activity target,
                Intent intent, int requestCode, Bundle options) {
    
            // before Hook
            Log.d(TAG, "\nNow Hook startActivity, paramters are:" + "\ntoken = [" + token + "], " +
                    "\ntarget = [" + target + "], \nintent = [" + intent +
                    "], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]");
    
            // Invoke original execStartActivity method by reflect
            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) {
                Log.d(TAG, "execStartActivity: Fail to exec by hook");
                throw new RuntimeException("Do not support! please adapt it manually.");
            }
        }
    }
    

    为了测试hook的效果,我们新建一个activity,然后在MainActivity的按钮中绑定跳转的逻辑

    Intent intent=new Intent(MainActivity.this,Main2Activity.class);
    startActivity(intent);
    

    至此,我们的工程demo就完成了。

    工程结构图


    hook startActivity效果

    我们点击按钮跳转activity后就会log出参数。


    参考

    相关文章

      网友评论

          本文标题:Android插件化原理--Hook Activity

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