Android 插件框架实现思路及原理

本文探讨了插件框架的技术可行性与实现要点,包括apk安装处理流程、资源读取机制及插件逻辑加载方式。同时,深入介绍了AssertManager、Resource和LayoutInflater等关键组件的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

插件框架实现思路及原理

一、技术可行性

a) apk的安装处理流程

i. apkcopy/data/app

ii. 解压apk中的class.dex,并对其进行优化,获得odex(即JIT)。最后保存到/data/dalvik_cache

iii. 还有一些权限和包信息,会缓存到/data/system中的packages.listpackages.xml中。


b) 在android上,对apk包的加载逻辑

i. 加载逻辑

 Zygote(孵化器)在成功启动一Android进程后,会根据packages.list的内容(启动时会加载到system_process中的pakcagemanager中),把odex文件,加载到dalvik中,完成逻辑的加载;


ii. 资源读取

资源读取,主要有两三个类,分别是ResourceAssertManagerLayoutInflater

当在显示界面时,就通过这三个类读取资源。


c) 结论和猜想

i. apk相对于整个android系统而言,其本身就是一种插件形式体现。根据上面关于逻辑和资源的读取概述,完全是可以静默实现的。其次,class.dex并没有包含Android SDK的代码,只是保留对Android SDK接口的调用。 可以这样想象,Android SDK即插件框架,而Android OS即为整个插件的宿主环境。因此这就可以解释了,为什么在1.x编译的代码,在2.x甚至3.x都可以运行,因为只要插件宿主的接口(Android SDK)不变,插件运行时所调用的接口都可以被找到。


ii. 为了减少内存占用,ResourceAssertManagerLayoutInflater必然不会把apk中的所有资源都加载进来,而是用时才加载并缓存,而且还有一些的处理机制(如最不常用清除等)。因此这些类当中,必然存在一个指明资源路径的字段或者结构。


iii. 要保证兼容性,插件框架公开给插件的接口,必须遵守Open-Close(开发-封闭)原则。另外,一些已经废弃掉接口,同样需要保留。比如Service中的setForegroundJDK的中关于Thread的一些接口等。


iv. 可以尝试通过反射,修改ResourceAssertManagerLayoutInflater中指明资源路径的字段;另外,还可以查看源码,查找设置资源路径的方法。



二、技术实现要点

a) 逻辑加载

i. 针对接口编程。这个是所有插件框架的基本设计模型。

ii. 通过DexClassLoader加载插件所实现的插件接口,详细可参考PluginManagerImpl中的parserPlugin方法实现,关键代码如下:




b) AssertManager的实现

经查阅Android源码,发现AssertManager的实例生成,用到两个隐藏的方法,如下所示:






通过以上代码,我们就可以得到我们插件的AssertManager了,关键代码如下所示:




c) Resource的实现

有了插件专用的AssertManager,那么插件的Resource也轻易得到了,关键代码如下所示:



d) LayoutInflater的实现

一般我们要获取LayoutInflater,都必须通过Context来获得,即是说LayoutInflater的资源读取,都是通过ContextgetResoure以及getAssert读取,是直接跟宿主挂勾的。这里有两个方法可选选择:

其一,自己重写Context类,并把getResouregetAssert的返回值改为上面所得的插件资源相关的实例,即包装法;

其二,考虑到平时我们用LayoutInflater时,主要是用来加载布局文件(XML),因此可以投机取巧点,只针对inflater进行修改。

目前框架采用的是第二种方法。先看看LayoutInflater的源码实现,如下所示:





因此,只调用插件的Resources,并调用其第二个方法,并可实现加载插件的布局问题,为了方便使用,定义了一个ILayoutInflater接口,封装实现细节,关键代码如下:




事实上,通过以上的方法,还是无法完成插件XML布局文件的加载,通过跟踪源码,会发生View的生成,还需要因为利用到当前Context(Activity)的一个类型Theme的实例。跟踪过程如下:

l View(Context context, AttributeSet arrts, int defStyle)

l Context.obtainStyledAttributes(AttrobiteSet arrts, int[]  attrs, int defStyleAttr, int defStyleRes)

l Context.getTheme()

l ......

而这个Theme类型,是Resource的一个内部类,不单可以直接引用Resource的,还通过Context保存AssertManager的引用。源码如下:




因此,我们还需要通过反射的方式,把当前Theme的实例,替换成我们插件的。当XML布局文件解释成功后,再恢复过来。留意上面代码中的beginend方法,就是这个过程的封装。关键代码如下:





而插件的Theme实例,可通过ResourcenewTheme获得,关键代码如下:




e) 混合资源解析的实现

基本上,上面的有了上面的三个类,就可以完全加载插件的读取插件的资源和逻辑。但往往事情并不是这么简单。

考虑到UI的可重用性,插件里,往往会很多的用到宿主所提示的UI库接口。因此,就需要考虑,当在解释插件的xml布局时,如何混合使用两方的资源。从上面的过程可得,无论是ResourcesAssertManager还是LayoutInflater,都是同时只能针对一方资源。当在插件的布局文件中,使用了两方的文件,必然会因为找不到资源,解释出错。

不过,LayoutInflater,提示了一个setFactory,可以在解释XML布局文件时,优先解释。关键代码如下:







三、待完善的地方

l 在宿主中定义的资源,目前除了自定义view之外,都不可以使用。比如文字、颜色值、样式等。

l 插件的权限,必须是宿主的子集。

l 插件中包含so的加载逻辑,还没有实现。

l 目前插件的加载,都是加载dex,而不是odex。还需要查阅源码,手工实现这个优化过程。



原文地址: https://i-blog.csdnimg.cn/blog_migrate/15d785cbd89d6ff271ec75c8997dfdb8.png

### Android 插件框架概述 Android插件化是一种开发模式,允许动态加载和卸载APK,实现模块化开发、热更新等功能[^1]。此技术依赖于类加载机制与双亲委托模型来完成不同APK间的资源管理和隔离。 #### 类加载器与双亲委派机制 在Java虚拟机中,每当启动一个新的应用程序实例时都会创建一个`AppClassLoader`用于加载该程序所需的字节码文件(.class)。而在Android平台下,则是由`PathClassLoader`或`DexClassLoader`负责加载来自`.dex`格式的字节码数据。对于插件化的实现来说,通常会采用后者——即`DexClassLoader`来进行自定义路径下的DEX文件加载操作[^3]。 当宿主应用尝试访问某个未被预先打包进自身内部的Class对象时(比如插件中的Activity),就需要依靠上述提到过的两种方式之一去获取对应的.class/.dex文件,并将其注入到当前运行环境中以便后续调用。这里涉及到的核心概念便是所谓的“双亲委派原则”,简单来讲就是子类加载器在接受到来自父级传递下来的请求之后再决定是否继续向下一层转发直至找到匹配项为止;反之亦然,在遇到未知类型的请求时也会逐层向上汇报给更高级别的加载单元处理直到最顶层为止。 #### 主流开源插件框架分析 - **AndFix** AndFix是一个轻量级的修复工具库,主要用来解决线上版本中存在的Bug问题而不必重新发布整个APP包体。它的工作原理是通过修改目标方法表(Method Area),使得旧版的方法指向新的修正后的实现部分。不过需要注意的是由于其作用范围有限制所以并不能完全替代传统意义上的插件架构设计思路[^2]。 - **DynamicApk** DynamicApk实现了较为完整的插件生命周期管理流程,包括但不限于安装、激活、销毁等环节的操作接口封装。除此之外还提供了诸如权限控制、广播接收者注册等一系列辅助特性支持以满足实际业务场景需求。更重要的一点在于该项目充分考虑到了安全性方面的要求因此具备一定的抗风险能力[^4]。 - **Qigsaw** Qigsaw作为阿里团队推出的一款高性能分发引擎产品不仅兼容性强而且性能优越。特别是在面对大规模并发下载任务时表现尤为突出。另外值得一提的是借助Split APKs这一特性还可以有效减少初次安装体积大小进而提升用户体验度。具体而言则是利用了Android P及以上版本新增加的功能特性来达到按需加载特定功能模块的目的。 ```java // 使用 DexClassLoader 加载外部 DEX 文件的例子 String dexPath = "/path/to/plugin.apk"; File optimizedDir = getDir("out", Context.MODE_PRIVATE); DexClassLoader classLoader = new DexClassLoader(dexPath, optimizedDir.getAbsolutePath(), null, getClass().getClassLoader()); try { Class<?> pluginClass = classLoader.loadClass("com.example.Plugin"); } catch (ClassNotFoundException e) { Log.e("PluginLoadError", "Failed to load plugin class.", e); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值