主流插件框架分析

来源:互联网 发布:linux 安装ttf 编辑:程序博客网 时间:2024/06/14 04:14

前言


目前移动端产品功能越来越复杂,模块不断增加,APK体积也不断增长。由于Android Dalvik最初设计的问题,单个.dex文件方法数最多是65536个。因此,APK难免会遇到64K方法数限制的问题。
Google官方提供了MultiDex解决方案。但是该解决方案,有比较大的局限性。

  • 应用进程启动时MultiDex需要在主线程去做DEXOPT操作,中间涉及到文件读写、文件验证、数据复制、反射调用等,非常耗时,应用启动将变得很慢,甚至出现ANR。

  • 由于Dalvik LinearAlloc的BUG,Android 4.0(API level 14)之前的系统,可能导致应用启动失败。应用上线前,需要针对低版本系统做严格测试。另外Android 5.0(API level 21)之前的系统,Dalvik加载类的堆内存的大小有限制,可能导致使用了MultiDex的应用在分配内存时,会出现崩溃。

  • 引入MultiDex时,会存在多个dex文件,应用启动时,先加载了主dex文件,其它的dex文件在Application的attachBaseContext()方法中加载。Android构建工具帮我们分析处理了一部分依赖,将应用启动需要的类都放入了主dex文件中。但是项目中我们自己引入的第三方库或者底层的native代码,可能还有一些类的依赖构建工具无法处理,会导致应用启动时找不到相关类而崩溃。

综上所述,MultiDex方案并不完善,将直接应用启动变慢和崩溃的问题。为了解决这些问题,我们引入插件的概念。


技术关键点


插件框架的基本形式就是将一个apk中的不同功能模块进行拆分,打包到不同的dex或者apk文件中,而主工程是一个提供了加载模块dex和apk的框架。插件框架需要着重解决如下几个问题:

  • 代码的动态加载。Android系统提供了DexClassLoader,可以用来动态加载apk、dex和jar文件。加载顺序决定了起作用的类。这一点可做用于热修复功能。

  • 四大组件的动态注册和生命周期管理。通过插桩的方式做动态代理。可以在多个层级做Hack,不同的层级Hack时需要做的适配工作难度也不同。一般都需要反射替换Instrumentation,并对一些关键方法使用动态代理,以达到“欺上瞒下”的目的。


开源框架简介


目前,已经有多种插件框架的实现方案,不同的方案都存在各自的优缺点。以下简单介绍几个主要的插件框架的基本原理和优缺点。

DroidPlugin

DroidPlugin是360手机助手实现的一种插件框架,可以直接加载运行第三方的独立apk,不需要对apk进行修改或安装,插件 apk可以独立运行。其特性主要有以下几点:

  • 通过Stub插桩的方式,预先静态注册多个不同属性的Activity、Service、Receiver和Provider,动态代理。一个插件一个ClassLoader,插件之间代码完全隔离,互不影响。
  • 资源动态加载。一个插件一个AssetManager,插件之间资源完全隔离,互不影响。
  • Hack了几乎所有的通过Context获取到的系统服务(AM、PM等),Hack了和应用进程密切相关的Instrucmentation、ApplicationThread、ActivityThread等相关类。框架中很大一部分代码都是与Hack有关。
  • 不同的插件APK运行于不同的进程,互不干扰。插件之间支持aidl进程间通信。提供进程管理机制。

存在局限的地方:

  • 由于进程隔离的原因,宿主和插件分别使用了不同的ClassLoader和AssetManager,插件和宿主、插件和插件之间资源和代码不能共享。
  • 插件apk中所有的Intent Filter无法工作。要启动插件中的四大组件,必须要指定包名和类名。
  • RemoteView支持不是太好,不支持自定义资源的Notification。
  • 带有native库的插件支持不是太好,可能存在异常崩溃。
  • Hack了很多系统类,兼容性方面可能会隐患较多。

综合以上信息,DroidPlugin比较适合主程序只提供入口,而插件apk对宿主不存在依赖,插件之间无太多通信要求的场景。


Small

Small的核心是轻巧,支持跨平台(支持Android、iOS、Web)。其特性主要有以下几点:

  • 轻度Hack,代码相对很少,逻辑简单。插件可以是apk文件、so库、lib库。
  • 提供了Gradle插件辅助构建项目,修改各个插件资源id,避免资源id重复问题。
  • 通过Stub插桩的方式,预先静态注册多个Activity,动态代理。
  • 资源在编译时使用自定义gradle插件的方式区分开。多个插件共享进程,共享代码,共享资源。同一个ClassLoader加载所有插件。
  • 使用uri的方式启动插件、传递参数,比较灵活。
  • 基于Apache协议开源。
  • 插件配置可以存放在服务端,demo中提供了简单的版本管理、在线更新机制。

其主要问题也比较明显:

  • 目前不支持懒加载模式。所有插件都是在应用进程启动时一次性加载的,内存占用较大。如果要支持按需加载,需要自己做改进。
  • 除了Activity,其它三大组件不支持插件化。作者没有支持其它三大组件的意向。插件中所有的Intent Filter无法工作。
  • 插件更新后不是实时生效,而是在类被重新加载的时候生效。生效时间和类加载时机一致。
  • Small核心类代码凌乱,部分代码严重影响程序稳定性,还附带了部分业务逻辑,扩展性差。

综合以上信息,Small比较适合插件依赖于主程序,并且插件和主程序之间存在通信的场景,并且程序不宜规模太大。


DynamicAPK

DynamicAPK是携程的开源项目,但是目前作者已经不再维护。其主要特点如下:

  • 同一个ClassLoader加载所有的插件代码,通过反射调整加载顺序,进而实现了热修复功能。一次性加载所有插件,未实现按需加载。
  • 所有的插件资源都加载到了一个AssetManager中,并通过反射动态替换了Context获取的Resource。
  • Activity等组件需要在宿主程序中预先注册。
  • 直接改造aapt的源码,通过动态配置的方式确定插件资源id的PP字段,避免id重复。

ACDD

ACDD的原名是OpenAtlas,目前作者也已经不再维护。携程的DynamicAPK有相当一部分代码借鉴了此项目。其主要特点如下:

  • 自定义ClassLoader,扩展了双亲委派机制,用于加载插件代码。不同插件使用不同的ClassLoader,实现了代码的隔离,也实现了通用代码的共享。使用额外的配置文件管理插件和插件之间的依赖关系,实现了懒加载机制。
  • 所有的插件资源都加载到了一个AssetManager中,并通过反射动态替换了Context获取的Resource。实现了懒加载机制。
  • Activity等组件需要在宿主程序中预先注册。
  • 代码和相关文档中未提及资源id重复的问题是如何修正的。其内部的build tools则对所有插件和配置文件做了一个简单md5校验,未发现资源id相关的代码。应该是使用aapt打包时,指定了package id。

DynamicLoadApk

大概算是最早开源的插件化框架了,github上已经不怎么活跃了。其主要特点如下:

  • 支持插件对主程序无调用、接口调用、完全调用3种集成方式。
  • 支持Activity、FragmentActivity、Service组件,并且组件不需要在主程序的AndroidManifest中声明。
  • 将Activity的生命周期抽象出接口,在代理类里实现对Activity生命周期的管理,并在代理类里保存原Activity的引用(that应用)。
  • DexClassLoader加载插件代码,AssetManager管理插件资源。插件之间资源和代码可以做到相互隔离。
  • 自定义Intent和API实现对插件Activity的启动。

主要问题如下:

  • 强侵入性,组件内很多API需要使用that调用,并需要继承特定的类,启动组件需要调用私有API。
  • 插件的集成方式一旦确定将不能更改。不同的集成方式,编译配置也不相同。
  • 插件之间资源和代码无法共享。启动插件中的组件必须使用包名+类名的方式。

VirtualApp

VirtualApp是一个App虚拟化引擎(简称VA)。VA在App内部创建一个虚拟空间,App可以在虚拟空间内任意安装、卸载、启动apk。而这一切都与外部隔离,如同一个黑盒。目前已经有较多的应用使用了VA,并支持多种方式的应用加固。其主要特性如下:

  • 支持四大组件,插件中的静态注册的Receiver会转为动态注册。
  • 插件和插件之间高度隔离,运行于独立的进程。
  • 插件之间支持aidl通信,支持Intent隐式调用。
  • IO重定向,插件相关的文件存放于主程序私有文件目录下。
  • 通过动态代理的方式,比DroidPlugin Hack了更多的系统类和方法,模拟了一个更完善的小型系统。
  • 项目比较活跃,问题的解答和bug修复相对较快。

主要缺点如下:

  • Hack了很多系统类和方法,虽然比DroidPlugin稳定,但还是存在很多兼容性方面的问题。
  • 使用GPL3.0的授权,有开源感染的风险。
  • 插件隔离的原因,不支持代码和资源的共享。

Atlas

Atlas是阿里在17年3月份才开源的,目前还处于快速迭代开发期,文档和demo都还不完善,部分细节的功能还有不少的BUG。

Atlas主要的特性有以下几点:

  • 单进程运行,更像一个组件框架,包含host、local bundle和remote bundle。
  • 所有bundle的代码和资源都是动态加载,并且支持按需加载。
  • 自定义两个ClassLoader,一个实现类加载时的路由逻辑,另一个则主要负责加载bundle中的类和其依赖的bundle中的类。
  • 自定义ResourceManager,每个bundle里的资源都加入了同一个AssetManager。
  • 支持差分升级和远程bundle调用。
  • 提供了打包插件,编译期自动合并Manifest文件,自动分配bundle的资源package id,自动生成差分包,并提供单模块调试功能。

主要缺点如下:

  • 项目刚开源,文档很少,主要功能不稳定。
  • 差分升级和远程bundle调用,需要强有力的后台和测试支撑。
  • 差分升级还不支持动态添加4大组件的功能。
0 0