美文网首页Android
android插件化介绍

android插件化介绍

作者: Lee_5566 | 来源:发表于2020-11-17 10:53 被阅读0次
    image.png
    转载:https://blog.csdn.net/suyimin2010/article/details/80958742

    插件化介绍

    image.png

    插件化技术最初源于免安装运行apk的想法,这个免安装的apk可以理解为插件。

    支持插件化的app可以在运行时加载和运行插件,这样便可以将app中一些不常用的功能模块做成插件,一方面减小了安装包的大小,另一方面可以实现app功能的动态扩展。

    想要实现插件化,主要是解决下面三个问题:

    1. 插件中代码的加载和与主工程的互相调用
    2. 插件中资源的加载和与主工程的互相访问
    3. 四大组件生命周期的管理

    插件化技术

    技术的发展,根据实现原理可以将这几个框架划分成了三代。


    image.png
    第一代

    dynamic-load-apk最早使用ProxyActivity这种静态代理技术,由ProxyActivity去控制插件中PluginActivity的生命周期。

    该种方式缺点明显,插件中的activity必须继承PluginActivity,开发时要小心处理context。

    而DroidPlugin通过Hook系统服务的方式启动插件中的Activity,使得开发插件的过程和开发普通的app没有什么区别,但是由于hook过多系统服务,异常复杂且不够稳定。

    第二代

    为了同时达到插件开发的低侵入性(像开发普通app一样开发插件)和框架的稳定性,在实现原理上都是趋近于选择尽量少的hook,并通过在manifest中预埋一些组件实现对四大组件的插件化。

    另外各个框架根据其设计思想都做了不同程度的扩展,其中Small更是做成了一个跨平台,组件化的开发框架。

    第三代

    VirtualApp比较厉害,能够完全模拟app的运行环境,能够实现app的免安装运行和双开技术。

    Atlas是阿里今年开源出来的一个结合组件化和热修复技术的一个app基础框架,其广泛的应用与阿里系的各个app,其号称是一个容器化框架。

    插件化技术原理

    类加载

    Android中常用的有两种类加载器,DexClassLoader和PathClassLoader,它们都继承于BaseDexClassLoader。

    
    // DexClassLoader
    public class DexClassLoader extends BaseDexClassLoader {    
        public DexClassLoader(String dexPath, String optimizedDirectory,
                String libraryPath, ClassLoader parent) {        
            super(dexPath, new File(optimizedDirectory), libraryPath, parent);
        }
    }
    // PathClassLoader
    public class PathClassLoader extends BaseDexClassLoader {    
        public PathClassLoader(String dexPath, ClassLoader parent) {     
           super(dexPath, null, null, parent);
        } 
     
        public PathClassLoader(String dexPath, String libraryPath,
                ClassLoader parent) {    
           super(dexPath, null, libraryPath, parent);
        }
    
    }
    

    DexClassLoader多传了一个optimizedDirectory参数,这个目录必须是内部存储路径,用来缓存系统创建的Dex文件。

    而PathClassLoader该参数为null,只能加载内部存储目录的Dex文件。

    Android对于外部的dex文件,主要通过 DexClassLoader 类加载。

    //第一个参数为apk的文件目录
    //第二个参数为内部存储目录
    //第三个为库文件的存储目录
    //第四个参数为父加载器
    new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent)
    

    类加载器的构造函数代码例子:

    private DexClassLoader createDexClassLoader(String apkPath) {
        File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);
        DexClassLoader loader = new DexClassLoader(apkPath, dexOutputDir.getAbsolutePath(),
                null, mContext.getClassLoader());
        return loader;
    }
    
    插件调用主工程

    在构造插件的ClassLoader时会传入主工程的ClassLoader作为父加载器,所以插件是可以直接可以通过类名引用主工程的类。

    主工程调用插件

    若使用多ClassLoader机制,主工程引用插件中类需要先通过插件的ClassLoader加载该类再通过反射调用其方法。

    插件化框架一般会通过统一的入口去管理对各个插件中类的访问,并且做一定的限制。

    若使用单ClassLoader机制,主工程则可以直接通过类名去访问插件中的类。

    注意:该方式有个弊病,若两个不同的插件工程引用了一个库的不同版本,则程序可能会出错,所以要通过一些规范去避免该情况发生。

    资源加载

    Android系统通过Resource对象加载资源。

    因此,只要将插件apk的路径加入到AssetManager中,便能够实现对插件资源的访问。

    
    //创建AssetManager对象 
    AssetManager assets = new AssetManager();
     //将apk路径添加到AssetManager中
      if (assets.addAssetPath(resDir) == 0){              
        return null;  
    }
     //创建Resource对象
     
    r = new Resources(assets, metrics, getConfiguration(), compInfo);
    

    由于AssetManager并不是一个public的类,需要通过反射去创建.

    资源路径处理

    和代码加载相似,插件和主工程的资源关系也有两种处理方式:

    • 合并式:addAssetPath时加入所有插件和主工程的路径;
    • 独立式:各个插件只添加自己apk路径

    合并式

    由于AssetManager中加入了所有插件和主工程的路径,因此生成的Resource可以同时访问插件和主工程的资源。

    但是由于主工程和各个插件都是独立编译的,生成的资源id会存在相同的情况,在访问时会产生资源冲突。

    独立式
    各个插件的资源是互相隔离的,不过如果想要实现资源的共享,必须拿到对应的Resource对象。

    Context处理

    通常我们通过Context对象访问资源,光创建出Resource对象还不够,因此还需要一些额外的工作。

    对资源访问的不同实现方式也需要不同的额外工作。

    以VirtualAPK的处理方式为例。

    创建Resource
    if (Constants.COMBINE_RESOURCES) {
        //插件和主工程资源合并时需要hook住主工程的资源
        Resources resources = ResourcesManager.createResources(context, apk.getAbsolutePath());
        ResourcesManager.hookResources(context, resources);  
          return resources;
    } else {  
          //插件资源独立,该resource只能访问插件自己的资源
        Resources hostResources = context.getResources();
        AssetManager assetManager = createAssetManager(context, apk);  
            return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
    }
    
    关联resource和Activity
    Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
    activity.setIntent(intent);
    //设置Activity的mResources属性,Activity中访问资源时都通过mResources
     
    ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources())
    

    四大组件支持

    Android开发中有一些特殊的类,是由系统创建的,并且由系统管理生命周期。

    如常用的四大组件,Activity,Service,BroadcastReceiver和ContentProvider。

    仅仅构造出这些类的实例是没用的,还需要管理组件的生命周期。

    其中以Activity最为复杂,不同框架采用的方法也不尽相同。下面以Activity为例详细介绍插件化如何支持组件生命周期的管理。

    大致分为两种方式:

    1. ProxyActivity代理
    2. 预埋StubActivity,hook系统启动Activity的过程
    ProxyActivity代理

    ProxyActivity代理的方式最早是由dynamic-load-apk提出的,其思想很简单,在主工程中放一个ProxyActivy,启动插件中的Activity时会先启动ProxyActivity,在ProxyActivity中创建插件Activity,并同步生命周期。

    下图展示了启动插件Activity的过程:


    image.png

    具体的过程如下:

    首先需要通过统一的入口(如图中的PluginManager)启动插件Activity,其内部会将启动的插件Activity信息保存下来,并将intent替换为启动ProxyActivity的intent。

    ProxyActivity根据插件的信息拿到该插件的ClassLoader和Resource,通过反射创建PluginActivity并调用其onCreate方法。

    PluginActivty调用的setContentView被重写了,会去调用ProxyActivty的setContentView。由于ProxyActivity重写了getResource返回的是插件的Resource,所以setContentView能够访问到插件中的资源。同样findViewById也是调用ProxyActivity的。

    hook方式
    image.png
    1. Activity1调用startActivity,实际会调用Instrumentation类的execStartActivity方法,Instrumentation是系统用来监控Activity运行的一个类,Activity的整个生命周期都有它的影子。
    2. 通过跨进程的binder调用,进入到ActivityManagerService中,其内部会处理Activity栈。之后又通过跨进程调用进入到Activity2所在的进程中。
    3. ApplicationThread是一个binder对象,其运行在binder线程池中,内部包含一个H类,该类继承于类Handler。ApplicationThread将启动Activity2的信息通过H对象发送给主线程。
    4. 主线程拿到Activity2的信息后,调用Instrumentation类的newActivity方法,其内通过ClassLoader创建Activity2实例。

    相关文章

      网友评论

        本文标题:android插件化介绍

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