/**
* Finds the named class in one of the dex files pointed at by
* this instance. This will find the one in the earliest listed
* path element. If the class is found but has not yet been
* defined, then this method will define it in the defining
* context that this instance was constructed with.
*
* @param name of class to find
* @param suppressed exceptions encountered whilst finding the class
* @return the named class or {@code null} if the class is not
* found in any of the dex files
*/
public Class findClass(String name, List suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
/**
* Provides a simple {@link 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).
*/
public class PathClassLoader extends BaseDexClassLoader {
/**
* A class loader that loads classes from {@code .jar} and {@code .apk} files
* containing a {@code classes.dex} entry. This can be used to execute code not
* installed as part of an application.
*
* This class loader requires an application-private, writable directory to
* cache optimized classes. Use {@code Context.getCodeCacheDir()} to create
* such a directory:
{@code
* File dexOutputDir = context.getCodeCacheDir();
* }
*
* Do not cache optimized classes on external storage.
* External storage does not provide access controls necessary to protect your
* application from code injection attacks.
*/
public class DexClassLoader extends BaseDexClassLoader {
/**
* Base class for common functionality between various dex-based
* {@link ClassLoader} implementations.
*/
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
//.... code
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
List suppressedExceptions = new ArrayList();
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;
}
findClass()方法中用到了
pathList.findClass(name),附DexPathList类代码
final class DexPathList {
/**
* List of dex/resource (class path) elements.
* Should be called pathElements, but the Facebook app uses reflection
* to modify 'dexElements' (http://b/7726934).
*/
private Element[] dexElements;
/**
* Finds the named class in one of the dex files pointed at by
* this instance. This will find the one in the earliest listed
* path element. If the class is found but has not yet been
* defined, then this method will define it in the defining
* context that this instance was constructed with.
*
* @param name of class to find
* @param suppressed exceptions encountered whilst finding the class
* @return the named class or {@code null} if the class is not
* found in any of the dex files
*/
public Class findClass(String name, List suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
public final class DexFile {
/**
* See {@link #loadClass(String, ClassLoader)}.
*
* This takes a "binary" class name to better match ClassLoader semantics.
*
* @hide
*/
public Class loadClassBinaryName(String name, ClassLoader loader, List suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie,
DexFile dexFile, List suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie, dexFile);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
DexFile dexFile)
throws ClassNotFoundException, NoClassDefFoundError;
}
public class FixDexUtils {
private static final String DEX_DIR = "odex";
private static HashSet loadedDex = new HashSet();
static {
loadedDex.clear();
}
public static void loadFixedDex(Context context) {
if (context == null) {
return;
}
//遍历所有的修复的dex
File fileDir = context.getDir(DEX_DIR, Context.MODE_PRIVATE);
File[] listFiles = fileDir.listFiles();
for (File file : listFiles) {
if (file.getName().startsWith("classes") && file.getName().endsWith(".dex")) {
loadedDex.add(file);
}
}
//合并之前的dex
doDexInject(context, fileDir, loadedDex);
}
private static void doDexInject(final Context appContext, File filesDir, HashSet loadedDex) {
String optimizeDir = filesDir.getAbsolutePath() + File.separator + "opt_dex";
File fopt = new File(optimizeDir);
if (!fopt.exists()) {
fopt.mkdirs();
}
//1.加载应用程序的dex
//2.加载指定的修复的dex文件。
//3.合并
try {
PathClassLoader pathLoader = (PathClassLoader) appContext.getClassLoader();
for (File dex : loadedDex) {
//2.加载指定的修复的dex文件。
DexClassLoader classLoader = new DexClassLoader(
dex.getAbsolutePath(),
fopt.getAbsolutePath(),
null,
pathLoader
);
///通过反射获得classLoader、pathLoader的pathList
Object dexObj = getPathList(classLoader);
Object pathObj = getPathList(pathLoader);
//通过反射获得classLoader、pathLoader的DexElements
Object mDexElementsList = getDexElements(dexObj);
Object pathDexElementsList = getDexElements(pathObj);
//将classLoader、pathLoader的DexElements合并
Object dexElements = combineArray(mDexElementsList, pathDexElementsList);
//重新给pathLoader的PathList里面的dexElements赋值
setField(pathObj, pathObj.getClass(), "dexElements", dexElements);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void setField(Object obj, Class> cl, String field, Object value) throws Exception {
Field localField = cl.getDeclaredField(field);
localField.setAccessible(true);
localField.set(obj, value);
}
private static Object getField(Object obj, Class> cl, String field)
throws Exception {
Field localField = cl.getDeclaredField(field);
localField.setAccessible(true);
return localField.get(obj);
}
//反射获得BaseDexClassLoader的pathList
private static Object getPathList(Object baseDexClassLoader) throws Exception {
return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
}
//反射DexPathList的dexElements
private static Object getDexElements(Object obj) throws Exception {
return getField(obj, obj.getClass(), "dexElements");
}
/**
* 两个数组合并
*
* @param arrayLhs
* @param arrayRhs
* @return
*/
private static Object combineArray(Object arrayLhs, Object arrayRhs) {
Class> localClass = arrayLhs.getClass().getComponentType();
int i = Array.getLength(arrayLhs);
int j = i + Array.getLength(arrayRhs);
Object result = Array.newInstance(localClass, j);
for (int k = 0; k < j; ++k) {
if (k < i) {
Array.set(result, k, Array.get(arrayLhs, k));
} else {
Array.set(result, k, Array.get(arrayRhs, k - i));
}
}
return result;
}
}
Android dex分包方案
安卓App热补丁动态修复技术介绍
微信Android热补丁实践演进之路
Alibaba-AndFix Bug热修复框架原理及源码解析
Android热补丁之AndFix原理解析
微信Tinker的一切都在这里,包括源码(一)
Android 热修复Nuwa的原理及Gradle插件源码解析
Android中热修复框架Robust原理解析+并将框架代码从"闭源"变成"开源"(下篇)