Android之ClassLoader类加载器(MultiDex、动态加载dex)

xiaoxiao2021-02-28  68

android中的类加载

先看个简单的打印日志,直接将代码贴到Activity#onCreate()中即可:

protected void onCreate(Bundle savedInstanceState) { //...其他省略 Log.e(TAG, "classLoader -> " + getClassLoader()); String classPath = System.getProperty("java.class.path", "."); String librarySearchPath = System.getProperty("java.library.path", ""); Log.e(TAG, "classPath -> " + classPath); Log.e(TAG, "librarySearchPath -> " + librarySearchPath); } //打印日志 07-11 17:42:39.299 3874-3874/com.tv189.dl E/MainActivity: classLoader -> dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.tv189.dl-1/base.apk"],nativeLibraryDirectories=[/data/app/com.tv189.dl-1/lib/arm64, /vendor/lib64, /system/lib64]]] 07-11 17:42:39.299 3874-3874/com.tv189.dl E/MainActivity: classPath -> . 07-11 17:42:39.299 3874-3874/com.tv189.dl E/MainActivity: librarySearchPath -> /vendor/lib64:/system/lib64

说明: dalvik.system.PathClassLoader 为android虚拟机类加载器 System.getProperty("java.class.path", ".")查看当前虚拟机中加载类的路径 System.getProperty("java.library.path", "") 查看当前虚拟机中加载的动态链接库的路径 显然这里android-VM中是通过PathClassLoader来加载类的。 android中类加载器的类图如下:

涉及的类

PathClassLoader.java PathClassLoader作用于本地文件系统中的文件列表和目录,而不能从网络加载classes。

DexClassLoader.java 从包含classes.dex的jar/apk文件中加载class,该类可以加载未打包到apk中的代码。所以可以用来动态加载dex等文件中的classes。 该加载器需要传入一个应用私有的可写入的目录来缓存优化过的dex,如File dexOutputDir = context.getDir("dex", 0); 。

BaseDexClassLoader.java 在android中使用该类加载系统classes和app中的classes。可以加载的文件类型为包含classes的jar/apk文件,也支持多个文件同时加载,使用File.pathSeparator 把文件路径连接即可并且android中默认为冒号: ,如path/to/a.jar:path/to/b.apk。dex文件实际被加在到private DexPathList pathList,而findClass(name) 也是从pathList 中来寻找已被加载的class的。

@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; } DexPathList.java 主要是类加载器把dex/resource保存在private final Element[] dexElements; 数组中的,每个Element 对应一个dex文件; 而BaseDexClassLoader#findClass 加载类时会从dexElements 中遍历获得,如果找到了Class就终止遍历。 注意这一段说明 /** * A pair of lists of entries, associated with a {@code ClassLoader}. * One of the lists is a dex/resource path — typically referred * to as a "class path" — list, and the other names directories * containing native code libraries. Class path entries may be any of: * a {@code .jar} or {@code .zip} file containing an optional * top-level {@code classes.dex} file as well as arbitrary resources, * or a plain {@code .dex} file (with no possibility of associated * resources). * * <p>This class also contains methods to use these lists to look up * classes and resources.</p> */ /*package*/ final class DexPathList { private static final String DEX_SUFFIX = ".dex"; /** class definition context */ private final ClassLoader definingContext; /** * List of dex/resource (class path) elements. * Should be called pathElements, but the Facebook app uses reflection * to modify 'dexElements' (http://b/7726934). */ private final Element[] dexElements; /** List of native library directories. */ private final File[] nativeLibraryDirectories;

解决android中方法数超出问题

由于android系统的bug,一个dex文件中方法数最大值为65536;所以随着应用代码的膨胀必然会导致程序dex中的方法数超过最大值的,要解决这个问题的方案就是把dex进行切分为多个dex,但是android5.0之前dalvik虚拟机系统只加载一个dex,从android5.0开始系统使用art虚拟机已支持加载多个dex文件;google官方也出了补丁(使用multidex加载多个dex)来修复这个bug;所以如果app要适配android5.0以下的系统程序中就要使用MultiDex方案,这个补丁支持api4 ~ api20(android5.0以下)。

涉及的类

主要类在 android.support.multidex包中

MultiDex.java 加载多个dex,主要过程:

删除旧的secondarydex缓存 clearOldDexDir;尝试加载已存在的secondarydexes,失败则从新解压apk然后加载secondarydex;

MultiDexExtractor .java 解压apk,并从中提取dexes

动态加载dex

参考QQ空间热修复方案,在app启动时把补丁dex加载到Element[] 数组的前端(参见classloader类图);

测试时可以先修改某个类,然后把这个修改过的类转为jar,再把jar转为dex; 再把修改的内容改回去; 把下面代码放入项目中去加载dex;

import android.content.Context; import java.lang.reflect.Array; import java.lang.reflect.Field; import dalvik.system.BaseDexClassLoader; import dalvik.system.DexClassLoader; /** * 加载补丁dex文件,并插入系统dex数组中的第一个位置 * <p> * Created by June on 2017/7/14. */ public class PatchDexLoader { private Context mContext; public PatchDexLoader(Context context) { this.mContext = context; } public void load(String path) { try { // 已加载的dex Object dexPathList = getField(BaseDexClassLoader.class, "pathList", mContext.getClassLoader()); Object dexElements = getField(dexPathList.getClass(), "dexElements", dexPathList); // patchdex String dexOptDir = mContext.getDir("patchDex_optDir", 0).getAbsolutePath(); DexClassLoader dcl = new DexClassLoader(path, dexOptDir, null, mContext.getClassLoader()); Object patchDexPathList = getField(BaseDexClassLoader.class, "pathList", dcl); Object patchDexElements = getField(patchDexPathList.getClass(), "dexElements", patchDexPathList); // 将patchdex和已加载的dexes数组拼接连接 Object concatDexElements = concatArray(patchDexElements, dexElements); // 重新给dexPathList#dexElements赋值 setField(dexPathList.getClass(), "dexElements", dexPathList, concatDexElements); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } /** * @param cls 被访问对象的class * @param fieldName 对象的成员变量名 * @param object 被访问对象 * @return * @throws NoSuchFieldException * @throws IllegalAccessException */ public Object getField(Class<?> cls, String fieldName, Object object) throws NoSuchFieldException, IllegalAccessException { Field field = cls.getDeclaredField(fieldName); field.setAccessible(true); return field.get(object); } /** * @param cls 被访问对象的class * @param fieldName 对象的成员变量名 * @param object 被访问对象 * @param value 赋值给成员变量 * @throws NoSuchFieldException * @throws IllegalAccessException */ public void setField(Class<?> cls, String fieldName, Object object, Object value) throws NoSuchFieldException, IllegalAccessException { Field field = cls.getDeclaredField(fieldName); field.setAccessible(true); field.set(object, value); } /** * 连接两个数组(指定位置) * * @param left 连接后在新数组的左侧 * @param right 连接后在新数组的右侧 * @return */ public Object concatArray(Object left, Object right) { int len1 = Array.getLength(left); int len2 = Array.getLength(right); int totalLen = len1 + len2; Object concatArray = Array.newInstance(left.getClass().getComponentType(), totalLen); for(int i = 0; i < len1; i++) { Array.set(concatArray, i, Array.get(left, i)); } for(int j = 0; j < len2; j++) { Array.set(concatArray, len1 + j, Array.get(right, j)); } return concatArray; } }

refrence

dalvik.systemandroid.support.multidexQQ空间热修复
转载请注明原文地址: https://www.6miu.com/read-72521.html

最新回复(0)