Tencent Shadow—零反射全动态Android插件框架正式开源

Shadow是一个腾讯自主研发的Android插件框架,主要有以下特点:

  • Shadow所指的插件是插件的代码完全是一个正常可安装的App代码,无需引用任何Shadow的库。这样的App代码应用了Shadow之后可以免安装运行在另一个App中。
  • Shadow是一个完全无Hack,甚至零反射实现的Android插件框架。
  • Shadow是一个全动态实现的插件框架,就是说插件框架的代码跟插件的代码一样都是动态发布的。
  • Shadow是目前业内唯一的真正能开detectNonSdkApiUsage严格检查模式运行的插件框架。

Shadow主要解决了两个大问题

问题一:Android 9.0开始限制非公开SDK接口访问

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信息:

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接口。

Shadow所支持的功能类型是十分丰富的,Shadow支撑了QQ群视频、Now直播、腾讯即玩等业务比较复杂、访问量巨大的业务,并没有对这些业务的功能做出任何限制。功能细节欢迎持续关注Github上的更新。

问题二:插件框架不完善,其本身的代码需要更新、修复

这是一个在我们长期接触插件框架技术后就意识到的问题。也是我们发现所有已知的插件框架没有解决的问题。我们将它称为全动态插件框架。全动态指的就是除了插件代码之外,插件框架本身的所有逻辑代码也都是动态的。实际上插件框架的代码我们是和插件打包在一起发布的。

这个特性有多重要呢?实际上它比无Hack、零反射实现还要重要!因为有了这个特性之后,就算是我们用了Hack的方案,需要兼容各种手机厂商的系统。我们也不需要等宿主App更新才能解决问题。

实际上,Shadow的这个特性是更早实现的。我们早在2015年就开始大量使用插件技术了。在2016年就实现了这个特性。凭借这个特性不断的动态发布插件框架的代码,去适配各种兼容性问题。在今年更是应用这个特性,在完全不跟宿主版本的前提下,将原本的具有上百个反射Hack调用的旧实现更新为了Shadow无Hack实现。新的Shadow自然也具备这个特性。

这个特性对于新研发的Shadow来说也尤为重要,因为新研发的东西肯定有很多不完善的地方。如果是要打包在宿主里发布的话,那必然要测试的非常小心,还要写大量过设计以满足未来的插件需求。但是,现在Shadow是不需要这样的,Shadow只实现业务眼前所需的功能就可以发布了。

这个特性还有一个好处就是对宿主的增量特别的小。我们的宿主对于业务接入在增量上有极其苛刻的要求。Shadow接入时只使用了15.1KB,160个方法。

而我们已知的其他插件框架对宿主的增量一般在110KB,900个方法到370KB,2300个方法之间。

Shadow为解决所有插件框架问题铺平了道路

我们总结对插件框架发布的版本更新其实只有两种原因:

1. 业务需要新功能

2. 兼容不同的手机系统

Shadow的两大特性正好针对这两类问题,并且一次性的解决了87%的问题。

实现原理

其实实现原理属于一层窗户纸,一捅就破了。重要的在于思路上的转变。以前的插件框架总是想用一些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实践的是一个非常著名的理论:

任何软件工程遇到的问题都可以通过增加一个中间层来解决——佚名

具体到每个问题,细节上的原理各不相同,请关注Shadow相关的后续技术分享文章。如果想探究某个功能Shadow能否支持,可以fork项目,然后在Sample里自己写一个用例测试一下。Shadow的工程中的Sample可以直接安装运行,也可以直接以插件方式运行,用来对比插件实现和正常安装时行为是否一致非常方便。如果你的用例Shadow不支持,也欢迎把不支持的用例提一个Pull Request过来,我们可以探讨一下如何实现。

真诚期待开源贡献

Shadow开源的思路是将我们已经实现的功能,最有借鉴价值的代码,分享给大家。我们没有试图实现一个覆盖所有功能的SDK直接给大家用。因为我们自身业务没有在使用的功能,我们实现了也是不可靠的。反过来说,Shadow开源的代码绝大部分代码都是经过了亿级用户线上检验的,是可靠的。由于精力有限,我们的自动化测试用例也还比较少。这些都需要大家共同完善。

Tencent Shadow 正式开源

Github 开源地址: https://github.com/Tencent/Shadow

(点击文末阅读原文直接访问)

请给 Shadow 一个 Star !

欢迎提出你的 issue 和 PR!

Shadow国内镜像地址:

https://git.code.tencent.com/Tencent_Open_Source/Shadow

腾讯工蜂源码系统为开源开发者提供完整、最新的腾讯开源项目国内镜像

原文发布于微信公众号 - 腾讯开源(tencentopen)

原文发表时间:2019-06-19

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

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券