360开源全面插件化方案RePlugin—让你像玩乐高一样开发APP

6月30日,360手机卫士插件化RePlugin正式开源,该款插件化方案可以帮助主程序在“确保极其稳定”的前提下,做到“无需升级主程序”就能支持新增组件、插件。其插件支持几乎所有在“单品”开发时的特性,包括静态Receiver、Task-Affinity、自定义Theme、进程坑位、AppCompat等。此外,RePlugin易于接入和管理,支持插件安装、升级、卸载、版本管理、内置插件管控等,对开发者而言非常容易运用,对用户而言也是“稳定和灵活”兼得。

写在前面

“RePlugin将是我们献给安卓世界最好的礼物。”当我们宣布这一消息时,心中的激动,无以言表。是的,三年的“厚积”,如今的“薄发”,看似平凡的话,实际上却饱含了我们太多的激动、辛酸与泪。

那么今天,我们就来详细的和您聊一聊,这个从 2014 年中旬,正式在手机卫士上启用,并即将开源的 360 RePlugin,究竟能为我们,更为您能带来什么。

RePlugin 是什么

RePlugin 是一套完整的、稳定的、适合全面使用的,占坑类插件化方案。其主要优势有:

  • 极其灵活:主程序无需升级(无需在 Manifest 中预埋组件),即可支持新增的四大组件,甚至全新的插件
  • 非常稳定:Hook 点仅有一处(Classloader)。其崩溃率做到仅为“万分之一”,并完美兼容市面上近乎所有的 Android ROM
  • 特性丰富:支持近乎所有在“单品”开发时的特性,包括静态 Receiver、Task-Affinity、自定义 Theme、进程坑位、AppCompat 等
  • 进程任意:可让各组件跑在 UI、常驻,甚至是“任意坑位进程”
  • 易于集成:无论插件还是主程序,只需“数行”就能完成接入
  • 自由隔离:想隔离就隔离(如不稳定或占资源的插件,易于释放),不想隔离的模块就混用(如各种基础、UI 插件,都跑在 UI 进程内,性能优异)
  • 管理成熟:拥有成熟稳定的“插件管理方案”,支持插件安装、升级、卸载、版本管理,甚至包括进程通讯、协议版本、安全校验等
  • 数亿支撑:有 360 手机卫士庞大的数亿用户做支撑,三年多的残酷验证,确保 App 用到的方案是最稳定、最适合使用的

截止 2017 年 6 月底,RePlugin 的:

  • 插件数已达 102 个(其中核心基础插件 57 个)
  • 插件占应用比(指把代码资源铺开,插件占整个应用的比例)高达 83%
  • 所支撑的单个应用年发版次数高达 596 次(平均每个工作日发版 2-3 次)

此外,目前 360 公司几乎所有的亿级用户量的 APP,以及多款主流第三方 APP,都采用了 RePlugin 方案。

总而言之,您们现在看到的 RePlugin,可以说是“久经沙场”、“历经风雨”的方案,是在数亿设备上,历经三年多时间验证的成熟方案,也是可以直接拿来为您们所用的,稳定与灵活兼得的方案。

(RePlugin 的核心优势)

(RePlugin 与现有插件化框架的对比)

为什么要做 RePlugin

插件化的好处

在讲述我们团队为何要在 2013 年底设计一套属于自己的插件化之前,我们先来简单谈谈,有了插件化方案后,能为我们带来多大的便利,它究竟解决了什么问题。

对于用户而言

一切按需:利用插件化方案,可以让您的应用变得“小而精”。只有当用户需要使用某个特定功能时,才可以下载并开启,且可以随时卸载插件。这不仅可以减小 APK 大小、节省流量,还可明显的减少内存、内部存储占用,将更多空间让给珍贵的相片、文档等资料。

随时体验新版:不用去应用市场等到大包升级,用户可以随时体验到新版的应用。现在红极一时的插件化、动态化(RN 类)、热更新技术,都或多或少的在围绕此点而展开,可见其对用户带来的巨大价值。

对于开发者而言

发版灵活:不用等市场上线,等用户主动升级,结果错过宝贵的时机。插件化方案可让您做到“随时发版”,不受“发版窗口期”的限制。甚至可针对不同地域、不同用户群、不同时段来更新,且可以快速验证自己的构想。

组织结构灵活:一旦发版变得足够灵活,则组织结构上就可以由原来的“统一作战”变成“百团作战”,每个团队都在开发“自己的插件单品”,制定自己的发版计划模块思维:可以让团队形成“模块意识”。当然,插件间、插件与宿主间允许有适度的耦合,但不会是“毫无控制”的那种。这让开发者们意识到,我们之间是“插件间的协定”,而非“同一屋檐下,随便胡来”,迫使团队以全新又合适的方式来开发应用。

Android 原生优势:和动态化(RN 类)不同,您可以使用最熟悉的 Java/Kotlin 语言,及各种原生 API 来开发您的插件。这使得应用能和系统更“契合”,充分利用原生的各种优势,且在性能上几乎感受不到影响。

如上所述,无论是对用户,还是对开发者而言,使用插件化框架都是大有益处的,理应做到“飞入寻常应用家”。

然而,在实际调查过程中,我们却发现了一个和这些好处完全不匹配的奇怪现象。究竟是什么呢?

既然插件化这么好,为什么……

虽然我事先已做好功课,然而在 GMTC 上的调查结果却让人大跌眼镜——在听讲的 200 多位安卓开发者中,仅有不足 5% 的比例,使用了插件化方案。超过九成的开发者,目前上没有将插件化应用在软件开发之中。

实际上,这和我们在线下观察到的结果基本吻合。结合之前的调查,我们发现,有三大挑战制约了插件化在 Android 开发界的普及:

  • 不够稳定:目前有很多比较灵活的插件化框架,虽然支持特性众多,但因 Hook 点较多,所以不是非常稳定。因此很多大型项目不是很愿意用它们来开发插件,担心出现应用崩溃、插件无法正常使用等问题。
  • 不够灵活:有一些相对稳定的插件化框架,又存在“不够灵活、自由”的问题,一旦插件有较大改动,如新增 Activity、Service、进程等,就需要主程序发版,更不用说能做到“一年前的主程序,无需升级,可以用新插件和组件”。也因此,很多项目的“接入动机”也就大打折扣了。
  • 功能丰富项目专用:目前市面上的插件化方案,大多仅在功能丰富的大型项目中,才被考虑使用,且多用于边缘功能,比如“红包”、“天气”、“摇一摇”等,他们认为只有“非核心”模块,才会考虑做成插件。这也使得插件化的应用范围非常狭窄。

然而,通过我们多年的实践证明,以上三大挑战,其实是可以被攻克的。这也是我们今天要为您介绍的,360 手机卫士首款 Android 开源项目——RePlugin。

既然这么大胆,那么,我们究竟是怎么做到的呢?

我们是怎么做的

“不够稳定”怎么破?

前文提到,不够稳定的主要原因是 Hook 了太多。那么市面上比较灵活的插件化框架,究竟 Hook 了哪些呢?

注意:这里所说的“Hook”是指通过 Java 反射手段,获取并修改与系统 Server 等交互的 Internal API,来让框架正常工作的行为,如上面所列部分。正常情况下的反射(例如反射类内部自己的字段)不属于 Hook。

看似灵活,然而下列三种情况,将很有可能导致插件甚至应用,彻底不能工作:

  • Android 升级:既然是内部 API,那么 Android 自然不会认为是“不能修改的”,一旦系统升级时做了改动,轻则功能不正常,重则直接 Crash。例如有的插件化框架曾遇到在 Android 7.0 上出现异常,必须升级主程序才能解决的事故,历历在目。
  • ROM 修改:比 Android 升级更可怕的,是第三方 ROM 对内部 API 的“各种改”。这个适配难度是可想而知的。例如自定义 Resource、自定义 WiFiService 等造成的“插件化血案”,不一而足。
  • 使用不当:“常在河边站,哪有不湿鞋”,Hook 的点多了,一旦对某一点的实现原理理解不透而出了错。结果,前功尽弃不说,还可能出现更严重,且更难以察觉的崩溃事故,细思恐极。

基于上述的情况,我们团队在 2014 年初,研究全新占坑插件化框架(注意,此时 DroidPlugin 类方案还没有出现)时,就定了个“小目标”:让 Hook 越少越好。经过一次次的研究讨论,最终确定只 Hook 一个点:ClassLoader,且要求“坚持到底”,所有改动都是基于此来展开。

对我们而言,这是里程碑式的决定,即便到现在来看也是如此。

唯一 Hook——ClassLoader

修改 ClassLoader 的点其实不难,如上图所示逐步反射即可。然而需要注意的是,这个 ClassLoader 一定得继承自 PathClassLoader,防止 Android 7.x 因使用 addDexPath 而有问题。

除此之外,此 ClassLoader 所在位置也非常稳定。目前来看,从 Android 2.1 至今都没有发生过位置、名称上的变化,可以长期使用。

关于这一点,我们之后会有一篇文章来详述,敬请期待。

“不够灵活”怎么破?

前文提到,就目前市面上的插件化框架而言,若做的足够稳定,则多少会失去一些灵活性。对于我们拥有这么多模块的产品而言,这同样也是不可接受的。

为此,我们在“坚持一个 Hook 点”原则的前提下,通过不断创新,最终解决了上述问题。

我们的核心思路,是 2015 年以后才开始“老生常谈”的一个词,那就是:坑位。

坑位方案思想

  • 非坑位方案:标准的一一对应关系。例如,插件有个 XXXActivity,那么主程序则要求必须也有个 XXXActivity(名字未必一样)来对应。 一旦插件要添加一个新的 Activity,则对应的,主程序也必须得添加,否则就无法使用这个 Activity。
  • 准坑位方案:有的(如 2013 年的我们)会通过 Fragment 来模拟 Activity,从而实现一定程度上的灵活。然而真的遇到大需求,如和其它应用 Activity 的交互等,就局限百出,很难称得上是完整坑位思想。
  • 坑位方案:可以做到“一对多”的关系。例如插件有个 XXXActivity,则运行时,主程序可以将自己的 N1ST1 对应到这个 XXXActivity 上。一旦该 Activity 退出,则 N1ST1 就“空闲”出来。而当 YYYActivity 进来后,又会重新占用 N1ST1。这样就可以做到一个坑位(如 N1ST1)对应多个实体。 此外,这个 YYYActivity 既可以是已有的,也可以是插件新增的。这样无论插件如何升级,主程序都可以不用动,即可支持新的 Activity。

当然,我们当时 2014 年设计坑位思想时,也绝不仅仅针对 Activity,而是整个四大组件,甚至到了后期,连 Theme、进程、Task-Affinity 等都做到了“坑位化”,只不过实现方式各异而已。

因篇幅所限,这里仅以常用的 Activity 来简述。有关更详细的内容,欢迎继续关注我们的《RePlugin 深度剖析》系列文章。

Activity 坑位

目前市面上的完整坑位方案,Hook 的地段可以说是“各有千秋”,从 AMS、Instrumentation 到 ContextImpl 都有,并以此让插件变得更灵活。

而我们的方案和他们有些不同:除了 ClassLoader 是 Hook 的,其余一律不需要。那么我们究竟是如何开启一个插件的 Activity 呢?

简单来说,我们有五个核心步骤:

  • 记录:通过 PM.startActivity 方法来“记录”到要打开的 Activity 的名字
  • 寻找坑:通过一系列流程来找到一个可用的坑位(如 N1ST1)并记录
  • 开启坑:通过系统的 startActivity 来直接打开这个坑位(注意,此处没有做 Hook)
  • 拦截:当系统调到我们的 HostClassLoader(唯一 Hook 点)时,我们“拦了一道”,找到此坑位(N1ST1)对应的真正的 Activity(XXXActivity)
  • 返回:加载插件并获取这个真正的 Activity 的 Class 对象并返回给系统

其中,PM.startActivity 可以由插件 / 宿主直接调用。若在插件内部,则可以直接通过 startActivity 方法来打开,更为方便。

当然,这只是核心思路,而每一步我们都会做各种逻辑处理,尤其是“寻找坑”一节,这也是我们的核心之一。

Activity 分层坑位

找坑是有非常多的注意点:

从“分层”上看,则需要支持:各种 LaunchMode、所有透明 / 非透明的 Theme、TaskAffinity 坑位、进程坑位等,甚至 AppCompat 的情况也要考虑。

从“管理”上看,又需要考虑到坑位回收释放,坑位分配,甚至坑位不足时的处理策略等,不一而足。

篇幅所限,以后会写详细介绍,敬请期待。

向完美前进——动态编译方案

通过刚才的叙述,像 PM.startActivity 等确实可通过一些方法,来让插件“无成本使用”。但是,像 Provider 的调用(本质是 IContentProvider),Service 的 stopSelf(是 final 的),以及因涉及坑位分配,而不得不需复写相应方法的 Activity 等。这里面存在两个矛盾点:

  • 若不 Hook,则必须要插件开发者“自行处理”,稍显繁琐,不够完美;
  • 一旦 Hook(如尝试 Hook AMS、IContentProvider 等),又破坏了我们坚持的“1 Hook 原则”,进而担心未来出现兼容性问题

利弊之间如何取舍,令人头疼。

针对这个问题,我们的核心理念是:“绝不在 Hook 及稳定性上做任何妥协”,转而创新性的做一套“动态编译方案”,力图从“编译期”来解决这个难题。

大体而言,就是把一些我们认为需要开发者修改的类和方法,借助神奇的 JavaAssist 来做自动化修改,这样可节省开发者的改动成本,达到想要的效果。

一图以蔽之:

而做到了这一点以后,你会发现,下面的“梦想”就变成了现实:

例如,有个名叫“360 桌面”的应用,它想把自己变成“插件”跑起来。那么,有了“动态编译方案”,结合“插件类库”和框架的支持,最终的效果是——只需改几行 Gradle,就能直接生成一个 APK。这个 APK:

既可以作为插件直接跑在主程序中 又可以作为单品直接安装到设备中

是的,就是这么的神奇!

就这些了?

当然远不止这些。我在 GMTC 上演示了一段视频,将庞大又复杂的 360 桌面变成插件,运行在 360 手机卫士中。这让在场嘉宾倍感惊叹。

试想,一个桌面(Luncher)插件涉及到的功能是“方方面面”的,小到 TaskDesription 和 SO 的使用,大到四大组件、Task-Affinity 坑位、静态 Receiver 和进程坑位的处理,都需一一兼顾。所以,要做到这一点,绝不仅仅是前面所说的那几点就能搞定的。

当然,“让 360 桌面变成插件”,还不是最有意义的。真正让 RePlugin 变得更有意义的,就是拿我们的 360 手机卫士来“开刀”,让数百个——甚至说,近乎一切——的模块,都成为 RePlugin 的插件,并完美的运行起来。

“功能丰富项目专用”怎么破?

前文提到,之所以“功能丰富项目专用”,主要和目前市面上的插件化方案的定位有关,以至于开发者认为:“插件 = 免安装”、“基础放在主程序里更放心”、“插件开发成本高”等。

然而,仔细分析深层原因后发现,其实最为核心的原因,是插件化和相关框架(包括热更新方案等)的“定位”不同。我知道的有:

  • 组件化:以 Atlas/ACDD 为代表,官方的定义是“在运行环境中按需地去完成各个 bundle 的安装,加载类和资源”,以解决大团队协作时的各种难题,提供了热修复能力。其依赖“编译期”较多但很稳定。而大组件的添加,则仍需要主程序发版才能解决,毕竟如官方所述,“组件化 ≠ 插件化”
  • 动态插件化:以 Dynamic-Load-Apk 为代表,其目的是解决发版、升级时的问题。大多采用“非占坑”思路,添加新组件时,还是会要求升级主程序。此类方案较多,且是插件化的“鼻祖”了,值得我们尊敬与学习
  • 免安装应用:以 DroidPlugin 为代表,官方的定义是“可以在无需安装、修改的情况下运行 APK 文件”。其场景比较类似于“应用分身”此外,此项目是由我们 360 公司的手机助手团队研发,在 2015 年中旬发布。它的出现又一次轰动了插件化界,并掀起了“插件化研究”的思潮。
  • 热修复:以 Tinker/Robust 为代表,目的是以最小的代价来快速打各种 Patch,让应用能够持续更新。其核心优势是“无需重启进程就能打补丁”。同样,新添加的大块功能,则还是需要升级主程序的,毕竟这不是“热修复”的主要目标。

那么,我们是这三种目标之一吗?

显然,都不是。那么,我们的目标究竟是什么?

答案:全面插件化

我们的目标只有一个:全面插件化。

全面插件化:以 RePlugin 为代表。其目的是“尽可能多的让模块变成插件”,并在很稳定的前提下,尽可能像开发“单品”那样灵活,并享受插件化方案带来的各种好处。

也就是说,无论是 UI、核心业务、合作插件、后台服务,还是基础功能,都可以变成插件,并在 RePlugin 框架内稳定又灵活的运行起来。甚至,不仅大项目能用,小项目——甚至只是个计算器——都可以使用 RePlugin 来提升自己的灵活性,并最终实现“插件满天下”的神奇效果。

而这一点,则是我们,和目前市面上大多数插件化框架的主要差异。

目前卫士插件的现状

目前手机卫士已有的插件,可以分为以下几类,供各 App 开发者参考:

  • UI 插件:如首页(是的,你没看错)、体检、信息流等
  • 业务插件:如清理、骚扰拦截、悬浮窗等
  • 合作插件:如程序锁、免费 WiFi、安全桌面等
  • 后台插件:如 Push、服务管理、Protobuf 等
  • 基础插件:如安全 WebView、分享、定位等

而这样的插件,我们有 102 个。可以想见,一旦这些插件不能用,那么手机卫士瞬间变空壳。三年已过,回头想想,值得回味。

一起创造更高的价值

说到这儿,让我想起了 Lody(VirtualApp 作者,高中大牛)在接受 InfoQ 采访,即将结束时所说的那段话:

“插件化技术的成熟程度虽然在最近几年呈上升趋势,但是总体而言仍然处于初、中级阶段。App 沙盒技术的出现就是插件化发展的创新和第一阶段的产物。在未来,我相信很多插件化技术会被更多的应用,如果插件化稳定到了一定的程度,甚至可以颠覆 App 开发的方式。”

这,其实也是 RePlugin 的终极价值,那就是——让插件化能“飞入寻常应用家”,做到稳定、灵活、自由,大小项目兼用。

当然,在“全面插件化”甚至“全民插件化”的道路上,我们还有太多的路要走,而如此庞大又复杂的 RePlugin,也绝不可能是一两个人在战斗,而是十多位研发人员共同努力的成果,且得到了部门领导和公司技术委员会的大力支持。我相信,RePlugin 的开源,是一场新的开始。这不仅仅是一个决定,而是一份信念,让全社区的有志之士能一起参与进来,共同为中国乃至世界 App 提供一套“全面插件化”的完美方案而努力。

那么,就让我们一起,为“全面插件化”的梦想而“窒息”吧!

全面插件化时代,为您来临!

7 月初正式开源,GitHub 项目地址:

https://github.com/Qihoo360/RePlugin

学习快乐!!


原文发布于微信公众号 - Android历练记(gh_db8538619cdd)

原文发表时间:2017-07-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏FreeBuf

iPhone碰上1970年变砖是什么梗?又该如何拯救?

继上个月的十二行代码分分钟让浏览器崩溃iPhone重启事件之后,近日又有网友爆出:如果把64位的iOS设备(iPhone、iPad、iPod touch)系统时...

213100
来自专栏北京马哥教育

海量的超赞 Linux 软件

1.7K40
来自专栏BIT泽清

这些年iOS AppStore 套壳开发上架(棋牌,彩票,金融原油期货类App)过程-系列2

在之前的文章里《 这些年iOS AppStore 套壳开发上架过程》说过了这些年iOS AppStore 套壳开发上架过程-从棋牌类到彩票类在到如今的金融原油期...

2.4K40
来自专栏Java技术栈

阿里巴巴,排行前10的开源项目!

1、FastDFS FastDFS是一个开源的分布式文件系统,她对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储...

48880
来自专栏Python爬虫与算法进阶

爬虫学到什么程度可以去找工作

随便看看知乎上的教程就可以入门了,就Python而言,会requests当然是不够的,还需要了解scrapy和pyspider这两个框架,scrapy_redi...

14510
来自专栏杨建荣的学习笔记

我身边的一些数据库事故 (r5笔记第52天)

最近携程的数据事故闹得沸沸扬扬,不管是什么原因,问题终究发生了。在问题发生的时候,更关键的是解决方法和防范措施,一般在升级或者重大的生产演练中,我们都有一个le...

390100
来自专栏FreeBuf

代码审计“吃鸡”辅助外挂黑色产业链

*本文原创作者:Draven,本文属FreeBuf原创奖励计划,未经许可禁止转载 绝地求生可以说是本年度最火的游戏了。首付99,月付30,也可以说是土豪游戏了。...

43070
来自专栏视频云

腾讯云直播答题方案解析

基于腾讯云业界领先的视频云技术,提供一站式在线知识竞技接入方案,并独家提供微信小程序接入方案。

6.7K90
来自专栏web前端教室

【全栈】web前端全栈开发,该怎么学?(我个人主观看法)

前端全栈,现在学前端不提全栈好像都不好意思跟人打招呼一样。今天周末就写一篇文章来跟大家交流一下,我心中的前端全栈开发,大概是个什么样子。

13640
来自专栏魏琼东

基于AgileEAS.NET SOA 中间件领域模型数据器快速打造自己的代码生成器

一、前言      AgileEAS.NET SOA 中间件平台是一款基于基于敏捷并行开发思想和Microsoft .Net构件(组件)开发技术而构建的一个快速...

38460

扫码关注云+社区

领取腾讯云代金券