java:类加载原理: 当类加载器收到加载类或资源的请求时,通常都是先委托给父类加载器加载,也就是说只有当父类加载器找不到指定类或资源时,自身才会执行实际的类加载过程,具体的加载过程如下:

1、源 ClassLoader 先判断该 Class 是否已加载,如果已加载,则直接返回 Class,如果没有则委托给父类加载器。

2、父类加载器判断是否加载过该 Class,如果已加载,则直接返回 Class,如果没有则委托给祖父类加载器。

3、依此类推,直到始祖类加载器(引用类加载器)。

4、始祖类加载器判断是否加载过该 Class,如果已加载,则直接返回 Class,如果没有则尝试从其对应的类路径下寻找 class 字节码文件并载入。如果载入成功,则直接返回 Class,如果载入失败,则委托给始祖类加载器的子类加载器。 5、始祖类加载器的子类加载器尝试从其对应的类路径下寻找 class 字节码文件并载入。如果载入成功,则直接返回 Class,如果载入失败,则委托给始祖类加载器的孙类加载器。

6、依此类推,直到源 ClassLoader。

7、源 ClassLoader 尝试从其对应的类路径下寻找 class 字节码文件并载入。如果载入成功,则直接返回 Class,如果载入失败,源 ClassLoader 不会再委托其子类加载器,而是抛出异常

    

Android类加载: Android中的类加载器是BootClassLoader、PathClassLoader、DexClassLoader,其中BootClassLoader是虚拟机加载系统类需要用到的,PathClassLoader是App加载自身dex文件中的类用到的,DexClassLoader可以加载直接或间接包含dex文件的文件。

PathClassLoader和DexClassLoader都继承自BaseDexClassLoader,它的一个DexPathList类型的成员变量pathList很重要。DexPathList中有一个Element类型的数组dexElements,这个数组中存放了包含dex文件(对应的是DexFile)的元素。BaseDexClassLoader加载一个类,最后调用的是DexFile的方法进行加载的


下面是代码热修复的两种方式:
1、根据类加载为父委托加载原理 将加载修复后的dex的DexClassLoader插入到PathClassLoader和BootstrapClassLoader中间,也就是将DexClassLoader设置为PathClassLoader的父加载器,将BootstrapClassLoader设置为DexClassLoader 顺序为:BootstrapClassLoader--->DexClassLoader--->PathClassLoader

//根据类加载为父委托加载原理   替换有bug的类放在dexPath中让DexClassLoader优先加载
    //类加载顺序:BootstrapClassLoader---->DexClassLoader----->PathClassLoader
    public void loadPatchDex(Context context, String dexPath, String optimizedDirectory, String librarySearchPath) {
        ClassLoader currentClassLoader = context.getClassLoader();//context加载器(PathClassLoader)
        ClassLoader parentClassLoader = currentClassLoader.getParent();//context的父加载器(BootstrapClassLoader)
        
        //加载dexPath加载器
        DexClassLoader dexClassLoader = new DexClassLoader(dexPath, optimizedDirectory, librarySearchPath, parentClassLoader);
        this.setField(ClassLoader.class, this.mParentFieldName, currentClassLoader, dexClassLoader);//设置当前类加载器的父加载器为dexClassLoader
    }

 /**
     * @param clazz
     * @param fieldName 属性名称
     * @param target    要设置属性的对象
     * @param value     设置属性的值
     * @return 设置成功
     */
    private boolean setField(Class clazz, String fieldName, Object target, Object value) {
        try {
            Field field = clazz.getDeclaredField(fieldName);
            if (field != null) {
                field.setAccessible(true);
                field.set(target, value);
            }
            return true;
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return false;//设置失败
    }

2、根据安卓的PathClassLoader加载机制将DexClassLoader加载的新的Dex添加的DexPathList的dexElements属性列表中,将更新的dex加载放在dexElements列表索引之前

    public void loadPatchDex2(Context context, String dexPath, String optimizedDirectory, String librarySearchPath) {
       ClassLoader pathClassLoader = context.getClassLoader();
       //加载dexPath加载器
       DexClassLoader dexClassLoader = new DexClassLoader(dexPath, optimizedDirectory, librarySearchPath, pathClassLoader.getParent());
       //获取BaseDexClassLoader的DexPathList属性 私有的需要通过反射获取
       Object dexPathList1 = this.getFieldValue(BaseDexClassLoader.class, this.mDexPathListFieldName, pathClassLoader);//pathClassLoader的PathList属性
       Object dexPathList2 = this.getFieldValue(BaseDexClassLoader.class, this.mDexPathListFieldName, dexClassLoader);
       if (dexPathList1 != null && dexPathList2 != null) {
           //获取对应pathList中的dexElements属性
           Object dexElements1 = this.getFieldValue(dexPathList1.getClass(), this.mElementFieldName, dexPathList1);//当前PathClassLoader中的Element[]属性
           Object dexElements2 = this.getFieldValue(dexPathList2.getClass(), this.mElementFieldName, dexPathList2);//DexClassLoader中的Element[]属性

           if (dexElements1 != null && dexElements2 != null) {
               //将两个Element[]属性合并
               Object finalElements = combineArray(dexElements2, dexElements1);
               //将合并的值设置给当前的pathClassLoader的Element[]中
               this.setField(dexPathList1.getClass(), this.mElementFieldName, dexPathList1, finalElements);
               Log.e("HotFixEngine", "loadPatchDex2: success");
           }
       }

   }


   /**
    * 获取对应属性值
    * @param clazz
    * @param fieldName 属性名称
    * @param target 要操作的对象
    * @return
    */
   private Object getFieldValue(Class clazz, String fieldName, Object target) {
       Field field = this.getField(clazz, fieldName);
       if (field != null) {
           try {
               field.setAccessible(true);
               return field.get(target);
           } catch (IllegalAccessException e) {
               e.printStackTrace();
           }
       }
       return null;
   }

   private Field getField(Class clazz, String fieldName) {
       try {
           Field field = clazz.getDeclaredField(fieldName);
           field.setAccessible(true);
           return field;
       } catch (NoSuchFieldException e) {
           e.printStackTrace();
       }
       return null;
   }

   /**
    * 两个数组合并
    *
    * @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;
   }

Demo地址:https://github.com/xuguohongai/android/tree/master/AndroidHotFixDemo