文章目录
文章目录
- (4.6.29.1)插件化:Android中的动态加载技术
- (4.6.29.2)插件化之代码加载:启动Activity
- (4.6.29.3)插件化之代码加载:启动Activity等四大组件之hook方式
- (4.6.29.4)插件化之资源加载:使用插件中的R资源
一、概述
所谓插件化,就是让我们的应用不必再像原来一样把所有的内容都放在一个apk中,可以把一些功能和逻辑单独抽出来放在插件apk中,然后主apk做到[按需调用],这样的好处是一来可以减少主apk的体积,让应用更轻便,二来可以做到热插拔,更加动态化
采集时间:2018年6月14日11:52:21
header 1 | github-start | lastUpadte | 四大组件 | 组件无需在宿主manifest中预注册 | 插件可以依赖宿主 | PendingIntent | Android特性 | 兼容性 | 插件构建 | 使用者 |
---|---|---|---|---|---|---|---|---|---|---|
Qihoo360/RePlugin | 4661 | 1月前 | — | ----- | — | — | ----- | — | — | 360手机卫士、360 手机助手、360 手机浏览器 |
Qihoo360/DroidPlugin | 1724 | 2年前 | √ | √ | × | √ | all | 高 | 无 | 360手机助手 |
wequick/Small | 4207 | 2月前 | 仅Activity | √ | √ | × | 大部分 | 中 | Gradle插件 | 酷狗音乐、小伙伴TV |
didi/VirtualAPK | 5869 | 23天前 | √ | √ | √ | √ | all | 高 | Gradle插件 | 滴滴、Uber |
CtripMobile/DynamicAPK | 2763 | 3年前 | 仅Activity | × | √ | × | most | 低 | aapt | 携程旅行 |
singwhatiwanna/dynamic-load-apk | 5358 | 1年前 | 仅Activity | √ | √ | × | most | 低 | 无 | — |
mmin18/AndroidDynamicLoader | 1339 | 5 年前 | — | ----- | — | — | ----- | — | — | 大众点评 |
仿Instant ,未开源 | — | — | — | ----- | — | — | ----- | — | — | 美团 |
alibaba/atlas | 6132 | 27天前 | — | ----- | — | — | ----- | — | — | 手机淘宝 |
houkx/android-pluginmgr | 1396 | 2年前 | — | ----- | — | — | ----- | — | — | — |
woyaowenzi/ACDD | 11 | 3年前 | — | |||||||
limpoxe/Android-Plugin-Framework | 1260 | 6月前 | — | — | ----- | — | — | — | — | — |
为什么需要插件化?
- 应用体积越来越大,需要拆分APK完成模块化与热部署
- 应用频繁更新,导致用户粘性降低
- 新功能的加入
- 不确定与用户需求的匹配性,需要动态增加或者更新,增加灵活性
- 一旦不适用或发生严重问题,无法做平滑处理(动态切换),只能紧急发布补丁版本升级
- 主应用用户量较大,同系应用需要导流,传统特性只能引导用户下载安装
插件化的特定就是 “ 应用在运行的时候通过加载一些本地不存在的可执行文件实现一些特定的功能”,这些可执行文件是可以替换的( 更换静态资源,比如换启动图、换主题、或者用服务器参数开关控制广告的隐藏现实等,不属于动态加载)。
Android中动态加载的核心思想是动态调用外部的 dex文件,极端的情况下,Android APK自身带有的Dex文件只是一个程序的入口(或者说空壳),所有的功能都通过从服务器下载最新的Dex文件完成
实现插件化后,能够:
- 减小主APK大小
- 降低新功能版本发布的频繁性
- 独立开发 A/B Test 模块
- bug修复工具
1.1 发展历史
很早之前已经有公司在研究插件化这项技术,淘宝做得比较早,但淘宝的这项技术一直是保密的。直到2015年才陆续出现很多框架,Android插件化分成很多技术流派,实现的方式都不太一样
-
2012.大众点评.屠毅敏.mmin18/AndroidDynamicLoader 1339框架
扫描二维码关注公众号,回复: 3751038 查看本文章- 用Fragment来实现。通过动态加载不同的Fragement,把想换的页面都换掉。
- 在这个项目中第一次看到了如何通过addAssetPath来读取插件中的资源
-
2013.23Code。
- 23Code提供了一个壳,在这个壳里可以动态下载插件,然后动态运行。可以在壳外编写各种各样的控件,放在这个框架下去运行。
-
2014年初.阿里.Altas技术
- 网上出现一个视频,仅进行了大方向讲解,并未开源
-
2014年底.滴滴.任玉刚.singwhatiwanna/dynamic-load-apk 5358【里程碑点】
- 这跟后续介绍的很多插件化项目都不太一样。它没有Hook太多的系统底层方法,而是从上层,即App应用层解决问题,创建一个继承自Activity的ProxyActivity类,然后让插件中的所有Activity都继承自ProxyActivity,并重写Activity所有的方法。
- 之所以说这个项目是里程碑式的,是因为在2015年之前业界没有太多资料可以参考
-
201504.OpenAltas(woyaowenzi/ACDD 11)。
- 这个框架参考了淘宝App的很多经验,主要就是Hook的思想,同时,还首次提出来通过扩展AAPT来解决插件与宿主的资源id冲突的问题。
-
201508.张勇.Qihoo360/DroidPlugin 1724【里程碑点】
- 这个项目太牛了,能把任意的App都加载到宿主里。可以基于这个框架写一个宿主App,然后就可以把别人写的App都当作插件来加载。这个框架的功能的确很强大,但强大的代价就是要改写很多Android系统的底层代码
- 是 360 手机助手实现的一种插件化框架,它可以直接运行第三方的独立 APK 文件,完全不需要对 APK 进行修改或安装。一种新的插件机制,一种免安装的运行机制,是一个沙箱(但是不完全的沙箱。就是对于使用者来说,并不知道他会把 apk 怎么样), 是模块化的基础
- 说明文档较少,导致技术人员掌握这个框架不太容易
-
201509.Andfix,Nuwa(女娲)…
- 热修复技术陆续登场
-
2015年底.林广亮.wequick/Small 4207框架
- 这个机制不太一样的地方就是,通过脚本的方式来解决资源冲突的问题
-
201703.ali.alibaba/atlas 6132
- 正式开源
-
201706.滴滴.didi/VirtualAPK 5869插件化框架
- 支持几乎所有的 Android 特性,四大组件方面
-
201707.360手机卫士.Qihoo360/RePlugin 4661
- 是一套完整的、稳定的、适合全面使用的,占坑类插件化方案,也是业内首个提出”全面插件化“(全面特性、全面兼容、全面使用)的方案
-
201710.美团.仿InstantRun框架
- 发表技术美团App 插件化实践,但整体方案未开源
1.2 需要知识
想完全明白插件化技术,首先需要了解Android系统的底层实现
1.2.1 Binder
你可以认为它是一个中介者模式,在客户端和服务器端之间,Binder就起到中介的作用,这是我这段时间对Binder的思考。
要实现四大组件的插件化,就需要在Binder上做修改。
1.2.2 App打包流程
代码写完了,执行一次打包操作,中途经历了资源打包、dex生成、签名等过程。其中最重要的就是资源的打包,即AAPT这一步,如果宿主和插件的资源id冲突,一种解决办法就是在这里做修改
1.2.3 App安装流程
熟悉安装流程不仅对插件化有帮助,在遇到安装bug的时候也非常重要。手机安装App的时候,经常会有下载异常,提示资源包不能解析,这时需要知道安装App的这段代码在什么地方,这只是第一步。第二步需要知道,App下载到本地后,具体要做哪些事情。手机有些目录不能访问,App下载到本地之后,放到哪个目录下,然后会生成哪些文件。插件化有个增量更新的概念,如何下载一个增量包,从本地具体哪个位置取出一个包,这个包的具体命名规则是什么,等等。这些细节都必须要清楚明白
1.2.4 App启动流程
Activity启动有几种方式?一种是写一个startActivity,第二种是点击手机App,通过手机系统里的Launcher机制,启动App里默认的Activity。通常,App开发人员喜闻乐见的方式是第二种。那么第一种方式的启动原理是什么呢?另外,启动的时候,main函数在哪里?这个main函数的位置很重要,我们可以对它所在的类做修改,从而实现插件化。
1.2.5 资源加载机制
使用ClassLoader动态加载一个外部的类是非常容易的事情,所以很容易就能实现动态加载新的可执行代码的功能,但是比起一般的Java程序,在Android程序中使用动态加载主要有两个麻烦的问题:
-
[代码加载] 首先是插件Dex的加载,如何把插件Dex中的类加载到内存?
- [加载]类的加载可以使用Java的ClassLoader机制
- [代理]但是对于Android来说,并不是说类加载进来就可以用了,很多组件都是有“生命”的;因此对于这些有血有肉的类,必须给它们注入活力,也就是所谓的组件生命周期管理
-
[资源加载] 插件可能是apk也可能是so格式, 不管哪一种, 都不会生成 R.id.XX,从而没办法使用。
- 一种解决方式是插件里需要用到的新资源都通过纯Java代码的方式创建(包括XML布局、动画、点九图等),麻烦但是有效,不多过描述;
- 一种是是重写Context的getAsset、getResource之类的方法,偷换概念,让插件读取插件里的资源,但缺点就是宿主和插件的资源id会冲突,需要重写AAPT。
- 另一种是重写AMS中保存的插件列表,从而让宿主和插件分别去加载各自的资源而不会冲突。
- 第三种方法,就是打包后,执行一个脚本,修改生成包中资源id。
说到底,抛开虚拟机的差别不说,一个Android程序和标准的Java程序最大的区别就在于他们的上下文环境(Context)不同。Android中,这个环境可以给程序提供组件需要用到的功能,也可以提供一些主题、Res等资源,其实上面说到的两个问题都可以统一说是这个环境的问题,而现在的各种Android动态加载框架中,核心要解决的东西也正是“如何给外部的新类提供上下文环境”的问题
整体来说,需要以下知识点:
- ClassLoader类加载器
要想实现加载外部dex文件(即插件)来实现热部署,那么必然要把其中的class文件加载到内存中。其中涉及到两种ClassLoader:DexClassLoader和PathClassLoader。而DexClassLoader可以加载外部的jar,dex等文件,正是我们需要的。
关于ClassLoader详解,见ClassLoader完全解析。
- Java反射
因为插件apk与宿主apk不在一个apk内,那么一些类的访问必然要通过反射进行获取。所以了解反射对插件化的学习是必须的。
关于Java反射,见Java反射详解
- 代理模式
插件化实现的过程主要靠欺上瞒下,坑蒙拐骗来实现。想想虽然加载进来了Activity等组件,但也仅仅是最为一个对象而存在,并没有在AndroidManifest中注册,没有生命周期的回调,并不能实现我们想要的效果。因此无论是dynamic_load_apk通过代理activity来操控插件activity的方式,还是DroidPlugin通过hook activity启动过程来启动插件activity的方式,都是对代理模式的应用
关于代理模式,见静态代理与动态代理
- 插件资源访问
res里的每一个资源都会在R.java里生成一个对应的Integer类型的id,APP启动时会先把R.java注册到当前的上下文环境,我们在代码里以R文件的方式使用资源时正是通过使用这些id访问res资源,然而插件的R.java并没有注册到当前的上下文环境,所以插件的res资源也就无法通过id使用了。
查看源码,通过“addAssetPath”方法重新生成一个新的Resource对象来保存插件中的资源,避免冲突。
关于插件资源访问,见使用插件中的R资源
1.2.6 Gradle配置打包
在实施插件化后,如何解决不同插件的开发人员的工作区问题。比如,插件1和插件2,需要分别下载哪些代码,如何独立运行?就像机票和火车票,如何只运行自己的插件,而不运行别人的插件?这是协同工作的问题。火车票和机票,这两个Android团队的各自工作区是不一样的,这时候就要用到Gradle脚本了,每个项目分别有各自的仓库,有各自不同的打包脚本,只需要把自己的插件跟宿主项目一起打包运行起来,而不用引入其他插件,还有更厉害的是,也可以把自己的插件当作一个App来打包并运行
1.3 主流框架
插件化框架各种各样,大致可以划分为:隔离型插件与非隔离型插件。
- 隔离型插件
- 此类插件是指:每个插件都是相对独立的个体,而且都运行在各自不同的沙盒中各个插件及宿主之间,不能像同一个应用一样直接共享数据。
- 比如360的RePlugin与DroidPlugin。DroidPlugin是不同插件有分别运行在不同的插件进程。RePlugin是每个插件都是使用的一个独立的classLoader来类加载器。都实现了代码级别的隔离,这两种都是隔离型插件。
- 非隔离型插件:
- 这种是对业务逻辑存在耦合的环境下,开发app最友好的插件化方案。这种插件框架,所有的插件都是运行在同一个进程中且未做隔离。宿主与插件、插件与插件之间可以直接共享数据。
- 比如Small和VirtualAPK.
Dynamic-load-apk详解
Dynamic-Load-Apk简称DL,这个开源框架作者是任玉刚,他的实现方式是,在宿主中埋一个代理Activity,更改ClassLoader后找到加载插件中的Activity,使用宿主中的Activity作为代理,回调给插件中Activity所以对应的生命周期。这个思路与AndroidDynamicLoader有点像,都是做一个代理,只不过Dynamic-load-apk加载的插件中的Activity
-
csdn伯努力不努力:Android插件化学习之路
-
拥有者csdn:singwhatiwanna
DroidPlugin详解
DroidPlugin是张勇实现的一套插件化方案,它的原理是Hook客户端一侧的系统Api
- Android插件化原理解析
Small框架详解
参考文献
-
简书H3c:Android插件化开发
-
csdn郑海波:Android插件化
-
csdn三精-大精wing:插件化技术
-
csdn scholarSu:Android插件化