Android插件化技术即将Apk,你值得拥有!(上)

前言

在我们平时的开发过程中,经常会遇到产品需求的变化或者bug;在传统模式下,我们需要先修改代码,然后重新打包Apk然后上线。当用户打开应用程序时,将更新

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

首先,上线周期长,从代码修改到用户更新需要很长时间。二是用户更新成本高,每次用户更新都需要下载整个Apk包;整个Apk包包含了一个应用的所有代码,会消耗大量的用户流量,如果是一些重要的更新,为了保证用户能更新到它,就必须使用强制更新,即即用户打开应用后,如果不更新应用,则退出应用。非常不友好

还有一种情况:一些比较大的app有很多功能,比如支付宝、微信等,如果把这些功能都打包到一个Apk里面,那就是一个巨Apk,用户在安装或者更新Apk。等待时间会很长

基于以上两点,Android的插件技术应运而生;插件技术就是将Apk按照功能模块进行划分,将不同的功能打包成不同的Apk,然后应用的主Apk按需加载对应功能的Apk。您只需要安装应用程序的主 Apk。主Apk相当于一个shell游戏apk修改,它会按需加载其他功能模块的Apk。

这种模式不仅解决了巨Apk的问题,而且当某个功能模块需要更改时,只需要修改相应功能的代码,打包功能Apk并更新即可游戏apk修改,不仅可以用户及时更新,更新成本非常小

但是我们知道在Android中,没有安装的apk是不能直接运行的,所以为了实现插件化,我们必须要能够使主Apk能够加载功能Apk并运行

插件开源框架

随着插件的发展,出现了很多框架。下图列出了一些框架:

我们在选择开源框架时,需要遵循自己的需求;如果加载的插件不需要与主机耦合或与主机通信,如加载第三方App,建议使用RePlugin,其他情况下建议使用VirtualApk

动态加载技术:

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

Hotfix:用于修复错误

插件:解决应用庞大、功能模块解耦、其他apk代码复用问题

插件思路:

将重复使用的apk作为插件,插入到另一个apk中。比如淘宝会有咸鱼页面,用淘宝把咸鱼排干;使用插件技术,可以直接使用咸鱼apk中的dex文件,节省了重新开发一套咸鱼页面的成本,有效降低了淘宝apk的耦合度

插件原理类加载机制及插件加载方式

我们熟悉的类加载器有:

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

每个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;
 }

插件类加载的两个选项

方案一:让宿主机的类加载器加载插件类

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

优点:易于实施

选项 2:为插件构建单独的 ClassLoader

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

如何加载资源如何加载资源

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的构造方法是隐藏的,所以需要通过反射区来创建

如何修复资源 ID

原理:Android在资源编译过程中预留了一个固定id方法,用于固定外部发布的jar包组件中引用的资源等,保证版本与jar包的兼容性。

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



格式为:



结束

私信“底层源码”即可获取完整代码及更多Android学习笔记+源码分析+访谈视频

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

Android架构师的路还很长,分享给大家

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