• Android 插件化技术应运而生出的 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

    动态加载技术:

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

    • 热修复:用于修复bug
    • 插件化:解决应用庞大,功能模块解耦,复用其他apk的代码

    插件化思想:

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

    插件化原理

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

    我们熟悉的ClassLoader有:

    • BootClassLoader:加载系统的类
    • PathClassLoader:加载已安装的apk类
    • DexClassLoader:自定义加载jar、dex的类
    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, ClassLoader parent) {
     super(dexPath, null, librarySearchPath, parent);
     }
    }
    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
     super(dexPath, null, librarySearchPath, parent);
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

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

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

    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;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    插件类加载的两种方案

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

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

    好处: 实现简单

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

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

    加载资源

    如何加载资源

    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());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    如何固定资源ID

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

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

    
    
    
    
    • 1
    • 2
    • 3
    • 4

    格式是 :

    
    
    • 1
    • 2

    今天有关于插件化开源框架的Apk动态加载技术的分析就到这里了,有需要文章中完整代码的朋友: 可以私信发送 “底层源码” 即可 免费获取;现在私信还可以获取一份 Android 开发学习笔记,里面汇总了我从事 Android 开发行业以来遇到的 技术难点问题

    Android 开发的道路还很长,望与大家一起共勉

  • 相关阅读:
    RabbitMQ-简述
    神经网络模型怎么建立,如何选择神经网络模型
    1050 String Subtraction
    第十三章 配置 Apache 以与 Web 网关配合使用 (Windows) - 推荐选项:Apache API 模块 (CSPa24.dll)
    如何设计高性能架构
    SpringCloudAlibaba实战-nacos集群部署
    Java实现加密(二)RSA加解密
    CSS 滚动驱动动画 scroll-timeline ( scroll-timeline-name ❤️ scroll-timeline-axis )
    流媒体集群应用与配置:如何在一台服务器部署多个EasyCVR?
    字符驱动开发2
  • 原文地址:https://blog.csdn.net/m0_62167422/article/details/126426534