zoukankan      html  css  js  c++  java
  • Service插件化解决方案

    --摘自《android插件化开发指南》

    1.ActivityThread最终是通过Instrumentation启动一个Activity的。而ActivityThread启动Service并不借助于Instrumentation,而是直接把Service反射出来就启动了。Instrumentation只给Activity提供服务

    2.一般预先在宿主app中创建10个StubService占位就够了

    ***startService的解决方案***

    首先把插件和宿主的dex合并

    /**
     * 由于应用程序使用的ClassLoader为PathClassLoader
     * 最终继承自 BaseDexClassLoader
     * 查看源码得知,这个BaseDexClassLoader加载代码根据一个叫做
     * dexElements的数组进行, 因此我们把包含代码的dex文件插入这个数组
     * 系统的classLoader就能帮助我们找到这个类
     *
     * 这个类用来进行对于BaseDexClassLoader的Hook
     * 类名太长, 不要吐槽.
     * @author weishu
     * @date 16/3/28
     */
    public final class BaseDexClassLoaderHookHelper {
    
        public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)
                throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {
            // 获取 BaseDexClassLoader : pathList
            Object pathListObj = RefInvoke.getFieldObject(DexClassLoader.class.getSuperclass(), cl, "pathList");
    
            // 获取 PathList: Element[] dexElements
            Object[] dexElements = (Object[]) RefInvoke.getFieldObject(pathListObj, "dexElements");
    
            // Element 类型
            Class<?> elementClass = dexElements.getClass().getComponentType();
    
            // 创建一个数组, 用来替换原始的数组
            Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);
    
            // 构造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile) 这个构造函数
            Class[] p1 = {File.class, boolean.class, File.class, DexFile.class};
            Object[] v1 = {apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0)};
            Object o = RefInvoke.createObject(elementClass, p1, v1);
    
            Object[] toAddElementArray = new Object[] { o };
            // 把原始的elements复制进去
            System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);
            // 插件的那个element复制进去
            System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length);
    
            // 替换
            RefInvoke.setFieldObject(pathListObj, "dexElements", newElements);
        }
    }

    其次采用“欺上瞒下”的方法

    public class AMSHookHelper {
    
        public static final String EXTRA_TARGET_INTENT = "extra_target_intent";
    
        public static void hookAMN() throws ClassNotFoundException,
                NoSuchMethodException, InvocationTargetException,
                IllegalAccessException, NoSuchFieldException {
    
            //获取AMN的gDefault单例gDefault,gDefault是final静态的
            Object gDefault = RefInvoke.getStaticFieldObject("android.app.ActivityManagerNative", "gDefault");
    
            // gDefault是一个 android.util.Singleton<T>对象; 我们取出这个单例里面的mInstance字段
            Object mInstance = RefInvoke.getFieldObject("android.util.Singleton", gDefault, "mInstance");
    
            // 创建一个这个对象的代理对象MockClass1, 然后替换这个字段, 让我们的代理对象帮忙干活
            Class<?> classB2Interface = Class.forName("android.app.IActivityManager");
            Object proxy = Proxy.newProxyInstance(
                    Thread.currentThread().getContextClassLoader(),
                    new Class<?>[] { classB2Interface },
                    new MockClass1(mInstance));
    
            //把gDefault的mInstance字段,修改为proxy
            Class class1 = gDefault.getClass();
            RefInvoke.setFieldObject("android.util.Singleton", gDefault, "mInstance", proxy);
        }
    
        public static void hookActivityThread() throws Exception {
    
            // 先获取到当前的ActivityThread对象
            Object currentActivityThread = RefInvoke.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");
    
            // 由于ActivityThread一个进程只有一个,我们获取这个对象的mH
            Handler mH = (Handler) RefInvoke.getFieldObject(currentActivityThread, "mH");
    
            //把Handler的mCallback字段,替换为new MockClass2(mH)
            RefInvoke.setFieldObject(Handler.class, mH, "mCallback", new MockClass2(mH));
        }
    }

    其中,HookService,让AMS启动StubService的实现在类MockClass1上

    class MockClass1 implements InvocationHandler {
    
        private static final String TAG = "MockClass1";
    
        // 替身StubService的包名
        private static final String stubPackage = "jianqiang.com.activityhook1";
    
        Object mBase;
    
        public MockClass1(Object base) {
            mBase = base;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
            Log.e("bao", method.getName());
    
            if ("startService".equals(method.getName())) {
                // 只拦截这个方法
                // 替换参数, 任你所为;甚至替换原始StubService启动别的Service偷梁换柱
    
                // 找到参数里面的第一个Intent 对象
                int index = 0;
                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof Intent) {
                        index = i;
                        break;
                    }
                }
    
                //get StubService form UPFApplication.pluginServices
                Intent rawIntent = (Intent) args[index];
                String rawServiceName = rawIntent.getComponent().getClassName();
    
                String stubServiceName = UPFApplication.pluginServices.get(rawServiceName);
    
                // replace Plugin Service of StubService
                ComponentName componentName = new ComponentName(stubPackage, stubServiceName);
                Intent newIntent = new Intent();
                newIntent.setComponent(componentName);
    
                // Replace Intent, cheat AMS
                args[index] = newIntent;
    
                Log.d(TAG, "hook success");
                return method.invoke(mBase, args);
            } else if ("stopService".equals(method.getName())) {
                // 只拦截这个方法
                // 替换参数, 任你所为;甚至替换原始StubService启动别的Service偷梁换柱
    
                // 找到参数里面的第一个Intent 对象
                int index = 0;
                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof Intent) {
                        index = i;
                        break;
                    }
                }
    
                //get StubService form UPFApplication.pluginServices
                Intent rawIntent = (Intent) args[index];
                String rawServiceName = rawIntent.getComponent().getClassName();
                String stubServiceName = UPFApplication.pluginServices.get(rawServiceName);
    
                // replace Plugin Service of StubService
                ComponentName componentName = new ComponentName(stubPackage, stubServiceName);
                Intent newIntent = new Intent();
                newIntent.setComponent(componentName);
    
                // Replace Intent, cheat AMS
                args[index] = newIntent;
    
                Log.d(TAG, "hook success");
                return method.invoke(mBase, args);
            }
    
            return method.invoke(mBase, args);
        }
    }

    第2,AMS被欺骗后,它原本会通知APP启动StubService,而我们要Hook掉ActivityThread的mH对象的mCallback对象,仍然截获它的handleMessage方法(handleCreateService方法),具体实现在MockClass2中

    class MockClass2 implements Handler.Callback {
    
        Handler mBase;
    
        public MockClass2(Handler base) {
            mBase = base;
        }
    
        @Override
        public boolean handleMessage(Message msg) {
    
            Log.d("baobao4321", String.valueOf(msg.what));
            switch (msg.what) {
    
                // ActivityThread里面 "CREATE_SERVICE" 这个字段的值是114
                // 本来使用反射的方式获取最好, 这里为了简便直接使用硬编码
                case 114:
                    handleCreateService(msg);
                    break;
            }
    
            mBase.handleMessage(msg);
            return true;
        }
    
        private void handleCreateService(Message msg) {
            // 这里简单起见,直接取出插件Servie
    
            Object obj = msg.obj;
            ServiceInfo serviceInfo = (ServiceInfo) RefInvoke.getFieldObject(obj, "info");
    
            String realServiceName = null;
    
            for (String key : UPFApplication.pluginServices.keySet()) {
                String value = UPFApplication.pluginServices.get(key);
                if(value.equals(serviceInfo.name)) {
                    realServiceName = key;
                    break;
                }
            }
    
            serviceInfo.name = realServiceName;
        }
    }

    在宿主中调用

    Intent intent = new Intent();
    intent.setComponent(
            new ComponentName("jianqiang.com.testservice1",
                    "jianqiang.com.testservice1.MyService1"));
    startService(intent);
    
    Intent intent = new Intent();
    intent.setComponent(
            new ComponentName("jianqiang.com.testservice1",
                    "jianqiang.com.testservice1.MyService1"));
    stopService(intent);

    ***bindService的解决方案***

    只要在实现类MockClass1中增加

    else if ("bindService".equals(method.getName())) {
    
        // 找到参数里面的第一个Intent 对象
        int index = 0;
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof Intent) {
                index = i;
                break;
            }
        }
    
        Intent rawIntent = (Intent) args[index];
        String rawServiceName = rawIntent.getComponent().getClassName();
        String stubServiceName = UPFApplication.pluginServices.get(rawServiceName);
    
    
        // replace Plugin Service of StubService
        ComponentName componentName = new ComponentName(stubPackage, stubServiceName);
        Intent newIntent = new Intent();
        newIntent.setComponent(componentName);
    
        // Replace Intent, cheat AMS
        args[index] = newIntent;
    
        Log.d(TAG, "hook success");
        return method.invoke(mBase, args);
    }

    宿主中调用

    Intent intent = new Intent();
    intent.setComponent(
            new ComponentName("jianqiang.com.testservice1",
                    "jianqiang.com.testservice1.MyService2"));
    bindService(intent, conn, Service.BIND_AUTO_CREATE);
    
    unbindService(conn);

    问1:为什么不在unbind的时候欺骗AMS?

    因为unbind语法是unbindService(conn),AMS会根据conn来找到对应的Service,所以我们不需要把MyService2替换为StubService2

    问2:为什么在MockClass2中不需要吧StubService2切换回MyService2?

    因为bindService是先走handleCreateService再走handleBindService方法。在handleCreateService方法中已经将StubService2切换回MyService2了,所以后面不需要切换了。

    欢迎关注我的微信公众号:安卓圈

  • 相关阅读:
    分小组
    抽签
    快速排序
    Zookeeper一致性协议原理Zab
    Zookeeper架构、ZAB协议、选举
    spring AOP
    spring bean生命周期
    [LeetCode] 35. Search Insert Position ☆(丢失的数字)
    [LeetCode] 29. Divide Two Integers(不使用乘除取模,求两数相除) ☆☆☆
    代理模式和装饰器模式的区别
  • 原文地址:https://www.cnblogs.com/anni-qianqian/p/10107685.html
Copyright © 2011-2022 走看看