这是最好的时代,各大应用市场对Hook的态度是开放的,因此,国内各大互联网App无一不有自己的插件化框架。有了开放的环境,才会有无数英雄竟折腰,在Android插件化的领域百家争鸣、百花齐放。
这是最坏的年代,随着插件化技术在中国的普及,你会发现,去中国的各大互联网公司面试,一般都会聊聊插件化的技术。这就难为了中国的这些Android开发人员,要去熟悉反射和newProxyInstance,要去掌握ClassLoader和Resources的原理。
插件化是什么?
一个游戏平台,比如说联众,支持上百种游戏,比如象棋、桥牌、80分。一个包括所有游戏的游戏平台往往有上百兆的体积,需要下载很久时间,但是用户往往只玩其中的1-2款游戏。让用户下载并不玩的上百款游戏,是不明智的做法。
此外,任何一个游戏更新或者新上线一个游戏,都需要重新下载上百兆的安装包,也是会让用户抓狂的事情。
所以,游戏平台必然采用插件化技术。
通常的做法是,只让用户下载一个十几兆大小的安装包,其中只包括游戏大厅和一个全民类游戏“斗地主”。用户进入游戏大厅,可以看到游戏清单,点击80分就下载80分的游戏插件,点击中国象棋就下载中国象棋的游戏插件——称之为“按需下载”。
这就是插件化编程,不过这是基于电脑上的游戏平台,是一个个exe可执行文件。
在Android领域,是没有exe这种文件的。所有的文件都会被压缩成一个apk文件,然后下载到本地。Android上所谓的安装App的过程,其实就是把apk放到本地的一个目录,然后使用PMS读取其中的权限信息和四大组件信息。所以Android领域的插件化编程,与电脑上的软件的插件化编程是不一样的。
其实,在Android领域,对于游戏而言,用的还真不是插件化技术,而是从服务器动态下发脚本,根据脚本中的信息,修改人物属性,增加道具和地图。
Android插件化技术,主要用在新闻、电商、阅读、出行、视频、音乐等领域,比如说旅游类App,因为涉及到下单和支付,所以算是电商的一个分支,旅游类App可以拆分出酒店、机票、火车票等完全独立的插件。
为什么需要插件化
就在Android程序员疯狂编写新需求之际,自然会衍生出各种bug,甚至是崩溃。
App有bug,会导致用户下不了单,而一旦崩溃,那就连下单页面都进不去,因此我们要在最短时间内修复这些问题,重新发版到Android各大市场已经来不及,每分每秒都在丢失生意的流失,因此,Android插件化的意义就体现出来了,不需要用户重新下载App,分分钟就能享受到插件新的版本。
另一方面,如果要和竞争对手抢占市场,那么谁发布新功能越快越多,对市场对用户的占有率就越高。如果隔三岔五就发布一个新版本到Android各大市场,那么用户会不胜其烦,发版周期固定为半个月,又会导致新功能长期积压,半个月后才能让用户见到,而竞争对手早就让用户在使用同样的新功能了。这时候,如果有插件化技术支持,那么新功能就可以做完之后立刻就让用户看到,这可是让竞争对手闻风丧胆的手段。
插件化的历史
2012年7月27日,是Android插件化技术的第一个里程碑。大众点评的屠毅敏(Github名为mmin18),发布了第一个Android插件化开源项目AndroidDynamicLoader ,大众点评的App就是基于这个框架来实现的。这是基于Fragment来实现的一个插件化框架。通过动态加载插件中的Fragement,来实现页面的切换,而Activity作为Fragement的容器却只有一个。我们也是在这个开源项目中第一次看到了如何通过反射调用AssetManger的addAssetPath方法来处理插件中的资源。
2013年,出现了23Code。23Code提供了一个壳,在这个壳里可以动态下载插件,然后动态运行。我们可以在壳外编写各种各样的控件,在这个框架下去运行。这就是Android插件化技术。这个项目的作者和开源地址,我不是很清楚,如果作者有幸读到我这本书,请联系我,一起喝杯咖啡。
2013年3月27日,阿里技术沙龙第16期,淘宝客户端的伯奎做了一个技术分享,专门讲淘宝的Atlas插件化框架,包括ActivityThread那几个类的Hook、增量更新、降级、兼容这些技术。这个视频 ,只是从宏观来讲插件化,具体怎么实现,一句没说,更没有开源项目可以参考。时隔5年再看这个视频,会觉得很简单,但在2013年,这个思想还是很先进的,毕竟那时的我还处在Android入门阶段。
2014年3月30日8点20分,是Android插件化的第2个里程碑。任玉刚开源了一个Android插件化项目dynamic-load-apk ,这跟后续介绍的很多插件化项目都不太一样,它没有篡改Android系统的底层方法,而是从上层——也就是App应用层解决问题,通过创建一个ProxyActivity类,由它来进行分发,启动相应的插件Activity。因为任玉刚在这个框架中发明了一个that关键字,所以我在本书中把它称为that框架。其实作者是不喜欢我给他的最爱起的这个外号的,他移植称之为DL。曾经和玉刚在一起吃饭吹牛,他曾经感慨当年如何举步维艰的开发这个框架,因为2014年之前没有太多的插件化技术资料可以参考。
that框架一开始只有Activity的插件化实现,后来随着田啸和宋思宇的加入,实现了Service的插件化。2015年4月that框架趋于稳定。我那个时间在途牛做App技术总监,无意中看到这个框架,毅然决定在途牛的App中引入(其实当时是没得选)that框架。当时具体实施的是汪亮亮和魏正斌,我的这两个兄弟当时一个刚生完孩子另一个快要生孩子,还是咬咬牙把这个that框架移植到了途牛App中。that框架经受住了千万级日活的App的考验,这是它落地的第一个实际项目 。
与此同时,张涛也在研究插件化的技术实现 ,并在2014年5月,在阅读了DL的全部源码后,发布了他的第一个插件化框架CJFrameForAndroid 。它的设计思想和that差不多,只是把ProxyActivity和ProxyService称为托管所。此外,CJFrameForAndroid框架还给出了Activity的LaunchMode的解决方案,这是对插件化框架一个很重要的贡献,可以直接移植到that框架中。
2014年11月,houkx在GitHub上发布了插件化项目android-pluginmgr ,这个框架最早提出在Manifest文件中注册一个StubActivity来欺骗AMS,实际上却打开插件中的ActivityA。但是他并没有使用篡改Instrumnetation和ActivityThread的技术,而是通过dexmaker.jar这个工具动态生成StubActivity,StubActivity类继承自插件中的ActivityA。
现在看起来,这种动态生成类的思想,并不适用于插件化,但在当时能走到这一步已经很不容易了。每个人都在插件化的舞台中很好的诠释自己的角色,在特定的时间发挥特定的作用。
同时,houkx还发现,在插件中申请的权限无法生效,所以要事先在宿主App中申请所有的权限。
android-pluginmgr有两个分支,作者的插件化思想位于dev这个分支。后来高中生Lody参与了这个开源项目,把android-pluginmgr修改为篡改Instrumnetation的思想,体现在master分支上,但这已是2015年11月的事情了。
2014年12月8日,有一个好消息,那就是Android Studio1.0版本的出现。Android开发人员开始逐步抛弃Eclipse,而投入Android Studio的怀抱。Android Studio借助于Gradle来编译和打包,这就使得插件化框架的设计变得简单了许多,排除了之前Eclipse还要使用Ant来运行Android SDK的各种不便。
时间到了2015年。
这里说到高中生Lody,此刻他还是高二。他是从初中开始研究Android系统源码的。他的第一个著名的开源项目是TurboDex ,能够以迅雷不及掩耳盗铃之势快速的加载dex,这在插件化框架中尤其好用,因为首次加载所有的插件需要花很久的时间。
2015年3月底,Lody发布插件化项目Direct-Load-apk 。这个框架结合了任玉刚的that框架的静态代理思想、Houkx的pluginmgr框架的欺骗AMS的思想,并篡改了Instrumnetation。可惜Lody当时还是个学生,没有花大力气宣传这个框架,以至于没有让太多的人知道这个框架的存在。
Lody的传奇还没结束,后来他投身于VirtualApp,这是一个app,相当于Android系统之上的虚机,这就是一个更深入的技术领域了,我们稍后再提及。
2015年5月,limpoxe发布插件化框架Android-Plugin-Framework 。
2015年7月,kaedea发布插件化框架android-dynamical-loading 。
2015年8月27号,是Android插件化技术的第3个里程碑,张勇的DroidPlugin问世了。张勇当时在360的手机助手团队,DroidPlugin就是手机助手使用的插件化框架。这个框架的神奇在于,能把任意的App都加载到宿主里面去。你可以基于这个框架写一个宿主App,然后就可以把别人写的App都当作插件来加载。
DroidPlugin把Hook机制做到了极致,这就好比跟媳妇撒了一个谎,事后要再撒100个谎来圆前面的那个谎言,谎言说多了,逐渐连你自己都相信了。
DroidPlugin的功能很强大,但强大的代价就是要篡改很多Android系统的底层代码,而且张勇这哥们比较懒,没有给DroidPlugin项目加任何说明文档,导致这个框架不太容易理解。网上有很多人写文章研究DroidPlugin,但其中写的最好的是田维术 。他当时就在360,刚刚毕业转正,据说还和张勇一起在公司楼下抽过烟。近水楼台先得月,于是他写出来一系列介绍DroidPlugin思想的文章,包括Binder和AIDL的原理、Hook机制、四大组件的插件化机制。
2015年是Android插件化蓬勃发展的一年,不光有that框架和DroidPlugin,很多插件化框架也在这个时候诞生。
OpenAtlas,这个项目是5月发布在github上的,后来改名为ACDD。里面提出了通过修改并重新生成AAPT命令,使得插件apk的资源id不再是固定的0x7f,可以修改为0x71这样的值。这就解决了把插件资源合并到宿主HostApp资源后资源id冲突的问题。
OpenAtlas也是基于篡改了Android底层的Instrumentation的execStartActivity方法,来实现Activity的插件化。
此外,OpenAltas还篡改了ContextWrapper,在其中重写了getResource等方法,因为Activity是ContextWrapper的孙子,所以插件Activity就会继承这些getResource方法,从而取到插件中的资源——这种做法现在已经不用了,我们是通过为插件Activity创建一个基类BasePluginActivity并在其中重写getResource方法来实现插件资源加载的。
携程于2015年10月开源了他们的插件化框架DynamicAPK ,这是基于OpenAltas框架基础之上,融入了携程自己特殊的业务逻辑。
2015年12月底,林光亮Small框架发布,他当时在福建一家二手车交易平台,这个框架是为这个二手车平台的App量身打造的。
随着2015年的落幕,插件化技术所涉及的各种技术难点都已经有了一种甚至多种解决方案。在这一年,插件化技术领域呈现了百家争鸣的繁荣,这一时期以个人主导发明的插件化框架为主,基本上分为两类,以张勇的DroidPlugin为代表的动态替换方案,以任玉刚的that框架为代表的静态代理方案。
就在2015年,Android热修复技术和React Native开始进入开发者的视线,与Android插件化技术平分秋色。Android插件化技术不再是开发人员唯一的选择。
2016年起,国内各大互联网公司陆续开源自己研发的插件化框架。这时候已经没有什么新技术出现了,因为插件化所有的解决方案都已经在2015年由个人开发者们给出来了。互联网公司是验证这些插件化技术是否可行的最好的平台,因为他们的App动辄千万用户的日活。
按时间顺序列举一下
2016年8月,掌阅推出Zeus 。
2017年3月,阿里推出Atlas 。
2017年6月26日,360手机卫士的RePlugin 。
2017年6月29日,滴滴推出VisualApk 。
仔细读这些这些框架的源码,会发现,互联网公司开源的这些框架,更多关注于:
插件的兼容性,包括Android系统的升级对插件化框架的影响,对各个手机ROM的不同而对插件化的影响。
插件的稳定性,比如说各种奇葩的崩溃。
对插件的管理,包括安装和卸载。
斗转星移,时光荏苒,虽然只有几年时间,但各个插件化框架已经渐趋稳定,现在做插件化技术的开发人员,只需要关注每年Android系统版本的升级,对自身框架的影响,以及如果消除这种影响。
随着插件化领域的技术基本成型,我身边很多做插件化的朋友,都开始转行,有的还是Android这个领域,比如说张勇基于他的DroidPlugin框架,在此基础上,在做他的创业项目闪电盒子;有的转入区块链,每天沉溺于用GO语言写智能合约。
谨以此为献给那些在插件化领域中做出过贡献的朋友们。包括开源框架的作者,以及写文章传经布道的作者。我的见识有限,有些人有些框架有些文章可能会没有提及,欢迎广大读者多多指正。
插件化的用途到底是什么
曾几何时,我们一度天真的认为,Android插件化是为了增加新功能,或者增加一个完整的模块。
费了不少时间和精力,等项目实施了插件化后,我们才发现,插件化80%的使用场景,是为了修复线上bug。在这一点上,它和Tinker、Robust这类热修复工具,具有相同的能力,甚至比热修复工具做得更好。
App每半个月发一次版,新功能上线,一般都会等这个时间点。另一方面,很多公司的Android发版策略是受iPhone新版本影响的,新功能要等两个版本一起面世,那就只有等Apple Store审核通过iPhone的版本,Android才能发版。所以,真的不是那么着急。
在没有插件化的年代,我们做开发,都是战战兢兢的,生怕写出什么bug,非常严重的,就要重新发版本。有了插件化框架,开发人员没有了后顾之忧,代码质量就不是很高了——反正出错了就赶快发一个插件包修复一下。于是App上线后,每个插件化,每天都会有一到两个新版本发布。
Android插件化框架,已经沦落为bug修复的工具。这是我们所不愿开到的场景。
其实,插件化框架,更适合于游戏领域。比如说王者荣耀,经常都会有新皮肤,或者隔几天上线一个新英雄,调整一下英雄的数据,这些都不需要发版。
插件化还有一种很好的使用场景,那就是ABTest,只是没有深入人心罢了。当产品经理为两种风格的设计举棋不定时,那么把这两种策略做成两个插件包,让50%的用户下载A策略,另外50%的用户下载B策略,一周后看数据,比如说页面转化率,就知道哪种策略更优了。数据驱动产品。
前面的章节我们讲过Android的组件化。那是随着业务线的独立,Android和iOS团队也拆分到各自的业务线,有各自的汇报关系,因此有必要把酒店机票火车票这些不同的业务拆分成不同的模块。在Android组件化中,模块之间还是以aar的方式进行依赖的,所以我们可以借助于Maven来管理这些aar。
Android的这种组件化模型,仅适用于开发阶段,一旦线上有了bug,或者要发布新功能,那就要所有模块重新打包一起发布新版本。
Android组件化再往前走一步,就是插件化。此时,各个业务模块提供的就不再是aar了,而是一个打包好的apk文件,放在宿主App的assets目录下。这样,发版后,某个模块有更新,只重新打包这个模块的代码,生成增量包,放到服务器上供用户下载就可以了。
这才是Android插件化在企业级开发中的价值所在。一般的小公司,只做了Andoid组件化,没有往前再走一步,做插件化,所以体会不到这个好处,因为开发成本很高,投入产出比很低。
只有中国这么玩?
有读者会问,Android插件化在中国如火如荼,为什么在国外缺悄无声息?
打开硅谷那些独角兽的App,都没有发现插件化的影子。
一方面原因是,国外人都使用Google Play,这个官方市场不允许插件化App的存在,审核会不通过,这就很像Apple Store了。
另一方面原因是,老外真的没有这样的需求啊。
所以当你发现国外某款App显示数据错误了,或者莫名其妙崩溃了,就算你反馈给他们,得到的也是一副坐看闲云、宠辱不惊的回复,下个版本再修复吧。下个版本什么时候?一个月后。
这就和中国国内的App境遇不同了。在一二线互联网公司,任何数据显示的错误,或者崩溃,都会导致订单数量的下降,直接影响的是钱啊。所以,我经常半夜被叫醒去修bug,然后快速出新版本的插件包,避免更多订单的损失。
国内的一二线互联网公司,会花很多钱养一群做插件化框架的人,框架设计完,他们一般会比较闲。在Android每年发布新版本的时候,他们会很忙,去研究新版本改动了哪些Android系统源码,会对自家公司的插件化框架有什么影响。从长期来看,公司花的这些钱是划算的,基本等于没有插件化而损失的订单数量赚的钱。
而对于国内的中小型公司,以及创业公司,他们没有额外的财力来做自己的插件化框架,一般就采用国内比较稳定的、开源的、持续更新的插件化框架。后来有了RN,就转投入RN的怀抱了。
就在中国的各路牛人纷纷推出自家的Android插件化框架,你方唱罢我登场之际,老外们在研究些什么呢?
老外们比较关注于用户体验,所以你看到在国外Material Design大行其道,而在中国,基本是设计师只设计出iOS的样稿,Android保持做的一样就够了。
老外们比较关注于函数式编程,他们追求代码的优雅、实用、健壮、复用,而不像国内的App,为了赶进度超越竞争对手,纯靠人力堆砌代码,甚至带bug上线,以后有时间了再进行重构,而此时当初写代码的哥们也许已经离职了。
所以,当硅谷那边层出不穷的推出ButterKnife、Dagger、AspectJ、OKHttp、Retrofit、RxJava的时候,中国能拿出来与之媲美的只有各种插件化框架和热修复框架,以及双开技术。
四大组件都需要插件化吗
四大组件都需要插件化吗?这些年,我是一直带着这个问题做插件化技术的。
我从事的几家公司都是OTA(在线旅游)行业。这类App类似于电商,包括完整的一套下单支付流程,用得最多的是Activity,两三百个不足为奇,而Service和Receiver用得很少,屈指可数,ContentProvider根本就没用过。
国内大部分App都是如此。
根据技术栈来划分App行业:
游戏类App,人家有一套自己的在线更新流程,很多用的是Lua这样的脚本。
手机助手,手机卫士,这类App对Service、Receiver、ContentProvider的使用比较多。所以四大组件的插件化都必须实现。
音乐类、视频类、直播类App,除了使用比较多的Activity,对Service和Receiver的依赖很强。
电商类、社交类、新闻类、阅读类App,基本是Activity,其它三大组件使用不是很多,可以只考虑对Activity和Service插件化的支持。
我们应该根据App对四大组件的依赖程度。来选择合适的插件化技术。四大组件全都实现插件化,固然是最好的;但是如果App中主要是Activity,那么选择静态代理That框架,其实就够了。
双开和虚机
插件化的未来是什么?答案是,虚拟机技术。
各位读者,应该有过在PC机上,安装虚拟机的经历。只要你电脑的内存足够大,那么就可以同时打开多个虚拟机,在每个虚拟机上,都安装QQ软件,使用不同的账号登录,然后,自己跟自己聊天。
自己和自己聊天,是一件多么孤单寂寞冷的事情,单身狗一定受到了一万多点伤害。
在Android系统上,是否也能支持安装一个或多个虚拟机呢?国内已经有人在做了,我所知道的,一个是高中生Lody,哦,写出这本书的时候,他应该已经是大学生了吧,他有一个很著名的开源项目VirtualApp ,这个项目现在已经商业化运作了;另一个是DroidPlugin的作者张勇,他现在创业专职做这个,在DroidPlugin的基础上,研发了闪电盒子,可以极速加载各种apk。
有了这样一个虚拟机系统,我们就可以在手机上打开两个不同账号的QQ,自己和自己聊天了。
我们称同时打开一个App的多个分身的技术叫双开。现在国内的有些手机系统已经支持双开技术了。可以在设置中看到这一选项。
关于双开和虚机的技术,我们就介绍这么多,毕竟这已经不是本书所设计的技术范畴了。
从原生页面到HTML 5的过渡
无线的技术越来越成熟,已经从2012年时的一片荒芜,发展为现在的蔚然大观。对于国人来说,我们比较关注的是这么几个点,热修复,性能,开发效率,App体积,数据驱动产品。这些点目前都已经有了很好的解决方案,也涌现了RxJava、CanaryLeak这样优秀的框架。这个话题很大,我就不展开说了。
由App技术的无比繁荣,回想起我20404年刚出来工作的时候,IT行业正在从CS转型为BS。CS就是Service-Client,2004年之前大都是这样的软件,比如Windows上安装一个联众的客户端就可以和网友斗地主了,后来互联网的技术成熟起来了,就开始把原先的系统都搬到网站上,这就是BS,全称是Browser-Server。
后来BS做多了,大家觉得BS太单薄,很多功能不支持,不如CS,于是就搞出个SmartClient的概念,也就是智能客户端,Outlook就是一个很好的例子,你可以脱机读和写邮件,没网络也可以,什么时候有网络了,再发写好的邮件发出去。
再后来Flash就火起来了,这个本来是网页制作工具三剑客,却阴差阳错的成为了网页富客户端的鼻祖。在此基础上便有了Flex,现在还有些公司在使用。微软这时候也来插一脚,搞出个Silverlight,也是搭载在网页上的。与此同时,JavaScript也在发力,并逐渐取代前者,成为富客户端的最后赢家,那时候有本书非常火,叫做《JavaScript设计模式》。
JavaScript在2004年还就是用来做网页特效的。时至今日,我们发现,十几年的时间,JavaScript经历了ajax、jQuery、ECMAScript从1到6、Webpack打包,以及Angular、React、vue三大主流框架,已经变得无比强大,已经被封装成一门“面向对象”的语言了。
前面铺垫了那么多,就是想说明App也在走同样的发展道路,先沉淀几年,把网站的很多技术都搬到App上,也就是目前的发展情况,差不多该有的也都有了,下一个阶段就是从CS过渡到BS,Hybird技术就是上面说到的BS,但是有很多缺陷,尤其是WebBrowser性能很差,然后便出现了React Native,HTML 5很慢,但可以把HTML 5翻译成原生的代码啊。再往前发展是什么样我不知道,但是这个发展周期是很清晰的。Android和iOS技术不会消亡,这就像微软现在还在卖他们的桌面Office,还是要养一大票研发人员做这个桌面软件;另一方面HTML 5将慢慢成为App开发的主流。
文章摘录自《Android插件化开发指南》图书,作者:包建强