Android进阶解密④—插件化原理

廉价感情. 提交于 2020-11-17 14:04:01

在学习插件化之前需要看前面几篇文章:

动态加载技术:

在程序运行时,动态加载一些程序中原本不存在的可执行文件并运行起来,,随着应用技术的发展,动态加载技术逐渐派生出两个分支,热修复和插件化;

  • 热修复:用于修复bug
  • 插件化:解决应用庞大,功能模块解耦,复用其他apk的代码
插件化思想:

将复用的apk作为插件,插入另一个apk中,比如淘宝中会有咸鱼的页面,用淘宝为咸鱼引流,使用插件化技术,可以直接使用咸鱼apk中的dex文件,这样省去再次开发一套咸鱼页面的成本,并且有效的降低了淘宝apk的耦合度;

Activity插件化原理:

插件化activity的目的是直接使用另一个apk的activity,而activity的启动和生命周期的管理需要经过AMS的处理,另一个apk的activity没有在本项目的manifest注册,肯定是无法通过的,所以我们需要hook startActivity的流程,绕过ams的验证,可以在本项目使用一个占坑activity,在发送给ams前将插件activity换成占坑activity去通过ams的验证,验证好以后在真实的启动时再将插件activity换回来;

步骤:
  • 事先在本项目准备好占坑activity
  • 使用占坑activity绕过ams验证
  • 还原插件activity

1. 准备占坑activity

直接在原项目准备一个空白的activity即可,记得必须在manifest注册,下文叫他SubActivity

2. 使用插件activity替换占坑activity

在交给ams进程验证之前,在用户进程会经过两个类的传递,Instrumentation, iActivityManager,者两个类都可以作为hook点,这里介绍hook iActivityManager的这种方法;

2.1 创建hook点的代理类,iActivityManagerProxy
public class IActivityManagerProxy implements InvocationHandler {
   
   

    private Object realActivityManager;

    public IActivityManagerProxy(Object realActivityManager) {
   
   
        this.realActivityManager = realActivityManager;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   
   
        if ("startActivity".equals(method.getName())){
   
   
            //  首先找到,原本需要启动的插件activity的原始intent
            Intent originIntent = null;
            int index = 0;
            for (int i = 0;i<args.length;i++){
   
   
                if (args[i] instanceof Intent){
   
   
                    originIntent = (Intent) args[i];
                    index = i;
                    break;
                }
            }
            //  新建欺骗ams的占坑activity的intent
            Intent fakeIntent = new Intent();
            fakeIntent.setClass("xxx.xxx.xxx",SubActivity.class);
            //  将真实的intent保存在fakeIntent中用于第三步的还原操作
            fakeIntent.putExtra("real_intent",originIntent);
            //  将fakeIntent写回原来的arges数组中
            args[index] = fakeIntent;
        }
        return method.invoke(realActivityManager,args);
    }
}

这里使用的动态代理创建iActivityManager的代理,首先找到原本启动的插件Activity的Intent,然后新建一个启动SubActivity的intent替换它;

2.2替换原本的iActivityManager:
    public void hookAMS() throws Exception {
   
   
        // 获取ActivityManager getService 返回的单例
        Class ActivityManagerClazz = ActivityManager.class;
        Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
        Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);

        //  通过单例.get()获取iActivityManager, 这两步需要参考源码的iActivityManager的获取
        Class singleClazz = IActivityManagerSingleton.getClass();
        Method getMethod = singleClazz.getDeclaredMethod("get");
        Object iActivityManager = getMethod.invoke(IActivityManagerSingleton,null);
        
        // 生成动态代理对象
        Object proxyInstance = Proxy.newProxyInstance(
                ActivityManagerClazz.getClassLoader(),
                ActivityManagerClazz.getInterfaces(),
                new IActivityManagerProxy(iActivityManager));

        // 将代理对象设置到单例上
        Field mInstanceField = singleClazz.getField("mInstance");
        mInstanceField.set(IActivityManagerSingleton,proxyInstance);
    }
  • 这个方法需要在startActivity前调用

3. 还原插件Activity

绕开ams验证后,我们还需要真实的启动TargetActivity,再学习了Handler机制后,我们知道message的处理顺序是首先会判断当前message.callback有没有逻辑,会首先执行callback;我们可以将Message作为Hook点

3.1 创建自定义CallBack,在handleMessage处理前,将fakeIntent换成真实的intent
  class MCallBack implements android.os.Handler.Callback {
   
   
        @Override
        public boolean handleMessage(Message msg) {
   
   
            try {
   
   
                Object activityClientRecord = msg.obj;
                // 获取fakeIntent
                Class acrClazz = activityClientRecord.getClass();
                Field intentField = acrClazz.getDeclaredField("intent");
                Intent intent = (Intent) intentField.get(activityClientRecord);
                // 取出targetActivity的Intent
                Intent realIntent = intent.getParcelableExtra("real_intent");
                // 将realIntent的内容设置到fakeIntent
                intent.setComponent(realIntent.getComponent());
                
            } catch (NoSuchFieldException e) {
   
   
                e.printStackTrace();
            } catch (IllegalAccessException e) {
   
   
                e.printStackTrace();
            }
            msg.getTarget().handleMessage(msg);
            return true;
        }
    }
3.2 hook ActivityThread, 修改主线程的H(Handler)的CallBack属性,原理参考dispatchMessage方法
    private void hookActivityThread() throws Exception {
   
   
        Class activityThreadClass = Class.forName("android.app.ActivityThread");
        Field singleInstanceField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
        Object activityThreadInstance = singleInstanceField.get(null);
        
        Field mHField = activityThreadClass.getDeclaredField("mH");
        Handler handler = (Handler) mHField.get(activityThreadInstance);
        
        // 修改handler 的callback
        Class handlerClazz = handler.getClass();
        Field callbackField = handlerClazz.getDeclaredField("mCallback");
        callbackField.set(handler,new MCallBack());
    }

在Handler机制中有两个callback,一个是Handler.mCallback,一个是Message.callback

    public void dispatchMessage(Message msg) {
   
   
        if (msg.callback != null) {
   
   
            handleCallback(msg);
        } else {
   
   
            if (mCallback != null) {
   
   
                if (mCallback.handleMessage(msg)) {
   
   
                    return;
                }
            }
            handleMessage(msg);
        }
    }

在loop中轮询处理message的时候会调用dispatchMessage;如果Message.callback不会null就处理Runnable的回调然后结束,如果msg.callback为null,则先执行Handler的mCallback,并根据Handler的mCallback.handleMessage的返回值判断是否执行Handler.handleMessage;
根据上面的流程,我们可以在ActivityThread的H处理startActivity这个Message的handleMessage前,在H的Callback中插入修改intent的代码,做到真实的开启TargetActivity

3.3 插件Activity的生命周期管理:

上面的操作只做到了开启activity,插件activity的生命周期是如何管理的,AMS通过token来对activity进行识别管理,而插件activity token的绑定是不受影响的,所以插件activity是具有生命周期的;

Service插件化原理

代理分发实现:

当启动插件Service时,就会先启动代理Service,当代理Service运行后,在其onStartCommand中启动插件Service;

步骤:
  • 项目中准备好代理Service
  • hook iActivityManager 启动代理Service
  • 代理分发:
  1. ProxyService需要长时间对插件Service进行分发,所以需要return START_STICKY ProxyService重新创建
  2. 创建插件Service,attach,onCreate;

1. 在项目中创建一个ProxyService,在manifest中注册;

2. hook iActivityManager,将要启动的TargetService换成ProxyService

2.1 创建自定义iActivityManagerProxy
public class IActivityManagerProxy implements InvocationHandler {
   
   

    private Object realActivityManager;

    public IActivityManagerProxy(Object realActivityManager) {
   
   
        this.realActivityManager = realActivityManager;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   
   
        if ("startService".equals(method.getName())){
   
   
            Intent targetIntent = null;
            int index = 0;
            for (int i = 0;i<args.length;i++){
   
   
                if (args[i] instanceof Intent){
   
   
                    targetIntent = (Intent) args[i];
                    index = i;
                    break;
                }
            }
            Intent proxyIntent = new Intent();
            proxyIntent.setClassName("com.xx.xx","com.xx.xx.ProxyService");
            proxyIntent.putExtra("target_intent",targetIntent);
            args[index] = proxyIntent;
        }
        return method.invoke(realActivityManager,args);
    }
}
2.2 hook AMS替换原来的IActivityManager 同上
 public void hookAMS() throws Exception {
   
   
        // 获取ActivityManager getService 返回的单例
        Class ActivityManagerClazz = ActivityManager.class;
        Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
        Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);

        //  通过单例.get()获取iActivityManager, 这两步需要参考源码的iActivityManager的获取
        Class singleClazz = IActivityManagerSingleton.getClass();
        Method getMethod = singleClazz.getDeclaredMethod("get");
        Object iActivityManager = getMethod.invoke(IActivityManagerSingleton,null);
        
        // 生成动态代理对象
        Object proxyInstance = Proxy.newProxyInstance(
                ActivityManagerClazz.getClassLoader(),
                ActivityManagerClazz.getInterfaces(),
                new IActivityManagerProxy(iActivityManager));

        // 将代理对象设置到单例上
        Field mInstanceField = singleClazz.getField("mInstance");
        mInstanceField.set(IActivityManagerSingleton,proxyInstance);
    }

只要在startService前调用这段代码,就会启动proxyService,下面我们在proxyService中对targetService进行分发;

2.3 在proxyService中启动TargetService:
  • 调用attach绑定Context
  • 调用onCreate
public class ProxyService extends Service {
   
   
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
   
   
        return null;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
   
   
        try {
   
   
            // 准备attach方法的参数
            Class activityThreadClazz = null;
            activityThreadClazz = Class.forName("android.app.ActivityThread");
            Method getApplicationMethod = activityThreadClazz.getDeclaredMethod("getApplicationMethod");
            Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThreadField.setAccessible(true);
            // activityThread
            Object activityThread = sCurrentActivityThreadField.get(null);
            // applicationThread
            Object applicationThread = getApplicationMethod.invoke(activityThread, null);
            Class iInterFaceClazz = Class.forName("android.os.IInterface");
            Method asBinderMethod = iInterFaceClazz.getDeclaredMethod("asBinder");
            asBinderMethod.setAccessible(true);
            // token
            Object token = asBinderMethod.invoke(applicationThread);
            // iActivityManager
            Class ActivityManagerClazz = ActivityManager.class;
            Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
            Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);
            Class singleClazz = IActivityManagerSingleton.getClass();
            Method getMethod = singleClazz.getDeclaredMethod("get");
            Object iActivityManager = getMethod.invoke(IActivityManagerSingleton, null);

            // targetService
            Class serviceClazz = Class.forName("android.app.Service");
            Service targetService = (Service) serviceClazz.newInstance();

            // attach
            Method attachMethod = serviceClazz.getDeclaredMethod("attach");
            attachMethod.invoke(targetService, this,
                    activityThread, intent.getComponent().getClassName(),
                    token, getApplication(), iActivityManager);
            targetService.onCreate();
        } catch (Exception e) {
   
   
            e.printStackTrace();
        }
        return START_STICKY;
    }
}

  • 首先准备attach需要的参数,通过反射获取
  • 调用targetService的attach方法
  • 调用targetService的onCreate方法

资源的插件化

参考这边换肤文章:安卓换肤实现原理

某东的插件化实践

1. 插件化做的事情:

插件化的目的就是在主工程使用插件工程的代码/类资源

1.1插件中四大组件的处理:

当使用插件的四大组件类时,比如插件activity的使用必须做特殊的处理,一般的处理方式有:

  • hook AMS,绕过AMS对四大组件的验证,手动管理生命周期
  • 直接在主工程的manifest注册插件的activity

方式1的特点是实现难度高,灵活性高,但是随着谷歌对于系统非sdk调用的限制,这个方式可能会在未来失效;
方式2的特点是实现简单,但是不够灵活,必须在manifest中写死

某东采用的方式2,直接在manifest中写死插件的四大组件注册

1.2插件中类的加载和使用:

每一个插件都设置一个ClassLoader,目的是整个插件的类都是由一个加载器加载,所有的插件的ClassLoader都在双亲委派中继承了另一个ClassLoader,这个目的是便于主工程的统一管理;

1.3DelegateClassLoader的替换:

替换LoadedAPK的ClassLoader为DelegateClassLoader即可;

1.4插件资源的引用

添加一个path给AssetManager就行,详见上文

2. 插件如何打包进主工程,即主工程如何集成插件包:

  • 将插件apk放入asset目录,通过assetManager去加载
  • 将插件apk文件修改后缀为.so 放入lib/armeabi目录,主工程apk在安装的时候会自动将这个目录的文件加载到data/data/< package_name >/lib/目录下,可以直接获取;

某东使用的第二种以so的形式放入lib目录自动加载,因为在运行时去使用AssetManager加载asset资源会影响程序的运行时速度

3. 插件和主工程如何通信

主要借鉴的airbnb的DeepLinkDispatch

DeepLinkDispatch类似Android原生的scheme协议,用于跳转到另一个APP的页面,比如在美团中打开高德地图,或者在微信中打开京东,

DeepLinkDispatch的实现思路:首先在插件工程中需要被打开的Activity添加注解,然后在主工程调用DeepLinkDispatch.startActivityDirect()传入注解中设置好的参数,最后会通过系统的apiContext.startActivity()开启页面

4. 插件化的未来

随着谷歌对于系统API的限制越来越严格,并且现在已经分成黑名单,深灰名单,浅灰名单留时间开发者调整,插件化应该是没有未来的,我们想想插件化到底是为了什么:

  • 独立编译,提高开发效率
  • 模块解耦,复用代码
东东的解决方案:

组件化:
传统的组件话是新建一个Android Library,在开发调试和实际引用的时候在Application和Library之间切换,东东的组件化是将每一个组件单独做成一个项目,然后在项目结构中保留Application和Android Library,library用于实现组件的功能,app用于开发调试,在主工程使用时直接通过依赖从云端的maven sync;

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!