作者:猪猪侠
近期的大厂面试都比较喜欢问原理。
那么要想成为Android高级工程师,插件化很有必要学一学。
对于面试官为什么喜欢问插件化框架相关的问题?因为现在一般不会让你去开发插件化框架,但是工作中经常会需要维护对应的插件化框架,这个需要你懂相关原理;而且插件化涉及到的知识面非常的广,需要掌握反射、类加载、动态代理、泛型、资源加载、Hook技术、四大组件启动流程、AMS、PMS、AAPT等等,所以插件化也是面试官用来查看应聘者技术功底的工具。
对于App而言,所谓的插件化,个人的理解就是把一个完整的App拆分成宿主和插件两大部分,我们在宿主app运行时可以动态的载入或者替换插件的部分,插件不仅是对宿主功能的扩展而且还能减小宿主的负担,所谓的宿主就是运行的app,插件即宿主运行时加载的apk文件,这样宿主和插件结合的方案技术大概就是插件化了吧。
为什么要插件化?
- 解耦,独立各大模块的业务成为插件,互不干扰,即用即插,方便开发与维护。当业务庞大、繁琐之后,是否存在牵一发而动全身的感觉,是否存在逻辑过于复杂、耦合度较高、难以掌控整个项目。
- 加快编译。每次修改后无需重新编辑整个工程项目,可以单独编译某个插件工程,对于庞大的项目而言,速度就是至上的武功。
- 动态更新。无需重新下载与安装app,可以单独下载某个插件apk,直接加载,从动态更新、包体积和流量上感觉是个不错的选择。
- 模块定制。需要什么模块下载什么模块,无需让app变得庞大,所需所得。
插件化原理简单描述
关于插件化主要解决的大概就是类加载、资源加载、组件的加载这些核心问题了吧,所谓的原理也就是围绕这些问题进行的探讨。
Android的类加载
android中的类加载系统的ClassLoader可以大致划分为BaseDexClassLoader,SecureClassLoader。作为插件化我们只简单分析一下PathClassLoader与DexClassLoader,毕竟类加载的内容也很多,要写的东西也很多 ,先看下android类加载继承关系图:
PathClassLoader 提供一个简单的类加载器实现,该实现对本地文件系统中的文件和目录列表进行操作,但不尝试从网络加载类。Android将该类用于其系统类加载器和应用程序类加载器(简单讲可加载已安装的apk)。下面是官方的描述:
Provides a simple ClassLoader implementation that operates on a list of files and directories in the local file system, but does not attempt to load classes from the network. Android uses this class for its system class loader and for its application class loader(s).
DexClassLoader 它从.jar和.apk文件中加载包含类.dex条目的类。这可以用于执行未作为应用程序的一部分安装的代码(简单讲可加载未安装的apk,热修复与动态更新可能就是靠它了)在API级别26之前,这个类加载器需要一个应用程序专用的可写目录来缓存优化的类。使用Context.getCodeCacheDir()创建这样一个目录:以下为官方的描述:
A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.
Prior to API level 26, this class loader requires an application-private, writable directory to cache optimized classes. Use Context.getCodeCacheDir() to create such a directory:
以上关于android的类加载只是轻描淡写了一下,说了半天,关于插件化当然用到了DexClassLoader,我们来看一下DexClassLoader的实现吧。
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
英语不是很好,下面我简单翻译一下
dexPath: 字符串变量,包含类和资源的jar/apk文件列表,用File.pathSeparator分隔,在Android上默认为“:”。
optimizedDirectory:不推荐使用此参数,貌似是一个废弃的参数,据说是.dex文件的解压路径,自API级别26起不再生效,那么26之前是怎么用的呢,查了一下是通过 context.getCodeCacheDir()。
librarySearchPath: 包含native库的目录列表,C/C++库存放的路径,用File.pathSeparator分隔;可能为null。
parent: 父类加载器ClassLoader.
再看一下调用的父类BaseDexClassLoader的构造方法及核心方法
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class "" + name + "" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
@Override
protected URL findResource(String name) {
return pathList.findResource(name);
}
@Override
protected Enumeration<URL> findResources(String name) {
return pathList.findResources(name);
}
@Override
public String findLibrary(String name) {
return pathList.findLibrary(name);
}
}
显然看出DexPathList这个成员对象的重要性,初始化构造方法的时候实例化DexPathList对象,同时,BaseDexClassLoader重写了父类findCla