Android知识点总结(六):Java反射类加载与动态代理

67 阅读7分钟

1、PathClassLoader与DexClassLoader的区别是什么?

1.1 概述

  • 程序运行时需要将class加载到JVM才能用,classLoader就是用来加载class文件的。每个class内部都有一个classLoader字段来标识自己是哪个ClassLoader加载的。
  • BootClassLoader : 加载FrameWork层的class文件
  • DexClassLoader : 用于android应用程序类加载器。用于加载指定的dex。jar、zip、apk中的classes.dex
  • PathClassLoader: 用于加载指定的dex。jar、zip、apk中的classes.dex
  • InMemoryDexClassLoader:Android 8.0新增 用于加载内存中的dex

1.2 区别

  • 4.4及以下
    • DexClassLoader:可加载jar、apk、dex,可从SD卡中加载
    • PathClassLoader:只能加载已安装到系统中(data/app)的apk文件
  • 5.0~8.0
    • DexClassLoader:可加载jar、apk、dex,可从SD卡中加载
    • PathClassLoader:可加载jar、apk、dex,可从SD卡中加载。但是无法进行dex2oat
  • 8.1及以上
    • 两者一样。

2、什么是双亲委托机制

2.1 当一个类加载器收到一个类加载请求时,先把请求委托给父类加载器,只有父类找不到指定的类的时候,他才会自己去加载

2.2 好处是什么?

2.2.1 防篡改
  • 如果用于自己编写一个java.lang.object。而且没有双亲委托,那么它写的类可能就覆盖掉系统的这个类,就会出错。
2.2.2 防重复加载
  • 只有类名和加载器名相同才会判定同一个类,双亲委派机器能够保证是同一个加载器加载,方式同一个类不同加载器加载导致不是同一个类。

3、Android 中加载类的方法有哪些?有啥区别?

  • 和Java加载类的方式一样,加载、校验、准备、解析和初始化

3.1具体方法5种

  • 使用new实例化对象,创建子类会先加载父类
  • 访问类的静态方法
  • 访问类的静态属性
  • 对类进行反射调用
  • Java程序中定义main类,启动java方法时,类会被嘉爱。
  • ClassLoader#loadClass(name)
  • Class.forName(name) 内部也是采用上述方法。除了上方法外还会类初始化,执行static块
    • 也就使用用它的重载函数,有个参数选择是否初始化。

4、ClassNotFound有可能什么原因

  • APK中dex未包含需要加载的类数据
  • dex分包,前边已加载的dex加载了还未加载的dex中的类。为了避免这种情况,确保正确地配置和使用多dex加载机制
  • 类数据校验合法失败,但在log中可以看到。

5、Odex是什么?解释型语言和变异型语言啥区别?

image.png

  • Odex 就是优化过的dex文件,OptimizedDex。
    • APK就是个zip压缩包。Android 虚拟机需要从Zip压缩中读取classes.dex 文件完成类加载,odex就是把dex文件从Zip压缩包中提取,每次运行就不用再去APK里去拿了。
  • 编译型语言
    • 利用编译器将源码一次性编译成二进制指令,生成可执行程序,C/C++
  • 解释型语言
    • 边执行边用解释器转换。比如javaScript PHP
  • java有点特殊,是用编译器编译成字节码,然后JVM将字节码解释执行
  • 解释型语言基本有跨平台能力,代码一样,在不同平台上将源码解释为不同的机器码就好啦。

6、说下反射的应用场景,哪些框架?

  • 判断
    • 运行时判断一个对象所属类用.getClass()
    • 运行时判断一个所有的成员变量和方法。
  • 获取
    • 运行时构建任意一个对象
    • 运行时获取任意对象的方法、成员变量。即使是private的
  • 生成动态代理 -哪些框架用?
    • 插件化(动态加载插件)、热修复(修改dex数组)、retrofit(通过泛型类型生成类哪里,用了反射)

7、反射为什么这啥这么慢?

  • 频繁拆装箱,invoke的参数是Object[],那么意味着基本类型要转为包装类,然后封装为Object数组。然后使用再拆箱。
  • 需要检查方法可见性。setAccessible
  • 需要检验参数
  • JIT无法优化,比如反射方法就无法内联

8、动态代理是什么,如何实现?

  • 代理模式,使用一个类代理另外一个类的功能,当一个对象不适合或者不能直接引用另外一个对象,而代理对象可以再 客户端和 目标对象之间起到中介作用。
  • 一般分为
    • 抽象角色
      • 提供接口给 代理角色和真实角色
    • 真实角色
      • 实现抽象角色提供的接口,写上具体 逻辑
    • 代理角色
      • 实现抽象角色接口,是 真实角色的代理,通过 调用真实角色来实现抽象方法

8.1、静态代理

- 可维护性差,一个方法改了,要改3个文件 ,维护性 查。
- 静态代理一对一,静态代理对象多了。就要写很多文件。
- 如果一对多,则扩展能力差。

8.2、动态代理

其实就是通过抽象接口和真实角色,通过Proxy的getProxyClass方法生成了一个代理的实例(有个代理的class可以看下),这个实例内部就是通过反射区调用真实角色的。 - 优点 - 运行时动态创建代理类,不要手动创建 - 可以处理多接口情况,不需要为每个接口创建一个代理类。 - 可以来灵活地为目标类扩展功能。 - 缺点 - 学习成本高 - 因为用到反射,所以性能相对低 - 的使用场景优先,一般框架设计,远程方法(Binder)调用。

8.3 我写的详细版

设计模式(2)-动态代理-大白话版 - 掘金 (juejin.cn)

9 动态代理的方法,怎么初始化的。

  • java动态大力中 会根据代理的接口生成Proxy派生类作为代理类。这个类会生成一段静态代码块,静态代码块中使用了放射获取到被代理的所有方法,假设代理方法名字叫buyMethod,并赋值给成员属性,供后续使用。
  • 动态代理对象调用代理方法buyMethod时,内部是会调用 InvocationHandler的invoke方法,并把通过反射 的来 的buyMethod传过去。在invoke里执行了内容,其中就有通过反射得来的buyMethod反射执行。就ok了。

10、讲一下CGlib

  • 基于ASM的字节码生成库,Android现在用不了。允许在运行时对字节码修改和动态生成。
  • 通过继承某类实现代理。在子类中采取 拦截的计数拦截父类方法调用。然后在前后加东西。
    • 优点
      • JDK只能处理接口,CGlib没有要求,成为被代理类的子类(代理类)。很有意思
    • 缺点
      • 速度比jdk动态代理慢一些
      • final 的方法无法重写
  • 步骤
    • 生成代理类的二进制字节码文件
    • 加载二进制 字节码,生成Class对象
    • 通过反射构建实例。
  • 总结
    • CGLIB运行时生成代理类 继承被代理类, final 方法无法代理
    • 代理类为委托方法假设为doIt生成两个方法,doIt和CGLIBdoItdoIt0
    • 代理对象执行doIt,代理对象则调用Enhancer.setCallback设置的MethodInterceptor.intercept方法,传入了CGLIBdoItdoIt0。
    • intercept里在CGLIBdoItdoIt0前后做操作,CGLIBdoItdoIt0,里则是调用父类。

11、为什么IO是耗时操作

  • IO操作包括从磁盘读取写入数据,网络发送或者接收数据。这些操作都需要一定时间来完成,因此可以被视为耗时操作。
  • 计算机上是使用DMA来访问磁盘等IO,发出请求之后,cpu就不管了,直到DMA将磁盘内容(或者网络内容)加载到内存后,通过中断通知CPU完成了。所以单独的一个IO时间对CPU占用很少。但是IO的速度太慢了,远远慢于内存和CPU。虽然后是不会耗费CUP但是会阻塞进程直到IO操作完成,从而影响程序的执行效率和相应速度。
  • 所以一般使用缓存、或者子线程处理IO完成后再通知主线程。