首页 / 知识
安卓插件化框架Shadow原理分析
2023-04-11 16:23:00

一、前言:
1.介绍:
Shadow是一个腾讯自主研发的Android插件框架,经过线上亿级用户量检验。 Shadow不仅开源分享了插件技术的关键代码,还完整的分享了上线部署所需要的所有设计。
与市面上其他插件框架相比,Shadow主要具有以下特点:
复用独立安装App的源码:插件App的源码原本就是可以正常安装运行的。
零反射无Hack实现插件技术:从理论上就已经确定无需对任何系统做兼容开发,更无任何隐藏API调用,和Google限制非公开SDK接口访问的策略完全不冲突。
全动态插件框架:一次性实现完美的插件框架很难,但Shadow将这些实现全部动态化起来,使插件框架的代码成为了插件的一部分。插件的迭代不再受宿主打包了旧版本插件框架所限制。
宿主增量极小:得益于全动态实现,真正合入宿主程序的代码量极小(15KB,160方法数左右)。
Kotlin实现:core.loader,core.transform核心代码完全用Kotlin实现,代码简洁易维护。
2.项目地址
项目地址:GitHub - Tencent/Shadow: 零反射全动态Android插件框架
二、原理分析:
Shadow号称无Hook点。核心原理是运用代理的方式,把原本的acitivty编译期间改成一个代理类,去代理宿主activity的所有生命周期。
2.1 传统的方案
既然这里提到了Shadow的特点是无hook,那么我们自然先简单的聊一下hook的方法是实现组件化。传统的方式,是通过hook了instrumetation或者classLoader,本来把本来启动HostActivity的任务,转换成了启动TargetActivity的任务,从而实现了插件当中TargetActivity的启动。
具体插件化的文章可以参看我的插件化系列课程:
https://blog.csdn.net/rzleilei/category_11590922.html?spm=1001.2014.3001.5482
hook有什么缺点吗?那自然是有的,否则Shadow也不会着重宣传其无hook的特性。hook最大的问题,就是风险性。随着安卓版本的更新, 之前hook的点很有可能会随着版本的变化而改变。比如下面通过反射Hook了Instrumetation。这样的代码在安卓12以下都是OK的,但是如果安卓14把mInstrumentation设置为了私有变量,那我们的整个插件化方案都会失效。
private fun checkInstrumentation(context: Context) {
if (state.isHookInstrumentation) {
return
}
state.isHookInstrumentation = true
val myInstrumentation =
MyInstrumentation()
//替换Acitivty中的mInstrumentation
val classLoader = javaClass.classLoader
val activityThreadClass = classLoader.loadClass("android.app.ActivityThread")
val activityThreadField =
activityThreadClass.getDeclaredField("sCurrentActivityThread")
activityThreadField.isAccessible = true
val activityThreadGet = activityThreadField.get(null)
val instrumentationField = activityThreadClass.getDeclaredField("mInstrumentation")
instrumentationField.isAccessible = true
instrumentationField.set(activityThreadGet, myInstrumentation)
}
2.2 Shadow中activity启动流程:
上面是我画了一下午整理出来的完整的启动流程图,基本上覆盖了所有的流程,下面就是对整个流程做一下解释。
1.首先click作为起点,我们点击按钮启动应用,这时候调用的是startPluginActivity的方法,启动就是启动我们在宿主中埋桩好的PluginDefaultProxyActivity,同时会带入一些必要信息,比如目标类的类名等等。
2.由于在mainfest中我们注册了PluginDefaultProxyActivity,所以AMS的检查会通过,然后通知Instrumetation进行对应的Activity的创建。
3.PluginDefaultProxyActivity创建时,会调用父类的构造函数。其父类是PluginContainerActivity。在PluginContainerActivity的构造方法中,会生成代理类ShadowActivityDelegate对象delegate,由这个代理类维护宿主和实现类的关系。
3.系统的Instrumetation创建后,会调用Activity的onCreate方法,由于PluginDefaultProxyActivity中没有任何实现,所以系统会调用其父类PluginContainerActivity的onCreate方法当中。
4.PluginContainerActivity的onCreate方法中,会通过之前创建的delegate对象,去创建targetActivity(根据传过来的类名等信息生成的),targetActivity就是我们的目标Activity,这里面包含了我们正常的业务逻辑。
5.创建好了对象之后,会调用targetActivity的onCreate方法。我们的目标Activity中,自然会有一些正常的使用逻辑。比如setContentView()等等。
6.这里就以setContentView为例。TargetActivity中调用setContentView方法,其实会调用到其父类ShadowActivity中的setContentView方法。
7.这里自然会有人会问了,我们正常的TargetActivity不是继承自Activity嘛,如果都把父类改成ShadowActivity岂不是很麻烦?这里Shadow用了字节码插桩的技术,就是在APK打包的时候,自动会帮我们做替换的。
8.在ShadowActivity的onCreate方法中,会通过代理类通知真正的宿主Activity的setContentView方法的。
|
最新内容
相关内容
python的单元测试框架
python的单元测试框架,代码,生态,信息,标准,测试,分析,工具,环境,条款,活跃,1、AutotestAutotest是Google、Redhat、IBM公司联合开发的分布式编写一个简单的游戏来学习 Python
编写一个简单的游戏来学习 Python,数字,标准,概念,名称,异常,软件,基础知识,培训,通用,流程,通过编写一个“猜数字”游戏来探索Python(和其他如何杀死一个Python线程
如何杀死一个Python线程,状态,线程,代码,标准,意外,工作,控制权,培训,事件,结束,我经常被问到如何杀死一个后台线程,这个问题的答案让很多人不用Python开发一个简单的猜数字游戏
用Python开发一个简单的猜数字游戏,数字,代码,培训,官网,设备,程序,玩家,注释,内容,游戏,本文介绍如何使用Python制作一个简单的猜数字游戏。Web开发人员推荐的Python框架
Web开发人员推荐的Python框架,系统,公司,灵活,时间,项目,软件,框架,认证,管理,异常,您可以在没有框架的情况下创建出色的应用程序。如果您正Python 里面的一些小技巧
Python 里面的一些小技巧,分析,代码,培训,字典,条件,表示,表达式,下面,里面,列表,三元条件判断的3种实现方法C语言中有三元条件表达式,如a>b?aPython 中如何实现参数化测试?
Python 中如何实现参数化测试?,密码,数据,测试,业务,情况,预期,培训,系统,参数,方法,之前,我曾转过一个单元测试框架系列的文章,里面介绍了unitPython模块化有哪些好处?
Python模块化有哪些好处?,管理,代码,软件,汽车,培训,复杂度,模块,功能,程序,好处,当然,如果不将程序分解成一个个独立的部分,而是整个一大坨,也Python继承性和 java 是一样的吗?
Python继承性和 java 是一样的吗?,情况,代码,名称,第一,培训,初始化,函数,方法,定义,变量,聊完了多态,再来看看面向对象的另一个特征:继承性。关于Python 的 Web 自动化测试的实
关于Python 的 Web 自动化测试的实践,代码,分析,环境,系统,软件,信息,测试,工作,较大,百度,Web测试是软件测试中比较的一个分支,而要实现Web自Python元类之通过元类实现数据库OR
Python元类之通过元类实现数据库ORM框架,数据,名称,信息,代码,主体,当中,字段,分析,一致,投入,ORM框架是什么如果是没有做过后端的小伙伴上来Python进程、线程、协程的区别
Python进程、线程、协程的区别,地址,单位,线程,入口,状态,培训,进程,区别,资源,空间,简述进程、线程、协程的区别以及应用场景?线程是指进程