您的位置 首页 java

「插件化技术」Apk动态加载技术的开源框架

前言

我们在平时的开发过程中,会经常遇到产品需求的变更或者出现bug; 在传统的模式中,我们需要首先需要修改代码,然后重新打包 apk 再上线,用户在打开应用的时候就会进行更新了

但是这种模式有几个缺点:

一是上线周期长,从修改代码到用户更新需要较长的时间,二是用户更新代价较大,每次用户更新都需要下载整个Apk包; 整个 Apk 包包括了一个应用的所有代码,要消耗用户较多的流量,并且,如果是一些重要的更新,为了确保用户都能更新到,还需要用到强制更新,即用户打开App后如果不更新应用则退出应用,这种对用户来说是极其不友好的

还有另外一种情况: 某些较大的App功能很多,比如 支付宝 微信 等,如果将这些功能全部塞到一个Apk中,那将会是一个巨型Apk,用户在安装或者更新Apk时将会经过漫长的等待时间

基于以上两点,Android的 插件化技术 应运而生; 插件化技术即将Apk按照功能模块划分,不同的功能打包成不同的Apk,然后应用的主Apk按需加载对应功能的Apk,用户只需要安装应用的主Apk即可,主Apk相当于一个壳,它会按需加载其他功能模块的Apk

通过这种模式,不仅解决了巨型Apk的问题,而且当某个功能模块需要变化时,也只需要修改对应功能的代码,打包功能Apk并更新即可,这样不仅可以让用户及时更新,而且更新的代价也很小

但是,我们知道, 在Android中,没有安装的apk是不能直接运行的,那么要想实现插件化,我们就必须能够让主Apk能够加载功能Apk并运行

插件化的开源框架

插件化发展到现在,已经出现了非常多的框架,下图列出部分框架:

我们在选择开源框架的时候,需要根据自身的需求来; 如果加载的插件不需要和宿主有任何 耦合 ,也无须和宿主进 行通信,比如加载第三方 App,那么推荐使用 RePlugin,其他的情况推荐使用 VirtualApk

动态加载技术:

在程序运行时,动态加载一些程序中原本不存在的 可执行文件 并运行起来; 随着应用技术的发展,动态加载技术逐渐派生出两个分支:热修复和插件化

插件化思想:

将复用的apk作为插件,插入另一个apk中,比如淘宝中会有咸鱼的页面,用淘宝为咸鱼引流; 使用插件化技术,可以直接使用咸鱼apk中的dex文件,这样省去再次开发一套咸鱼页面的成本,并且有效的降低了淘宝apk的耦合度

插件化原理

类加载机制和插件加载方法

我们熟悉的ClassLoader有:

BootClassLoader:加载系统的类

PathClassLoader:加载已安装的apk类

DexClassLoader:自定义加载jar、dex的类

 <pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n91" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px;  padding : 8px 4px 6px; margin-bottom: 15px;  margin -top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;  text-decoration -style: initial; text-decoration-color: initial;">public class PathClassLoader  extends  BaseDexClassLoader {
 public PathClassLoader(String dexPath, ClassLoader parent) {
 super(dexPath, null, null, parent);
 }

 public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
 super(dexPath, null, librarySearchPath, parent);
 }
}

// DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader {
 public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath,  Class Loader parent) {
 super(dexPath, null, librarySearchPath, parent);
 }
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
 super(dexPath, null, librarySearchPath, parent);
}</pre>  

每一个ClassLoader有一个父ClassLoader(组合关系),尝试加载一个类时,会先让父亲去加载

loadClass方法实现了双亲委托机制: 父Classloader无法加载类时,再调用自己的findClass方法尝试加载类

 <pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n96" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { 
 //首先从已经加载的类中查找
 Class<?> clazz = findLoadedClass(className);
 if (clazz == null) {
 ClassNotFoundException suppressed = null; 
 try { 
 //如果没有加载过,先调用父加载器的loadClass
 clazz = parent.loadClass(className, false);
 } catch (ClassNotFoundException e) {
 suppressed = e;
 }
 if (clazz == null) {
 try { 

 //父加载器都没有加载,则尝试加载
 clazz = findClass(className);
 } catch (ClassNotFoundException e) {
 e.addSuppressed(suppressed); 
 throw e;
 }
 }
 }
 return clazz;
 }
</pre>  

插件类加载的两种方案

方案1:让宿主的classloader去加载插件类

原理: classloader.findClass方法是从一个pathList中加载类,我们把插件的路径添加到这个List中

好处: 实现简单

方案2 : 构建插件单独的ClassLoader

原理: 每个插件创建一个单独的classloader,Hook系统classloader,更改loadClass逻辑:先尝试从宿主classloader中加载类,再尝试从每个插件中加载类

加载资源

如何加载资源

 <pre mdtype="fences" cid="n104" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">Android系统通过Resource对象加载资源,因此只需要添加资源(即apk文件)所在路径到AssetManager中,即可实现对插件资源的访问。
// 创建AssetManager对象
AssetManager assetManager = new AssetManager();
// 将apk路径添加到AssetManager中
if (assetManager.addAssetPath(apkPath) == 0) {
 return null;
}
// 创建插件Resource对象
Resources pluginResources = new Resources(assetManager, metrics, getConfiguration());
</pre>  

由于AssetManager的 构造方法 时hide的,需要通过反射区创建

如何固定资源ID

原理: android在资源编译过程中预留了固定id的方法,用于对诸如对外发布的jar包组件中引用的资源做固定处理以保障版本对 jar 包的兼容性。

方法: 将需要固定的资源以及id写在一个public.xml中,放置于res/values/public.xml中,这样在编译时相关的资源id就固定为xml中定义的id,可以支持几乎所有的R文件资源类型,定义如下: 部分代码如下:

 <pre mdtype="fences" cid="n107" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><public type="id" name="background" id="0x01020000" />
<public type="id" name="checkbox" id="0x01020001" />
<public type="id" name="content" id="0x01020002" />
</pre>  

格式是 :

 <pre mdtype="fences" cid="n108" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><public type="资源类型" name="资源名" id="0x7f080000" />
</pre>  

尾述

私信发送 “底层源码” 即可获取 完整代码 以及 更多Android学习笔记+源码解析+面试视频

技术是无止境的,你需要对自己提交的每一行代码、使用的每一个工具负责,不断挖掘其底层原理,才能使自己的技术升华到更高的层面

Android 架构师之路还很漫长,与君共勉

PS:有问题欢迎指正,可以在评论区留下你的建议和感受;

欢迎大家点赞评论,觉得内容可以的话,可以转发分享一下

文章来源:智云一二三科技

文章标题:「插件化技术」Apk动态加载技术的开源框架

文章地址:https://www.zhihuclub.com/183777.shtml

关于作者: 智云科技

热门文章

网站地图