Android 插件化实现方式(Hook)

一、首先我们要找到Hook的点

1. 分析
  • 我们先大概看下activity的启动流程(图片来自Android 插件化开发指南)


    image
  • 当我们调用startActivity的时候,AMS对我们要启动的Activity进行检查,是否在AndroidManifest中声明过,如果没有就报没有在AndroidManifest的错误。这个时候需要欺骗AMS,我们需要hook,要它去检查一个我们预配置的Activity,通过AMS的检查。
  • 为什么不能直接hook掉AMS,AMS是系统的进程,管理者所有app的启动,不仅仅只是我们自己的app。
2. 看看源码怎么启动的(主要就是拦截这个方法startActivity)
 //通过ActivityManagerNative.getDefault()获取一个对象,开始启动新的Activity
            int result = ActivityManagerNative.getDefault().startActivity(whoThread,
            
            who.getBasePackageName(), intent,intent.resolveTypeIfNeeded(who.getContentResolver()),
            
            token, target != null ? target.mEmbeddedID : null,
            requestCode, 0, null, options);
3. 看看代码的实现

    private Context mContext;
    private Class<?> mProxyClass;

    private String TAG = HookUtil.class.getName();
    private final String EXTRA_ORIGIN_INTENT = "EXTRA_ORIGIN_INTENT";

    public HookUtil(Context context, Class<?> proxyClass) {
        this.mContext = context.getApplicationContext();
        this.mProxyClass = proxyClass;
    }
    
    public void hookStartActivity() throws Exception {
        //1. 通过反射,拿到IActivityManager对象;
        Class amnClass = Class.forName("android.app.ActivityManagerNative");
        //2. 获得指定的私有属性
        Field gDefaultField = amnClass.getDeclaredField("gDefault");
        gDefaultField.setAccessible(true);
        // 获取字段上面的值传递null  证明是属性是static的,此处返回的是
        // new Singleton<IActivityManager>()
        Object gDefault = gDefaultField.get(null);


        Class singletonClass = Class.forName("android.util.Singleton");
        Field mInstanceField = singletonClass.getDeclaredField("mInstance");
        mInstanceField.setAccessible(true);
        //不是static的方法 ,需要传入当前使用的对象  此处返回的是IActivityManager
        Object iamInstance = mInstanceField.get(gDefault);

        Class iamClass = Class.forName("android.app.IActivityManager");
        Object proxyInstance = Proxy.newProxyInstance(HookUtil.class.getClassLoader(),
                new Class[]{iamClass},
                // InvocationHandler 必须执行者,谁去执行
                new StartActivityInvocationHandler(iamInstance));

        //f.set(obj, "刘德华");
        mInstanceField.set(gDefault, proxyInstance);

    }
    
    
    
        private class StartActivityInvocationHandler implements InvocationHandler {
        // 方法执行者
        private Object mObject;

        public StartActivityInvocationHandler(Object object) {
            this.mObject = object;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 替换Intent,过AndroidManifest.xml检测
            if (method.getName().equals("startActivity")) {
                Log.e(TAG,"Activity已经开始启动");
                Log.e(TAG,"小弟到此一游!!!");
                // 1.首先获取原来的Intent
                Intent originIntent = (Intent) args[2];

                // 2.创建一个安全的
                Intent safeIntent = new Intent(mContext, mProxyClass);
                args[2] = safeIntent;

                // 3.绑定原来的Intent
                safeIntent.putExtra(EXTRA_ORIGIN_INTENT, originIntent);
            }
            return method.invoke(mObject, args);
        }
    }

4. 初始化插件
 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ButterKnife.bind(this);
        mButton.setText("test");


        //初始化插件
        HookUtil hookUtil =
                new HookUtil(this, ProxyActivity.class);
        try {
            hookUtil.hookStartActivity();
        } catch (Exception e) {
            e.printStackTrace();
        }
  }
  
  
   @OnClick(R.id.btn)
   public void btnOnclick() {
        Intent intent = new Intent(MainActivity.this, Main3Activity.class
        );
        startActivity(intent);
    }
5. 打印结果(没有报错,打印了当前activity的stop方法,证明通过AMS的检查了)
com.dhcc.net.plug.HookUtil: Activity已经开始启动
com.dhcc.net.plug.HookUtil: 小弟到此一游!!!

二、启动时替换成我们自己的Actvity

1. 当AMS加载activty完成以后,就要启动activity了,这个时候他是通过ActivityThread类中的Handler去处理的。我们首先看看Handler是怎么分发消息的(我们处理msg.callback,将优先级最大化)
/**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
2. 替换成为自己的Activity
 public void hookLaunchActivity() throws Exception{
        // 3.4.1 获取ActivityThread实例
        Class<?> atClass = Class.forName("android.app.ActivityThread");
        Field scatField = atClass.getDeclaredField("sCurrentActivityThread");
        scatField.setAccessible(true);
        Object sCurrentActivityThread = scatField.get(null);
        
        
        // 3.4.2 获取ActivityThread中的mH
        Field mhField = atClass.getDeclaredField("mH");
        mhField.setAccessible(true);
        Object mHandler = mhField.get(sCurrentActivityThread);
        
        
        // 3.4 设置当前对象(也就是ActivityThread)的mH的成员变量
        Class<?> handlerClass = Class.forName("android.os.Handler");
        Field mCallbackField = handlerClass.getDeclaredField("mCallback");
        mCallbackField.setAccessible(true);
        mCallbackField.set(mHandler,new HandlerCallBack());
    }
    
    
    private class HandlerCallBack implements Handler.Callback{

        @Override
        public boolean handleMessage(Message msg) {
            Log.e(TAG,"handleMessage");
            // 每发一个消息都会走一次这个CallBack发放
            if(msg.what == 100){
                handleLaunchActivity(msg);
            }
            return false;
        }

        /**
         * 开始启动创建Activity拦截
         * @param msg
         */
        private void handleLaunchActivity(Message msg) {
            try {
               Object record = msg.obj;
                // 1.从record 获取过安检的Intent
                Field intentField = record.getClass().getDeclaredField("intent");
                intentField.setAccessible(true);
                Intent proxyInent = (Intent) intentField.get(record);

                // 2.从safeIntent中获取原来的originIntent
                Intent realIntent = proxyInent.getParcelableExtra(EXTRA_ORIGIN_INTENT);

                // 3.重新设置回去
                if(realIntent != null){
                    Log.e(TAG,"启动我们自己的activity了");
                    intentField.set(record,realIntent);
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
3. 修改一下OnCreate方法
   protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ButterKnife.bind(this);
        mButton.setText("test");


        //初始化插件
        HookUtil hookUtil =new HookUtil(this, ProxyActivity.class);
        try {
            hookUtil.hookStartActivity();
            hookUtil.hookLaunchActivity();
        } catch (Exception e) {
            e.printStackTrace();
        }
  }
4. 看看打印结果
com.dhcc.net.plug.HookUtil: Activity已经开始启动
com.dhcc.net.plug.HookUtil: 小弟到此一游!!!
com.dhcc.net.plug.HookUtil: 启动我们自己的activity了
com.dhcc.net E/Main3Activity: 我是没有注册的Activity
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容