Android ClassLoader原理(一)

2017-12-08  本文已影响0人  nothingren

Android的类加载机制遵循Java的双亲委派原理。其继承关系如下:

ClassLoader <--- BaseDexClassLoader <---PathClassLoader、DexClassLoader

PathClassLoader和DexClassLoader的源码如下:

public class PathClassLoader extends BaseDexClassLoader {

    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }

    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
    }
}

public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;

    /**
     * Constructs an instance.
     *
     * @param dexPath the list of jar/apk files containing classes and
     * resources, delimited by {@code File.pathSeparator}, which
     * defaults to {@code ":"} on Android
     * @param optimizedDirectory directory where optimized dex files
     * should be written; may be {@code null}
     * @param librarySearchPath the list of directories containing native
     * libraries, delimited by {@code File.pathSeparator}; may be
     * {@code null}
     * @param parent the parent class loader
     */
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, 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;
    }

  ...

  }

PathClassLoader和DexClassLoader的区别仅仅在于构造方法中的optimizedDirectory是否为空,这里先不讨论optimizedDirectory的作用,后续会详细介绍。
从BaseDexClassLoader的源码中可以看出来,加载一个类的时候,都是从pathList(DexPathList的实例)中查找的。DexPathList的findClass()方法源码如下:

public Class findClass(String name, List<Throwable> suppressed) {
        //从dexElements数组中查找,dexElements就是用于存储dex文件信息,dexElements在BaseDexClassLoader的构造方法中创建。
        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;
    }

DexFile执行了dex文件的class查找工作,其loadClassBinaryName()方法源码如下:

   public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
       return defineClass(name, loader, mCookie, this, suppressed);
   }

   private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                    DexFile dexFile, List<Throwable> 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;
   }

其中defineClassNative()方法是一个native方法后面会专门介绍。
Android的ClassLoader加载机制可以总结为一句话:从遍历dexElements数组,挨着查找每个dex文件中是否有待加载打Class文件,如果有,则加载,如果没有,则接着查找下一个dex文件,直到将dexElements数组遍历完毕。很多插件化和热修复技术就是基于这个基本原则来设计。以热修复为例,将待修复的类打包成jar包,在应用启动的时候,将jar包解析成dex文件,并插入到dexElements数组的头部。这样等到待修复的类加载时,永远查找到的是dexElements头部第一个dex文件中的已经修复过的类。热修复的原理就这么简单,当然,热修复的实现方式还有很多,这里不一一说明。基于在dexElements数组头部插入的方式还有一个问题。那就是CLASS_ISPREVERIFIED标记。CLASS_ISPREVERIFIED的问题后面章节会详细说明。

上一篇下一篇

猜你喜欢

热点阅读