学习学习之鸿蒙&Android

Android_类加载机制之双亲委派

2021-07-12  本文已影响0人  信仰年輕

本文目标

深入理解Android的类加载机制

1.什么是双亲委派

双亲委派是如何实现的

任意一个classLoader对象都会有一个parent对象,我们下面的customClassLoader创建的时候虽然没有传递parent对象,但是在下面的ClassLoader类中的空参构造方法可以看出,会调用getSystemClassLoader()从而调用ClassLoader.createSystemClassLoader();,最后创建了一个PathClassLoader对象作为parent,而且在创建PathClassLoader的同时也指定了它的parentBootClassLoader

 ClassLoader customClassLoader= new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                return super.loadClass(name);
            }
  };
public abstract class ClassLoader {

    static private class SystemClassLoader {
        public static ClassLoader loader = ClassLoader.createSystemClassLoader();
    }

    protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }

    protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }

    @CallerSensitive
    public static ClassLoader getSystemClassLoader() {
        return SystemClassLoader.loader;
    }

    private static ClassLoader createSystemClassLoader() {
        String classPath = System.getProperty("java.class.path", ".");
        String librarySearchPath = System.getProperty("java.library.path", "");
        //最终会调用PathClassLoader这个classLoader
        return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
    }
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
            //1.先检查是否已经加载过--findLoaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    //2.如果自己没加载过,存在父类,则委托父类
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                }

                if (c == null) {
                    //3.如果父类也没加载过,则尝试本级classLoader加载
                    c = findClass(name);
                }
            }
           return c;
    }
}

来继续看是如何实现的,看下loadClass方法,对于任意一个classLoader对象来说,来加载文件的时候都会调用loadClass方法

2.双亲委派下的Class文件加载流程

总结一下,比如说现在要去加载个String.class文件,我们自己定义了一个CustomerClassLoader

双亲委派的作用

3.Android中的主要类加载器

4.PathClassLoaderDexClassLoader 到底有何不同以及源码解析


我们可以发现PathClassLoaderDexClassLoader都继承自BaseDexClassLoader,然后BaseDexClassLoader继承自ClassLoader,
具体源码如下
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 {
    //dexpath:dex文件以及包含dex的apk文件或jar文件的路径,多个路径用文件分隔符分割,默认文件分隔符为 ': '
    //optimizedDirectory:Android系统将dex文件进行优化后所生产的ODEX文件的存放路径,该路径必须是一个
                                    //PathClassLoader中默认使用路径"/data/dalvik-cache"
                                    //而DexClassLoader则需要我们指定ODEX文件的存放路径
    //librarySearchPath:所使用到的C/C++库存放的路径
    //parent:这个参数的主要作用是保留java中ClassLoader的委托机制
    public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
         super(dexPath, null, librarySearchPath, parent);
    }
}

阅读源码,我们可以发现PathClassLoaderDexClassLoader都没有重写findClass(name);方法,仅仅只是暴露出来1-2个构造方法而已,他俩啥事也没干,在Android 8.0以后两个类功能是一样的
参数解析

这个PathClassLoader主要是用来加载APK中的文件的,然后有没有重新findClass(name);来实现文件加载,我们就去父类BaseDexClassLoader中找该方法,

public class BaseDexClassLoader extends ClassLoader {

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

    @Override
    public String findLibrary(String name) {
        return pathList.findLibrary(name);
    }

    //资源文件的加载
    @Override
    protected URL findResource(String name) {
        return pathList.findResource(name);
    }

    //class文件的加载
    @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;
    }
}

我们阅读上边的源码可以发现,findClass(String name)findResource(String name)findLibrary(String name)等方法都是通过pathList这个对象来进行加载的,然后这个pathList对象是在构造函数中创建的,来具体看下

final class DexPathList {

   /** list of dex/resource (class path) elements */
    private final Element[] dexElements;

   /** list of native library directory elements */
    private final File[] nativeLibraryDirectories;

    public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory,boolean isTrusted) {
        ......
        //1.根据传进来的dexPath路径加载出来所有的dex文件
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory);
        //2.根据libraryPath加载出来所有的动态库文件
        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
    }

    /**
     * Element of the dex/resource file path
     */
    /*package*/ static class Element {
        public final File file;
        public final ZipFile zipFile;
        public final DexFile dexFile;
        public Element(File file, ZipFile zipFile, DexFile dexFile) {
            this.file = file;
            this.zipFile = zipFile;
            this.dexFile = dexFile;
        }
        public URL findResource(String name) {
            if ((zipFile == null) || (zipFile.getEntry(name) == null)) {
                return null;
            }
            try {
                return new URL("jar:" + file.toURL() + "!/" + name);
            } catch (MalformedURLException ex) {
                throw new RuntimeException(ex);
            }
        }
    }
}

DexPathList创建的时候 主要干了

来我们继续看dexElements是如何被加载进来的,具体看下makeDexElements(splitDexPath(dexPath), optimizedDirectory);这个方法
首先会先调用splitDexPath(dexPath)对传进来的dexPath做一个分割,然后会返回一个File的集合

final class DexPathList {
    private static ArrayList<File> splitDexPath(String path) {
        return splitPaths(path, false);
    }
    private static List<File> splitPaths(String searchPath, boolean wantDirectories) {
       List<File> result = new ArrayList<>();
       if(searchPath!=null){
        for(String path : searchPath.split(File.pathSeparator)){
            if(directoriesOnly){
                try{
                      StructStat sb = Libcore.os.stat(path);
                      if(!S_ISDIR(sb.st_mode)){
                           continue;
                       }
                }catch(ErrnoException ignored){
                    continue;
                }
            }
            result.add(new File(path));
        }
      }
    }
}

接下来看makeDexElements方法


    private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
            List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
      Element[] elements = new Element[files.size()];
      int elementsPos = 0;
      /*
      * 遍历传进来的File集合
       */
      for (File file : files) {
          if (file.isDirectory()) {
              elements[elementsPos++] = new Element(file);
          } else if (file.isFile()) {
              String name = file.getName();
              DexFile dex = null;
              //判断是否是已 .dex结尾的
              if (name.endsWith(DEX_SUFFIX)) {
                     dex = loadDexFile(file, optimizedDirectory, loader, elements);
             } else {//不是以.dex结尾的,有可能是zip或者jar文件,照样跟上面的逻辑一样
                    dex = loadDexFile(file, optimizedDirectory, loader, elements);
              }
         } else {
              System.logW("ClassLoader referenced unknown path: " + file);
          }
      }
      if (elementsPos != elements.length) {
          elements = Arrays.copyOf(elements, elementsPos);
      }
      return elements;
    }

    private static DexFile loadDexFile(File file, File optimizedDirectory)
            throws IOException {
        //android 8.0以后 optimizedDirectory 是废弃掉的
        if (optimizedDirectory == null) {
            return new DexFile(file,loader,elements);
        } else {
            String optimizedPath = optimizedPathFor(file, optimizedDirectory);
            return DexFile.loadDex(file.getPath(), optimizedPath, 0,loader,elements);
        }
    }

该方法内部主要是一个for循环把应用中的所有dex文件遍历了出来,被加载出来之后都会存储到dexElements这个数组中,这个dex文件可以理解为一个一个的文件夹,这个文件夹中含有很多class文件


app可以把启动的.class文件集中打进第一个dex包中,这是启动优化的一个思路
然后我们这个第一个dex文件夹和下面的classes2.dex中都包含一个MainActivity.class文件,则会优先加载第一个,这也就是热修复的鼻祖方案

5.1Class文件加载步骤和源码分析

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构,并且提供了访问方法区内的数据结构的方法.

1.装载(Load)查找并加载类的二进制数据(查找和导入Class文件)

2.链接(Link)
验证:确保被加载的类的正确性

准备:为类的静态变量分配内存,并将其初始化为默认值

解析:把类中的符号引用转换为直接引用

3.初始化(Intialize):指向类的方法,对类的静态变量,静态代码块执行初始化操作(不是必须的)
这个初始化不是执行构造函数,也不是执行字节码中的init方法,是执行的类构造器的cinit方法,

以下方式会触发类的初始化

5.2Class文件字节码分析

先抛一个问题,为什么静态方法不能访问非静态变量?,我们来通过一个简单的class类看一下字节码就能明白了

public class Test {
    public static int num = 3;
    public int num2 = 3;
    static void test(){
        System.out.println("test");
    }
}
.class public Lcom/yadong/day01_lsn01/Test;
.super Ljava/lang/Object;
.source "Test.java"

# static fields  声明的是静态的
.field public static num:I

# instance fields  声明的是非静态的
.field public num2:I

# direct methods
.method static constructor <clinit>()V
    .registers 1
    .line 12
    const/4 v0, 0x3
    # 静态的是在这里调用的 num,为num进行赋值
    sput v0, Lcom/yadong/day01_lsn01/Test;->num:I
    return-void
.end method

.method public constructor <init>()V
    .registers 2
    .line 9
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    .line 15
    const/4 v0, 0x3
    # 非静态的是在这里调用的 num2,为num2进行赋值
    iput v0, p0, Lcom/yadong/day01_lsn01/Test;->num2:I
    return-void
.end method

.method static test()V
    .registers 2
    .line 19
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
    const-string v1, "test"
    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
    .line 20
    return-void
.end method

我们能看出来,静态字段num和非静态字段num1是在不同的地方初始化的

为什么静态方法不能访问非静态变量?

public class MainActivity  {

    //在准备阶段他的值默认为0,初始化阶段才会被赋值为3
    //因为把value赋值为3的public static语句在编译后的指令是在类构造器<clinit>()方法之中被调用的
    static int value =3;
    
    int value2 = 3;//随着对象实例化的时候,才会被赋值
    
    static void  test(){
        value2=100;//静态方法为什么不能访问非静态变量
    }
}

总结: 当我们在调用MainActivity.test()这个静态方法的时候,这时候并没创建MainActivity的实例对象,既然没有创建实例对象,那字段value2会被赋值么?肯定不会,那肯定不会访问

如何编译字节码步骤如下


6.1Class.forNameClassLoader.loadClass加载有何不同

总结一下就是:
虽然ClassLoader.loadClass也能加载一个类,但是不会触发类的初始化(也就是说不会对类的静态变量,静态代码块进行初始化操作),
Class.forName这种方式,不但会加载一个类,还会触发类的初始化阶段,也能够为这个类的静态变量,静态代码块进行初始化操作

6.2源码分析

先分析下Class.forName这种方式

public final class Class<T> {
    public static Class<?> forName(String className)throws ClassNotFoundException {
        //1.如果是在MainActivity中调用的Class.forName,则这个caller就是MainActivity.class对象
        Class<?> caller = Reflection.getCallerClass();
        // 2.ClassLoader.getClassLoader(caller) 得到的是当前这个类(MainActivity.class)的ClassLoader对象
        return forName(className, true, ClassLoader.getClassLoader(caller));
    }

    /**
    *initialize为true:类被加载的时候会触发类的初始化操作
    */
    @CallerSensitive
    public static Class<?> forName(String name, boolean initialize,ClassLoader loader) throws ClassNotFoundException {
        if (loader == null) {
            loader = BootClassLoader.getInstance();
        }
        Class<?> result;
        try {
            //调用这个native层的方法得到result
            result = classForName(name, initialize, loader);
        } catch (ClassNotFoundException e) {
            Throwable cause = e.getCause();
            if (cause instanceof LinkageError) {
                throw (LinkageError) cause;
            }
            throw e;
        }
        return result;
    }

    @FastNative
    static native Class<?> classForName(String className, boolean shouldInitialize,
            ClassLoader classLoader) throws ClassNotFoundException;

    public ClassLoader getClassLoader() {
        if (isPrimitive()) {
            return null;
        }
        return (classLoader == null) ? BootClassLoader.getInstance() : classLoader;
    }
}
public abstract class ClassLoader {
    // Returns the class's class loader, or null if none.
    static ClassLoader getClassLoader(Class<?> caller) {
        if (caller == null) {
            return null;
        }
        return caller.getClassLoader();
    }
}

先看第一个方法public static Class<?> forName(String className)throws ClassNotFoundException

继续追进去发现,调用这个native层的方法得到result
在native层的java_lang_Class.cp文件中,这行代码就是类的初始化

来看下ClassLoader.loadClass这种方式是如何实现的

public class BaseDexClassLoader extends ClassLoader {

  private final DexPathList pathList;

    @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;
    }
}
final class DexPathList {
    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;
    }

     static class Element {
           public Class<?> findClass(String name, ClassLoader definingContext,List<Throwable> suppressed) {
               return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed): null;
          }
    }
}
public final class DexFile {
   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;
    }

   private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,DexFile dexFile)
            throws ClassNotFoundException, NoClassDefFoundError;
}

最后会走到这里,发现并没有做任何初始化类的操作
至此,全部分析完毕

上一篇 下一篇

猜你喜欢

热点阅读