有关Android插件化思考

最近几年移动开发业界兴起了「 插件化技术 」的旋风,各个大厂都推出了本身的插件化框架,各类开源框架都评价自身功能优越性,使人应接不暇。随着公司业务快速发展,项目增多,开发资源却有限,如何能在有限资源内知足需求和项目的增加,同时又能快速响应问题和迭代新需求,这就是一个矛盾点。此时,插件化技术正好风生水起,去了解各个主流框架实现思路,看看能对目前工做是否有帮助,是颇有必要的。html

主要分为如下几个部分vue

  • 插件化介绍
  • 入门知识
  • 实现原理
  • 主流框架
  • 实战
  • 小结

插件化介绍

百度百科里是这么定义插件的:「 是一种遵循必定规范的应用程序接口编写出来的程序,只能运行在程序规定的系统平台下,而不能脱离指定的平台单独运行。」,也就是说,插件能够提供一种动态扩展能力,使得应用程序在运行时加载本来不属于该应用的功能,而且作到动态更新和替换。node

那么在 Android 中,何为「 插件化 」,顾名思义,就是把一些核心复杂依赖度高的业务模块封装成独立的插件,而后根据不一样业务需求进行不一样组合,动态进行替换,可对插件进行管理、更新,后期对插件也可进行版本管理等操做。在插件化中有两个概念须要讲解下:android

  • 宿主

所谓宿主,就是须要能提供运行环境,给资源调用提供上下文环境,通常也就是咱们主 APK ,要运行的应用,它做为应用的主工程所在,实现了一套插件的加载和管理的框架,插件都是依托于宿主的APK而存在的。git

  • 插件

插件能够想象成每一个独立的功能模块封装为一个小的 APK ,能够经过在线配置和更新实现插件 APK 在宿主 APK 中的上线和下线,以及动态更新等功能。github

那么为什么要使用插件化技术,它有何优点,能给咱们带来什么样好处,这里简单列举了如下几点:面试

  • 让用户不用从新安装 APK 就能升级应用功能,减小发版本频率,增长用户体验。
  • 提供一种快速修复线上 BUG 和更新的能力。
  • 按需加载不一样的模块,实现灵活的功能配置,减小服务器对旧版本接口兼容压力。
  • 模块化、解耦合、并行开发、 65535 问题。

入门知识

首先咱们要知道插件化技术是属于比较复杂一个领域,复杂点在于它涉及知识点普遍,不只仅是上层作应用架构能力,还要求咱们对 Android 系统底层知识须要有必定的认知,这里简单罗列了其中会涉及的知识点:mongodb

image

首先,要介绍的是 Binder ,咱们都知道 Android 多进程通讯核心就是 Binder ,若是没有它真的步履维艰。 Binder 涉及两层技术,你能够认为它是一个中介者模式,在客户端和服务器端之间, Binder 就起到中介的做用。若是要实现四大组件的插件化,就须要在 Binder 上作修改, Binder 服务端的内容没办法修改,只能改客户端的代码,并且四大组件的每一个组件的客户端都不同,这个就须要深刻研究了。学习Binder的最好方式是 AIDL ,这方面在网上有不少资料,最简单的方式就是本身写个 aidl 文件自动生成一个 Java 类,而后去查看这个Java类的每一个方法和变量,而后再去看四大组件,其实都是跟 AIDL 差很少的实现方式。编程

其次,是 App 打包的流程。代码写完了,执行一次打包操做,中途经历了资源打包、 Dex 生成、签名等过程。其中最重要的就是资源的打包,即 AAPT 这一步,若是宿主和插件的资源id冲突,一种解决办法就是在这里作修改。安全

第三, App 在手机上的安装流程也很重要。熟悉安装流程不只对插件化有帮助,在遇到安装 Bug 的时候也很是重要。手机安装 App 的时候,常常会有下载异常,提示资源包不能解析,这时须要知道安装 App 的这段代码在什么地方,这只是第一步。第二步须要知道, App 下载到本地后,具体要作哪些事情。手机有些目录不能访问, App 下载到本地以后,放到哪一个目录下,而后会生成哪些文件。插件化有个增量更新的概念,如何下载一个增量包,从本地具体哪一个位置取出一个包,这个包的具体命名规则是什么,等等。这些细节都必需要清楚明白。

第四,是 App 的启动流程。 Activity 启动有几种方式?一种是写一个 startActivity ,第二种是点击手机 App ,经过手机系统里的 Launcher 机制,启动 App 里默认的 Activity 。一般, App 开发人员喜闻乐见的方式是第二种。那么第一种方式的启动原理是什么呢?另外,启动的时候,Main 函数在哪里?这个 Main 函数的位置很重要,咱们能够对它所在的类作修改,从而实现插件化。

第五点更重要,作 Android 插件化须要控制两个地方。首先是插件 Dex 的加载,如何把插件 Dex 中的类加载到内存?另外是资源加载的问题。插件多是 Apk 也多是 so 格式,无论哪种,都不会生成 R.id ,从而没办法使用。这个问题有好几种解决方案。一种是是重写 Context 的 getAsset 、 getResource 之类的方法,偷换概念,让插件读取插件里的资源,但缺点就是宿主和插件的资源 id 会冲突,须要重写 AAPT 。另外一种是重写 AMS中保存的插件列表,从而让宿主和插件分别去加载各自的资源而不会冲突。第三种方法,就是打包后,执行一个脚本,修改生成包中资源id。

第六点,在实施插件化后,如何解决不一样插件的开发人员的工做区问题。好比,插件1和插件2,须要分别下载哪些代码,如何独立运行?就像机票和火车票,如何只运行本身的插件,而不运行别人的插件?这是协同工做的问题。火车票和机票,这两个 Android 团队的各自工做区是不同的,这时候就要用到 Gradle 脚本了,每一个项目分别有各自的仓库,有各自不一样的打包脚本,只须要把本身的插件跟宿主项目一块儿打包运行起来,而不用引入其余插件,还有更厉害的是,也能够把本身的插件看成一个 App 来打包并运行。

上面介绍了插件化的入门知识,一共六点,每一点都须要花大量时间去理解。不然,在面对插件化项目的时候,不少地方你会一头雾水。而只要理解了这六点核心,一切可迎刃而解。

实现原理

在Android中应用插件化技术,其实也就是动态加载的过程,分为如下几步:

  • 把可执行文件( .so/dex/jar/apk 等)拷贝到应用 APP 内部。
  • 加载可执行文件,更换静态资源
  • 调用具体的方法执行业务逻辑

Android 项目中,动态加载技术按照加载的可执行文件的不一样大体能够分为两种:

  • 动态加载 .so 库
  • 动态加载 dex/jar/apk文件(如今动态加载广泛说的是这种)

第一点, Android 中 NDK 中其实就使用了动态加载,动态加载 .so 库并经过 JNI 调用其封装好的方法。后者通常是由 C/C++ 编译而成,运行在 Native 层,效率会比执行在虚拟机层的 Java 代码高不少,因此 Android 中常常经过动态加载 .so 库来完成一些对性能比较有需求的工做(好比 Bitmap 的解码、图片高斯模糊处理等)。此外,因为 .so 库是由 C/C++ 编译而来的,只能被反编译成汇编代码,相比中 dex 文件反编译获得的 Smali 代码更难被破解,所以 .so 库也能够被用于安全领域。

其二,“基于 ClassLoader 的动态加载 dex/jar/apk 文件”,就是咱们指在 Android 中 动态加载由 Java 代码编译而来的 dex 包并执行其中的代码逻辑,这是常规 Android 开发比较少用到的一种技术,目前说的动态加载指的就是这种。

Android 项目中,全部 Java 代码都会被编译成 dex 文件,Android 应用运行时,就是经过执行 dex 文件里的业务代码逻辑来工做的。使用动态加载技术能够在 Android 应用运行时加载外部的 dex 文件,而经过网络下载新的 dex 文件并替换原有的 dex 文件就能够达到不安装新 APK 文件就升级应用(改变代码逻辑)的目的。

因此说,在 Android 中的 ClassLoader 机制主要用来加载 dex 文件,系统提供了两个 API 可供选择:

  • PathClassLoader:只能加载已经安装到 Android 系统中的 APK 文件。所以不符合插件化的需求,不做考虑。
  • DexClassLoader:支持加载外部的 APK、Jar 或者 dex 文件,正好符合文件化的需求,全部的插件化方案都是使用 DexClassloader 来加载插件 APK 中的 .class文件的。

主流框架

在 Android 中实现插件化框架,须要解决的问题主要以下:

  • 资源和代码的加载
  • Android 生命周期的管理和组件的注册
  • 宿主 APK 和插件 APK 资源引用的冲突解决

下面分析几个目前主流的开源框架,看看每一个框架具体实现思路和优缺点。

DL 动态加载框架 ( 2014 年末)

是基于代理的方式实现插件框架,对 App 的表层作了处理,经过在 Manifest 中注册代理组件,当启动插件组件时,首先启动一个代理组件,而后经过这个代理组件来构建,启动插件组件。 须要按照必定的规则来开发插件 APK,插件中的组件须要实现通过改造后的 Activity、FragmentActivity、Service 等的子类。

优势以下:

  • 插件须要遵循必定的规则,所以安全方面可控制。
  • 方案简单,适用于自身少许代码的插件化改造。

缺点以下:

  • 不支持经过 This 调用组件的方法,须要经过 that 去调用。
  • 因为 APK 中的 Activity 没有注册,不支持隐式调用 APK 内部的 Activity。
  • 插件编写和改造过程当中,须要考虑兼容性问题比较多,联调起来会比较费时费力。

DroidPlugin ( 2015 年 8 月)

DroidPlugin 是 360 手机助手实现的一种插件化框架,它能够直接运行第三方的独立 APK 文件,彻底不须要对 APK 进行修改或安装。一种新的插件机制,一种免安装的运行机制,是一个沙箱(可是不彻底的沙箱。就是对于使用者来讲,并不知道他会把 apk 怎么样), 是模块化的基础。

实现原理:

  • 共享进程:为android提供一个进程运行多个 apk 的机制,经过 API 欺骗机制瞒过系统。
  • 占坑:经过预先占坑的方式实现不用在 manifest 注册,经过一带多的方式实现服务管理。
  • Hook 机制:动态代理实现函数 hook ,Binder 代理绕过部分系统服务限制,IO 重定向(先获取原始 Object –> Read ,而后动态代理 Hook Object 后–> Write 回去,达到瞒天过海的目的)。

插件 Host 的程序架构:

image

优势以下:

  • 支持 Android 四大组件,并且插件中的组件不须要在宿主 APK 中注册。
  • 支持 Android 2.3 及以上系统,支持全部的系统 API。
  • 插件与插件之间,插件与宿主之间的代码和资源彻底隔阂。
  • 实现了进程管理,插件的空进程会被及时回收,占用内存低。

缺点以下:

  • 插件 APK 中不支持自定义资源的 Notification,通知栏限制。
  • 插件 APK 中没法注册具备特殊的 IntentFilter 的四大组件。
  • 缺少对 Native 层的 Hook 操做,对于某些带有 Native 代码的插件 APK 支持不友好,可能没法正常运行。
  • 因为插件与插件,插件与宿主之间的代码彻底隔离,所以,插件与插件,插件与宿主之间的通讯只能经过 Android 系统级别的通讯方式。
  • 安全性担心(能够修改,hook一些重要信息)。
  • 机型适配(不是全部机器上都能行,由于大量用反射相关,若是rom厂商深度定制了framework层,反射的方法或者类不在,容易插件运用失败)

Small ( 2015 年末)

Small 是一种实现轻巧的跨平台插件化框架,基于“轻量、透明、极小化、跨平台”的理念,实现原理有如下三点。

  • 动态加载类:咱们知道插件化不少都从 DexClassLoader 类有个 DexPathList 清单,支持 dex/jar/zip/apk 文件格式,却没有支持 .so 文件格式,所以 Small 框架则是把 .so 文件包装成 zip 文件格式,插入到 DexPathList 集合中,改写动态加载的代码。
  • 资源分段:因为 Android 资源的格式是 0xPPTTNNNN ,PP 是包 ID ,00-02 是属于系统,7f 属于应用程序,03-7e 则保留,能够在这个范围内作文章 , TT 则是 Type 好比,attr 、layout 、string 等等,NNNN 则是资源全局 ID。那么这个框架则是对资源包进行从新打包,每一个插件从新分配资源 ID ,这样就保证了宿主和插件的资源不冲突。
  • 动态代理注册:在 Android 中要使用四大组件,都是须要在 manifest 清单中注册,这样才可使用,那如何在不注册状况也能使用呢,这里就是用到动态代理机制进行 Hook ,在发送 AMS 以前用占坑的组件来欺骗系统,经过认证后,再把真正要调用的组件还原回来,达到瞒天过海目的。

架构图:

Picture

优势以下:

  • 全部插件支持内置宿主包中。
  • 插件的编码和资源文件的使用与普通开发应用没有差异。
  • 经过设定 URI ,宿主以及 Native 应用插件,Web 插件,在线网页等可以方便进行通讯。
  • 支持 Android 、 iOS 、和 Html5 ,三者能够经过同一套 Javascript 接口实现通讯。

缺点以下:

  • 暂不支持 Service 的动态注册,不过这个能够经过将 Service 预先注册在宿主的 AndroidManifest.xml 文件中进行规避,由于 Service 的更新频率一般很是低。

与其余主流框架的区别:

DyLA  : Dynamic-load-apk          @singwhatiwanna  
DiLA  : Direct-Load-apk           @FinalLody  
APF   : Android-Plugin-Framework  @limpoxe  
ACDD  : ACDD                      @bunnyblue  
DyAPK : DynamicAPK                @TediWang  
DPG   : DroidPlugin               @cmzy, 360
  • 功能
DyLA DiLA ACDD DyAPK DPG APF Small
加载非独立插件 × x ×
加载.so后缀插件 × × ! × × ×
Activity生命周期
Service动态注册 × × × x
资源分包共享 × × ! ! × !
公共插件打包共享 × × × × × ×
支持AppCompat × × × × × ×
支持本地网页组件 × × × × × ×
支持联调插件 × x × × × ×
  • 透明度
ACDD DyAPK APF Small
插件Activity代码无需修改
插件引用外部资源无需修改name × × ×
插件模块无需修改build.gradle × x ×

VirtualAPK (2017年 6 月 )

VirtualAPK 是滴滴开源的一套插件化框架,支持几乎全部的 Android 特性,四大组件方面。

架构图:

image

实现思路:

VirtualAPK 对插件没有额外的约束,原生的 apk 便可做为插件。插件工程编译生成 apk后,便可经过宿主 App 加载,每一个插件 apk 被加载后,都会在宿主中建立一个单独的 LoadedPlugin 对象。以下图所示,经过这些 LoadedPlugin 对象,VirtualAPK 就能够管理插件并赋予插件新的意义,使其能够像手机中安装过的 App 同样运行。

  • 合并宿主和插件的ClassLoader 须要注意的是,插件中的类不能够和宿主重复
  • 合并插件和宿主的资源 重设插件资源的 packageId,将插件资源和宿主资源合并
  • 去除插件包对宿主的引用 构建时经过 Gradle 插件去除插件对宿主的代码以及资源的引用

image

特性以下:

四大组件均不须要在宿主manifest中预注册,每一个组件都有完整的生命周期。

  • Activity:支持显示和隐式调用,支持Activity的themeLaunchMode,支持透明主题;
  • Service:支持显示和隐式调用,支持Service的startstopbindunbind,并支持跨进程bind插件中的Service;
  • Receiver:支持静态注册和动态注册的Receiver;
  • ContentProvider:支持provider的全部操做,包括CRUDcall方法等,支持跨进程访问插件中的Provider。
  • 自定义View:支持自定义 View,支持自定义属性和style,支持动画;
  • PendingIntent:支持PendingIntent以及和其相关的AlarmNotificationAppWidget
  • 支持插件Application以及插件manifest中的meta-data
  • 支持插件中的so

优秀的兼容性

  • 兼容市面上几乎全部的Android手机,这一点已经在滴滴出行客户端中获得验证。
  • 资源方面适配小米、Vivo、Nubia 等,对未知机型采用自适应适配方案。
  • 极少的 Binder Hook,目前仅仅 hook了两个Binder:AMSIContentProvider,hook 过程作了充分的兼容性适配。
  • 插件运行逻辑和宿主隔离,确保框架的任何问题都不会影响宿主的正常运行。

入侵性极低

  • 插件开发等同于原生开发,四大组件无需继承特定的基类;
  • 精简的插件包,插件能够依赖宿主中的代码和资源,也能够不依赖;
  • 插件的构建过程简单,经过 Gradle 插件来完成插件的构建,整个过程对开发者透明。

以下是 VirtualAPK 和主流的插件化框架之间的对比。

特性 DynamicLoadApk DynamicAPK Small DroidPlugin VirtualAPK
支持四大组件 只支持Activity 只支持Activity 只支持Activity 全支持 全支持
组件无需在宿主manifest中预注册 ×
插件能够依赖宿主 ×
支持 PendingIntent × × ×
Android 特性支持 大部分 大部分 大部分 几乎所有 几乎所有
兼容性适配 通常 通常 中等
插件构建 部署aapt Gradle插件 Gradle插件

RePlugin (2017 年 7 月)

RePlugin是一套完整的、稳定的、适合全面使用的,占坑类插件化方案,由360手机卫士的RePlugin Team研发,也是业内首个提出”全面插件化“(全面特性、全面兼容、全面使用)的方案。

框架图:

image

主要优点有:
极其灵活:主程序无需升级(无需在Manifest中预埋组件),便可支持新增的四大组件,甚至全新的插件
很是稳定:Hook 点仅有一处(ClassLoader),无任何 Binder Hook!如此可作到其崩溃率仅为“万分之一”,并完美兼容市面上近乎全部的 Android ROM。
特性丰富:支持近乎全部在“单品”开发时的特性。包括静态 Receiver、 Task-Affinity 坑位、自定义 Theme、进程坑位、AppCompat、DataBinding等。
易于集成:不管插件仍是主程序,只需“数行”就能完成接入。
管理成熟:拥有成熟稳定的“插件管理方案”,支持插件安装、升级、卸载、版本管理,甚至包括进程通信、协议版本、安全校验等。
数亿支撑:有 360 手机卫士庞大的数亿用户作支撑,三年多的残酷验证,确保App用到的方案是最稳定、最适合使用的。

实战

主要是测试各个框架之间上手的容易度如何,并作不一样对比,这边写了两个 Demo 例子,一个是基于 Small 框架,一个基于 VirtualAPK 框架,从中能看出不一样。

Small 实践

要引用官方最新的版本,否则在宿主和插件合并build.gradle 的时候会出现一个 BUG,这是个坑位,注意行走。其次在模块命名上要遵循必定的规则,好比业务模块用 app. ,公共库模块用 lib. ,至关于包名 .app.,.lib. 。每次在插件中添加一个 activity 组件,都须要在宿主中配置路由,而后在从新编译插件一遍,否则直接运行的话,在宿主中是找到新添加的 activity 组件,会报该组件没在系统 manifest 中,因此每次新增或修改建议插件都从新编译一遍。官方里说了,对于 Service 支持不太友好,就没去实践了。

VirtualAPK 实践
有个坑须要注意的是构建环境,官方说明是要如下版本环境,Gradle 2.14.1 和 com.android.tools.build 2.1.3, 以前编译的是用最新的Gradle版本,致使一直有问题,至因而否有其余问题,能够看官方文档。

具体代码

Small Demo :
https://github.com/cr330326/MySmall

VirtualAPK Demo :
https://github.com/cr330326/MyVirtualAPKDemo

小结

正如开头所说,要实现插件化的框架,无非就是解决那典型的三个问题:插件代码如何加载、插件中的组件生命周期如何管理、插件资源和宿主资源冲突怎么办。每一个框架针对这三个问题,都有不一样的解决方案,同时呢,根据时间顺序,后出来的框架每每都会吸取已经出的框架精髓,进而修复那些比较有里程碑意义框架的不足。但这些框架的核心思想都是用到了代理模式,有的在表面层进行代理,有的则在系统应用层进行代理,经过代理达到替换和瞒天过海,最终让 Android 系统误觉得调用插件功能和调用原生开发的功能是同样的,进而达到插件化和原生兼容编程的目的

声明原做者:斜杠Allen
原文地址:http://www.apkbus.com/blog-94...

阅读更多

20+个很棒的Android开源项目

我是如何进入Facebook的?

2018年Android面试题含答案—适合中高级(下)

看完你就该会git了(手把手教你用vue+node+mongodb搭建一个小商城

欢迎关注个人微信公众号:终端研发部,有问题随时来撩我!

相关文章
相关标签/搜索