最近几年,腾讯对于开源事业也是愈来愈支持,今天要说的就是在腾讯被普遍使用的Shadow框架,一个通过线上亿级用户量检验的反射全动态Android插件框架。 首先,让咱们来看一下官方对于Shadow的简介:android
Shadow是一个腾讯自主研发的Android插件框架,通过线上亿级用户量检验。 Shadow不只开源分享了插件技术的关键代码,还完整的分享了上线部署所须要的全部设计。git
与市面上其余插件框架相比,Shadow主要具备如下特色:github
除此以外, Shadow支持的特性有:bash
众所周知,Android 9.0出现限制非公开SDK接口访问以后,能够说当时咱们已知的全部插件框架实现都或多或少的出现了适配问题。你们的应对方式基本上都是一种对抗的思想,有的去破解限制,有的经过和Google沟通浅灰名单有效期暂时续命。框架
遇到了这个问题,咱们没有选择和这个策略进行对抗,咱们很是理解Google为何限制使用非公开SDK接口。因此咱们从新Review了插件框架的本质原理和设计缺陷,进而设计了全新的插件框架Shadow。Shadow没有使用任何非公开SDK接口,实现了和本来在用的使用了大量非公开SDK接口的实现同样的功能。工具
在Shadow的Sample中,能够添加以下所示的代码来开启严格检查模式运行,而其余插件框架并不能作到。测试
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
builder.penaltyDeath();
builder.detectNonSdkApiUsage();
StrictMode.setVmPolicy(builder.build());
复制代码
好比,咱们看到的一款也宣传未使用非公开SDK接口,支持Android 9.0的插件框架,在它的Sample中开启严格模式运行后,出现了以下Crash信息:gradle
W/.xxx.sampl: Accessing hidden method Landroid/view/View;->computeFitSystemWindows(Landroid/graphics/Rect;Landroid/graphics/Rect;)Z (light greylist, reflection)
W/System.err: StrictMode VmPolicy violation with POLICY_DEATH; shutting down.
复制代码
可见,即便它的实现代码中没有出现任何非公开SDK的引用,实际上它依赖的第三方组件内部也使用了非公开SDK接口。ui
所谓全动态,指的就是除了插件代码以外,插件框架自己的全部逻辑代码也都是动态的。而且,Shadow框架实际上也作到了这一点,即插件框架的代码咱们是和插件打包在一块儿发布的。spa
全动态化插件框架有多重要呢?其实它比无Hack、零反射实现还要重要!由于有了这个特性以后,就算是咱们用了Hack的方案,须要兼容各类手机厂商的系统。咱们也不须要等宿主App更新才能解决问题。
实际上,Shadow的这个特性是更早实现的。咱们早在2015年就开始大量使用插件技术了。在2016年就实现了这个特性。凭借这个特性不断的动态发布插件框架的代码,去适配各类兼容性问题。在今年更是应用这个特性,在彻底不跟宿主版本的前提下,将本来的具备上百个反射Hack调用的旧实现更新为了Shadow无Hack实现。新的Shadow天然也具有这个特性。
在实际使用过程当中,咱们的宿主对于业务接入在增量上有极其苛刻的要求。Shadow接入时只使用了15.1KB,160个方法。而咱们已知的其余插件框架对宿主的增量通常在110KB,900个方法到370KB,2300个方法之间。
之前的插件框架老是想用一些Hack手段去修改系统行为,找到系统的漏洞达到目的。Shadow的原则是不去跟系统对抗。既然只是限制非公开SDK接口访问,而没有限制动态加载代码。那么确定有办法在不使用非公开SDK接口的前提下实现原来的目的。由于咱们插件技术的目的本质上来讲仍是动态加载代码。
那么一个重要的原则就是,若是一个组件须要安装才能使用,那么就别在没安装的状况下把它交给系统。咱们已知的插件框架中,作的最好的也不符合这个原则,因此尽管它的Hook点少,但就是因为它将没有安装的Activity交给系统了,因此后面就不得不作一些Hack的事修补。
因此套一个壳子的方案就很是好。这种思路其余框架很早就有了,可是它们一直想把一个插件Activity套在一个宿主Activity之中,而后想办法实现一个转调关系。若是插件Activity是一个真的Activity,那这个插件就能够正常编译安装运行,对开发插件或者直接上架插件App很是有利。可是因为它是个系统的Activity子类,它就有不少方法不能直接调用,甚至还可能须要避免它的super方法被调用。若是插件Activity不是一个真的Activity,只是一个跟Activity有差很少方法的普通类,这件事就简单多了,只须要让壳子Activity持有它,转调它就好了。但这种插件的代码正常编译成独立App安装运行会比较麻烦,代码中可能会出现不少插件相关的if-else,也很差。
Shadow作了一个很是简单事,经过运用AOP思想,利用字节码编辑工具,在编译期把插件中的全部Activity的父类都改为一个普通类,而后让壳子持有这个普通类型的父类去转调它就不用Hack任何系统实现了。虽说是很是简单的事,实际上这样修改后还带来一些额外的问题须要解决,好比getActivity()方法返回的也不是Activity了。不过Shadow的实现中都解决了这些问题。
Shadow框架的原理示意图以下:
第一次clone Shadow的代码到本地后,建议先在命令行编译一次。
在命令行测试编译时能够执行以下编译任务:
./gradlew build
复制代码
若是没有出错,再尝试用Android Studio打开工程。
而后就能够在IDE中选择sample-host模块直接运行便可,以下: