1. 類加載機制
1.1 類加載UML圖
1.2 loadClass流程
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
if (parent != null) {
//使用父ClassLoader加載
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
首先先判斷是否已經(jīng)加載過這個類
如果沒有加載過就讓父加載器來加載
-
如果父加載器沒有加載到才使用當(dāng)前的ClasLoader來加載----雙親委派機制
- 避免多次加載
- 避免系統(tǒng)class被自定義的ClasLoader篡改
-
findClass方法由ClassLoader的子類實現(xiàn)灵寺,主要由
BaseDexClassLoader
類實現(xiàn)@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; }
在
BaseDexClassLoader
類中包含一個DexPathList
對象在這個
pathList
中查找對應(yīng)的加載類
DexPathList
中通過一個Element
數(shù)組保存加載過的類唤崭,然后遍歷這個數(shù)組當(dāng)找到從找到一個和當(dāng)前類名匹配的Class
對象時就停止遍歷拓春,返回這個Class
。public Class<?> findClass(String name, List<Throwable> suppressed) { for (Element element : dexElements) { Class<?> clazz = element.findClass(name, definingContext, suppressed); if (clazz != null) { return clazz; } } if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null; }
2. 實現(xiàn)原理
主要流程
- 將新的dex文件轉(zhuǎn)換成Element數(shù)組
- 獲取新生成的Element數(shù)組中的所有對象
- 獲取舊的Element數(shù)組中的對象
- 將新的對象插入到舊的對象之前,在遍歷時就能獲取到最新的class文件
- 把組合的Element數(shù)組設(shè)置到DexPathList對象中
2.1 從類加載器中獲取DexPathList
對象
Class<?> cl = null;
Field pathListFiled = null;
for (cl = classLoader.getClass(); cl != null; cl = cl.getSuperclass()) {
try {
//從BaseDexClassLoader中找pathList屬性
pathListFiled = cl.getDeclaredField("pathList");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
if (pathListFiled != null) {
pathListFiled.setAccessible(true);
break;
}
}
因為只有BaseDexClassCloader
中才持有這個對象夫植,因此需要對當(dāng)前ClassLoader的父類進行遍歷,通過反射拿到DexPathList
屬性對象
2.2 獲取makePathElements方法
通過反射從DexPathList
對象中獲取makePathElements
這個方法,通過這個方法把傳入的Dex文件轉(zhuǎn)換成Element數(shù)組乒融。
Object pathList = pathListFiled.get(classLoader);
Class<?> pathListClass = pathList.getClass();
Method makePathListMethod = pathListClass.getDeclaredMethod("makePathElements",
List.class, File.class, List.class);
makePathListMethod.setAccessible(true);
//要插入的新的文件集合
List<File> dexFile = new ArrayList<>();
File f = new File(file);
dexFile.add(f);
List<IOException> suppressedException = new ArrayList<>();
//獲得新的dexElements數(shù)組
Object[] elements = (Object[]) makePathListMethod.invoke(pathList, dexFile,new File(optPath),
suppressedException);
Log.d("sxhLog", "fix: " + elements.length);
2.3 獲取PathList中原有的Element數(shù)組
直接通過反射拿到PathList
對象中的dexElement
對象。
Field oldElementsFiled = pathListClass.getDeclaredField("dexElements");
oldElementsFiled.setAccessible(true);
Object[] oldElements = (Object[]) oldElementsFiled.get(pathList);
Log.d("sxhLog", "fix: " + oldElements.length);
2.4 將新的數(shù)組插入舊的數(shù)組
//合并兩個數(shù)組
Object[] newArrayElement = (Object[]) Array.newInstance(oldElements.getClass().getComponentType(),
elements.length + oldElements.length);
//將兩個數(shù)組中的元素拷貝到新的數(shù)組中
System.arraycopy(elements, 0, newArrayElement, 0, elements.length);
System.arraycopy(oldElements,0, newArrayElement, elements.length, oldElements.length);
//把原有的Element數(shù)組中的內(nèi)容替換為新的
oldElementsFiled.set(pathList, newArrayElement);
先新建一個數(shù)組摄悯,然后把新的Element數(shù)組插入再把舊的Element數(shù)組插入赞季,這里因為Element類是對外不可見的,因此需要使用Array.newInstance
來創(chuàng)建對應(yīng)的數(shù)組射众。
2.5 最后用組裝的新Element數(shù)組替換舊的數(shù)組
//把原有的Element數(shù)組中的內(nèi)容替換為新的
oldElementsFiled.set(pathList, newArrayElement);
3. 制作dex文件
先使用jar命令把指定的class文件打成jar包jar cvf fix.jar com/example/hotfix/Bug.class
使用dx命令(build-tools/29.0.3/目錄下)把剛才的jar打包成dex文件 dx --dex --output=output.dex --fix.jar
然后把這個dex文件push到手機目錄下碟摆,在修復(fù)時把這個文件傳入就可以加載對應(yīng)的Class。