【Android面试题】2023最新面试专题:Java反射类加载与动态代理(一)

1 PathClassLoader与DexClassLoader的区别是什么?

这道题想考察什么?

Android中类加载机制的原理

考察的知识点

ClassLoader类加载机制

考生如何回答

ClassLoader就是我们常说的类加载器。

ClassLoader简介

任何一个 Java 程序都是由一个或多个 class 文件组成,在程序运行时,需要将 class 文件加载到 JVM 中才可以使用,负责加载这些 class 文件的就是 Java 的类加载机制。ClassLoader 的作用简单来说就是加载 class 文件,提供给程序运行时使用。每个 Class 对象的内部都有一个 classLoader 字段来标识自己是由哪个 ClassLoader 加载的。

class Class<T> {
  ...
  private transient ClassLoader classLoader;
  ...
}

ClassLoader是一个抽象类,而它的具体实现类很多,最为主要被使用的有:

  • BootClassLoader

    用于加载Android Framework层class文件。

  • PathClassLoader

    用于Android应用程序类加载器。可以加载指定的dex,以及jar、zip、apk中的classes.dex

  • DexClassLoader

    用于加载指定的dex,以及jar、zip、apk中的classes.dex

  • InMemoryDexClassLoader

    Android8.0新增,用于加载内存中的dex

DexClassLoader与PathClassLoader

在Android 5.0以下的版本中,两者之间的区别为:

  1. DexClassLoader : 可加载jar、apk和dex,可以从SD卡中加载
  2. PathClassLoader : 只能加载已安裝到系統中(即/data/app目录下)的apk文件

但是随着Android版本的升级,到Android 5.0及以后就已经不是这样了。

我们先来看看在Android 中的PathClassLoaderDexClassLoader:

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

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

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

其中PathClassLoaderDexClassLoader都是继承自同一个父类:BaseDexClassLoader,而两者的区别则是DexClassLoader必须传递一个optimizedDirectory 用于存放dexopt的结果,后者则不用。

dexopt:

Dalvik中虚拟机在加载一个dex文件时,对 dex 文件 进行 验证 和 优化的操作,其对 dex 文件的优化结果变成了 odex(Optimized dex) 文件,这个文件使用了一些优化操作码,和 dex 文件很像。

其实无论是PathClassLoader还是DexClassLoader,大家可以看到并没有重写父类的其他方法。如果optimizedDirectory 优化目录为Null,即PathClassLoader,则Android5.0以下,则会使用默认的优化目录: /data/dalvik-cache/

而这个目录在安装某个APK时,系统会自动在其中存放odex文件:data@app@packagename.apk@classes.dex
在这里插入图片描述

而在使用PathClassLoader加载时,如果加载的不是已经安装在手机中的APK,则会报出:Dex cache directory isn’t writable: /data/dalvik-cache,这个目录我们的应用自身并不具备写的权限。因此PathClassLoader只能加载已经安装的APK中的dex文件。

而到了ART下,加载方式发生了截然不同的变化,在安装时对 dex 文件执行dex2oat(AOT 提前编译操作),编译为OAT(实际上是ELF文件)可执行文件(机器码)。而如果在加载时,无法成功加载oat文件,仍然会尝试从原dex中加载,因此ART下,PathClassLoaderDexClassLoader都能加载任意指定的dex,以及jar、zip、apk中的classes.dex。但是从原dex加载会导致无法dex2oat,加快加载速度,降低运行效率。

到了Android N之后采用解释,AOT与JIT 混合模式。

到了Android 8.1及以后,DexClassLoader变为:

public class DexClassLoader extends BaseDexClassLoader {
35    /**
36     * Creates a {@code DexClassLoader} that finds interpreted and native
37     * code.  Interpreted classes are found in a set of DEX files contained
38     * in Jar or APK files.
39     *
40     * <p>The path lists are separated using the character specified by the
41     * {@code path.separator} system property, which defaults to {@code :}.
42     *
43     * @param dexPath the list of jar/apk files containing classes and
44     *     resources, delimited by {@code File.pathSeparator}, which
45     *     defaults to {@code ":"} on Android
46     * @param optimizedDirectory this parameter is deprecated and has no effect
47     * @param librarySearchPath the list of directories containing native
48     *     libraries, delimited by {@code File.pathSeparator}; may be
49     *     {@code null}
50     * @param parent the parent class loader
51     */
52    public DexClassLoader(String dexPath, String optimizedDirectory,
53            String librarySearchPath, ClassLoader parent) {
54        super(dexPath, null, librarySearchPath, parent);
55    }
56}

此时DexClassLoader中optimizedDirectory同样固定传递null,因此两者没有任何区别了。

总结
  • Android 4.4及以下:
    • DexClassLoader : 可加载jar、apk和dex,可以从SD卡中加载
    • PathClassLoader : 只能加载已安裝到系統中(即/data/app目录下)的apk文件
  • Android 5.0~Android 8.0:
    • DexClassLoader : 可加载jar、apk和dex,可以从SD卡中加载
    • PathClassLoader : 可加载jar、apk和dex,可以从SD卡中加载,但会导致无法进行dex2oat操作
  • Android 8.1及以上:
    • DexClassLoader 与 PathClassLoader 完全一致

2 什么是双亲委托机制,为什么需要双亲委托机制?

这道题想考察什么?

类加载机制原理

考察的知识点

类加载机制

考生如何回答

双亲委托机制

双亲委托机制是指当一个类加载器收到一个类加载请求时,该类加载器首先会把请求委派给父类加载器。每个类加载器都是如此,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载。

public abstract class ClassLoader{
    //父类加载器
    ClassLoader parent;

    protected ClassLoader(ClassLoader parentLoader) {
            this(parentLoader, false);
    }

    ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
        if (parentLoader == null && !nullAllowed) {
            throw new NullPointerException("parentLoader == null && !nullAllowed");
        }
        parent = parentLoader;
    }

    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
        //先找缓存
        Class<?> clazz = findLoadedClass(className);
        if (clazz == null) {
             ClassNotFoundException suppressed = null;
            if(parent !=null){
                try {
                     //交给父类加载器加载
                    clazz = parent.loadClass(className, false);
                } catch (ClassNotFoundException e) {
                    suppressed = e;
                }
            }
            if (clazz == null) {
                try {
                    //父类加载器加载不到,自己加载
                    clazz = findClass(className);
                } catch (ClassNotFoundException e) {
                    e.addSuppressed(suppressed);
                    throw e;
                }
            }
        }

        return clazz;
    }
}
双亲委托机制的作用

1、防止重复加载

2、安全,保证系统类不能被篡改。

Java虚拟机只会在不同的类的类名相同且加载该类的加载器均相同的情况下才会判定这是一个类。如果没有双亲委派机制,同一个类可能就会被多个类加载器加载,如此类就可能会被识别为两个不同的类,相互赋值时问题就会出现。

双亲委派机制能够保证多加载器加载某个类时,最终都是由一个加载器加载,确保最终加载结果相同。

没有双亲委派模型,让所有类加载器自行加载的话,假如用户自己编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,系统就会出现多个不同的Object类, Java类型体系中基础行为就无法保证,应用程序就会变得一片混乱。

3 Android中加载类的方法有哪些?有什么区别?

这道题想考察什么?

类加载机制中类加载的过程

考察的知识点

  1. 类加载过程
  2. 类加载时机

考生应该如何回答

Android加载类的方式实际上就是Java的类加载。虚拟机把描述类的信息从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终才能够变成可以被虚拟机直接使用的 Java 类型。

类加载时机

在以下情况下,类会自动加载:

  1. 使用 new 实例化对象,创建子类的实例会首先加载其父类
  2. 访问类的静态方法
  3. 访问类的静态属性
  4. 对类进行反射调用
  5. Java程序中定义了main的类,在启动main方法时该类会被加载

以上五种情况都会触发类的加载并且完成对类的初始化。除了以上情况之外,我们也可以主动调用ClassLoader#loadClass(name) 或者Class.forName(name) 进行加载。实际上Class.forName(name)也是通过ClassLoader完成对指定类的加载。

public static Class<?> forName(String className) throws ClassNotFoundException {
    //得到调用者的类,如main方法所在类
	Class<?> caller = Reflection.getCallerClass();
    //ClassLoader.getClassLoader(caller):获得main方法所在类的类加载器,使用其完成className的加载
	return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

但是ClassLoader#loadClass(name)Class.forName(name) 不同的是,后者除了完成类加载之外,还会对类进行初始化,执行类中的static块。而 ClassLoader#loadClass只会完成类的加载。

当然 ,我们也可以使用:Class.forName(String name, boolean initialize, ClassLoader loader)。通过第二个参数initialize ,从而选择是否需要对类进行初始化。

最后

此面试题会持续更新,请大家多多关注!!!!
有需要此面试题的朋友可以扫描下方二维码免费领取!!!
同时扫描下方二维码还可以进群享受ChatGPT机器人的服务哦!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值