转载

Android 中的ClassLoader

上一篇文章我们了解了 JavaClassLoader ,上一篇文章传送门JVM 类加载机制

其实 Android 中的 ClassLoaderjava 中的是不一样的,因为 java 中的 CalssLoader 主要加载 Class 文件,但是 Android 中的 ClassLoader 主要加载 dex 文件

Android中的ClassLoader

Android 中的 ClassLoader 分为俩种类型, 系统类加载器自定义类加载器 。其中 系统的类加载器 分为三种, BootClassLoaderPathClassLoaderDexClassLoader

BootClassLoader

Android 系统启动时,会用BootClassLoader来预加载常用类,与java中的ClassLoader不同,他不是用C++实现,而是用java实现的,如下:

class BootClassLoader extends ClassLoader {

    private static BootClassLoader instance;

    @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }

        return instance;
    }
...
复制代码

BootClassLoaderClassLoader 的内部类,并继承自 ClassLoaderBootClassLoader 是一个单例类,需要注意的是 BootClassLoader 是默认修饰符,只能包内访问,我们是无法使用的

DexClassLoader

DexClassLoader可以加载dex文件和包含dex文件的压缩文件(比如jar和apk文件),不管加载那种文件,最终都是加载dex文件,我们看一下代码

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }
}
复制代码

DexClassLoader有四个参数

  • dexPath :dex相关文件的路径集合,多个文件用路径分割符分割,默认的文件分割符为 ":"
  • optimizedDirectory :解压的dex文件储存的路径,这个路径必须是一个内部储存路径,一般情况下使用当钱应用程序的私有路径 /data/data/<Package Name>/...
  • librarySearchPath :包含C++库的路径集合,多个路径用文件分割符分割,可以为null
  • parent :父加载器

DexClassLoade 继承自 BaseDexClassLoader ,所有的实现都是在 BaseDexClassLoader

PathClassLoader

AndroidPathClassLoader 来加载系统类和应用程序类,代码如下:

public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }

    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }
}
复制代码

PathClassLoader 继承自 BaseDexClassLoader ,所有的实现都是在 BaseDexClassLoader

PathClassLoader 构造方法没有 optimizedDirectory 参数,因为 PathClassLoader 默认 optimizedDirectory 参数是 /data/dalvik-cache ,很显然 PathClassLoader 无法定义解压的 dex 储存的位置,因此 PathClassLoader 通常用来加载已经安装的 apkdex 文件(安装的 apk的dex 文件在 /data/dalvik-cache 中)

ClassLoder的继承关系

运行一个应用程序需要几个ClassLoader呢?

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ClassLoader classLoader = MainActivity.class.getClassLoader();
        while (classLoader != null) {
            Log.d("mmmClassLoader", classLoader.toString()+"/n");
            classLoader = classLoader.getParent();
        }

    }
复制代码

看下log

mmmClassLoader: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.baidu.bpit.aibaidu.idl3-2/base.apk"],
nativeLibraryDirectories=[/data/app/com.baidu.bpit.aibaidu.idl3-2/lib/arm64, /data/app/com.baidu.bpit.aibaidu.idl3-2/base.apk!/lib/arm64-v8a, /system/lib64, /vendor/lib64, /system/vendor/lib64, /product/lib64]]]
 
java.lang.BootClassLoader@fcb14c9
复制代码

我们看到主要用了俩个 ClassLoader ,分别是 PathClassLoaderBootClassLoader ,其中 DexPathList 包含了很多 apk 的路径,其中 /data/app/com.baidu.bpit.aibaidu.idl3-2/base.apk 就是实例应用安装在手机上的位置, DexPathList 是在 BaseDexClassLoder 中创建的,里面储存 dex 相关文件的路径

除了上方的3中ClassLoader,Android还提供了其他的类加载器和ClassLoader相关类,继承关系如下:

Android 中的ClassLoader

分别介绍一下上方的8种ClassLoader

  • ClassLoader 是一个抽象类,其中定义了 ClassLoder 的主要功能, BootClassLoader 是他的内部类
  • SecureClassLoader 和JDK8中的 SecureClassLoader 代码是一样的,他继承抽象类 ClassLoaderSecureClassLoader 并不是 ClassLoader 的实现类,而是扩展了 ClassLoader 权限方面的功能,加强了 Classloader 的安全性
  • URLClassLoader 和JDK8中的URLClassLoader是一样的,他继承了 SecureClassLoader 通能过 URL 路径从jar文件中加载类和资源
  • InMemoryDexClassLoader 他是Android8.0新加的类加载器,继承自 BaseDexClassLoader ,用于加载内存中的dex文件
  • BaseDexClassLoader 继承自 ClassLoader ,是抽象类 ClassLoader 的具体实现类

ClassLoader的加载过程

Android 中的 ClassLoader 同样遵循双亲委托模型, ClassLoader 的加载方法为 loadClass ,方法定义在ClassLoader中

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException{
            // 首先检查类是否被加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    //如果父类抛出ClassNotFoundException异常
                    //则说明父类不能加载该类
                }

                if (c == null) {
                    //如果父类无法加载,则调用自身的findClass进行加
                    c = findClass(name);
                }
            }
            return c;
    }
复制代码

上方逻辑很清楚,首先检查类是否被加载过,如果没有被加载过,就调用父类的加载器加载,如果父类加载器为空就调用启动加载器加载,如果父类加载失败,就调用自己的 findClass 加载,我们看一下这个方法

protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
复制代码

直接抛出异常,这说明findClass需要子类BaseDexClassLoader实现,如下:

public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);

        if (reporter != null) {
            reporter.report(this.pathList.getDexPaths());
        }
    }
    
    ...
    
      @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        //注释1
        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 ,然后再注释1处调用 DexPathListfindClass 方法

public Class<?> findClass(String name, List<Throwable> suppressed) {      //注释1
        for (Element element : dexElements) {
            //注释2
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }
复制代码

在注释1处遍历 dexElements 数组,在注释2处调用 ElementfindClass 方法

public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) {
            System.err.println("Warning: Using deprecated Element constructor. Do not use internal"
                    + " APIs, this constructor will be removed in the future.");
            if (dir != null && (zip != null || dexFile != null)) {
                throw new IllegalArgumentException("Using dir and zip|dexFile no longer"
                        + " supported.");
            }
            if (isDirectory && (zip != null || dexFile != null)) {
                throw new IllegalArgumentException("Unsupported argument combination.");
            }
            if (dir != null) {
                this.path = dir;
                this.dexFile = null;
            } else {
                this.path = zip;
                this.dexFile = dexFile;
            }
        }
        ...
        
        
            public Class<?> findClass(String name, ClassLoader definingContext,
                List<Throwable> suppressed) {
            //注释1
            return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
                    : null;
        }
复制代码

我们从构造方法可以看出,它内部封装了 DexFile ,他用于加载 dex ,注释1处如果 dexFile 不为 null 则调用 DexFileloadClassBinaryName 方法

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

又调用了 defineClass 方法

private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                     DexFile dexFile, List<Throwable> suppressed) {
        Class result = null;
        try {
            //注释1
            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;
    }
复制代码

注释1处调用了 defineClassNative 方法来加载 dex 相关文件,这个是 native 方法不在向下分析

参考: 《Android进阶解密》

原文  https://juejin.im/post/5ddde4eae51d454e3a750c16
正文到此结束
Loading...