安卓插件化原理

本文深入探讨了插件化技术的发展历史与原理,详细讲解了类加载机制、资源加载及固定资源ID的方法,并分析了插件依赖宿主资源的检测方式。

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

一、发展历史

插件化技术最初源于免安装运行apk的想法,这个免安装的apk可以理解为插件。支持插件化的app可以在运行时加载和运行插件,这样便可以将app中一些不常用的功能模块做成插件,一方面减小了安装包的大小,另一方面可以实现app功能的动态扩展。想要实现插件化,主要是解决下面三个问题:

插件中代码的加载和与主工程的互相调用

插件中资源的加载和与主工程的互相访问

四大组件生命周期的管理

二、插件化原理

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

我们熟悉的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);
    }
}

每一个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:让宿主的classloader去加载插件类

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

好处:实现简单

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

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

实现:见下方

2、加载资源

如何加载资源

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());

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

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

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

<public type="id" name="background" id="0x01020000" />
<public type="id" name="checkbox" id="0x01020001" />
<public type="id" name="content" id="0x01020002" />

格式是 :

<public type="资源类型" name="资源名" id="0x7f080000" />

如何检测插件依赖宿主的资源
原理:读取插件apk的dex、xml、resources.asrc中的id,判断如果是0x7开头则是宿主的资源

xml 直接读文件,判断是否匹配 0x7000000 (${prefix}(?:[0-9a-f]){6})

resources.asrc 直接读文件,判断是否匹配 0x7000000 (${prefix}(?:[0-9a-f]){6})

dex转成smali之后,判断 R$layout;-> R\$((?:drawable|dimen|bool|color|string|raw|integer|layout|mipmap|style|anim|attr);->.+):

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值