Android开发 - ClassLoader 加载外部类解析

ClassLoader 是什么

  • ClassLoader 主要作用是将字节码文件(.class 文件)加载到 Java 虚拟机(JVM)中,以便应用程序可以使用这些类

ClassLoader 的好处

  • 模块化加载:应用程序可能由多个模块组成,而这些模块可能需要按需加载

  • 插件机制:很多应用支持插件化,插件在安装或更新后需要动态加载

  • 热修复:一些应用支持在不重新启动应用的情况下修复 bug,这也需要动态加载新类

ClassLoader 的层次结构

  • Bootstrap ClassLoader引导类加载器,负责加载核心 Java 类库(如 java.lang 包)

  • System ClassLoader(又称 Application ClassLoader)系统类加载器,负责加载来自 classpath(或 Android 的 APK 文件中的类路径)中的类

  • Custom ClassLoader自定义类加载器,开发者可以继承 ClassLoader 并实现自己的加载逻辑,用于特殊场景(如插件化框架、热修复框架)

ClassLoader 的工作原理

  • 双亲委派模型ClassLoader 先把类加载的请求传递给父类加载器(通常是上一级的类加载器)。如果父类加载器能找到对应的类,它就加载这个类。

  • 自己加载:如果父类加载器不能加载该类,那么当前的 ClassLoader 会尝试自己加载

  • 加载完成:一旦类加载成功,它会被缓存起来,以便后续使用不需要重新加载

ClassLoader 的应用

  • 应用启动:使用 PathClassLoader加载应用的资源

  • 动态加载:在使用反射(Reflection)动态加载类(如使用 DexClassLoader)时ClassLoader 能够帮助动态地加载 .dex 文件或 .jar 文件中的类

  • 插件化框架:如 RePluginSmall插件化框架都依赖于 ClassLoader 来加载插件中的类

自定义 ClassLoader的高级作用

  • 动态更新和修复:通过自定义 ClassLoader,可以动态加载新的类文件替换已有的类,实现应用热修复

  • 模块隔离:使用自定义 ClassLoader 可以实现不同模块间的类隔离,避免类冲突

代码示例

使用 DexClassLoader 动态加载类

  • 假设有一个外部的 external.dex 文件,其中包含我们想要动态加载com.example.external.ExternalClass

    • 在项目中添加权限:在 AndroidManifest.xml 中添加读取外部存储的权限,以便能够从外部存储加载 .dex 文件

      <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
      
    • 代码示例

      import android.content.Context;
      import android.os.Bundle;
      import android.widget.Toast;
      import androidx.appcompat.app.AppCompatActivity;
      
      import java.io.File;
      import dalvik.system.DexClassLoader;
      
      // 当活动创建时调用此方法
      public class MainActivity extends AppCompatActivity {
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
      
              // 获取外部 .dex 文件的路径
              String dexPath = "/sdcard/external.dex"; // 假设 .dex 文件在外部存储根目录
      
              // 获取应用私有目录作为解压的输出路径
              // 使用 getDir 方法获取应用的私有目录,以存储优化后的 .dex 文件
              File optimizedDirectory = getDir("dex", Context.MODE_PRIVATE); // Context.MODE_PRIVATE 指定目录私有于该应用
      
              // 使用 DexClassLoader 加载外部 .dex 文件
              DexClassLoader dexClassLoader = new DexClassLoader(
                      dexPath,                          // .dex 文件的路径
                      optimizedDirectory.getAbsolutePath(), // 解压优化后的 .dex 存放目录
                      null,                             // 本示例中没有使用 C/C++ 本地库,所以传 null
                      getClassLoader()                  // 父类加载器(通常为应用的类加载器)
              );
      
              try {
                  // 动态加载指定类的 Class 对象
                  // 使用 dexClassLoader.loadClass 方法动态加载 .dex 文件中的类
                  Class<?> externalClass = dexClassLoader.loadClass("com.example.external.ExternalClass");
      
                  // 创建该类的实例
                  // 使用 newInstance 方法创建加载类的一个新实例。
                  Object instance = externalClass.newInstance();
      
                  // 调用该类的一个方法
                  // 使用反射调用该类的方法。这里调用 externalMethod 方法
                  externalClass.getMethod("externalMethod").invoke(instance);
      
                  Toast.makeText(this, "External class loaded and method invoked!", Toast.LENGTH_LONG).show();
      
              } catch (Exception e) {
                  // 获可能发生的异常(如类未找到、方法不存在等),并显示错误消息
                  e.printStackTrace();
                  Toast.makeText(this, "Failed to load external class: " + e.getMessage(), Toast.LENGTH_LONG).show();
              }
          }
      }
      
  • 注意事项

    • 权限:应用需要有读取外部存储的权限

    • 安全性:加载外部代码存在安全风险,需要确保 .dex 文件来源可信

    • 兼容性:不同安卓版本的 ClassLoader 机制可能有些不同,确保测试在目标平台上运行

自定义 ClassLoader 加载外部类

  • 创建一个自定义的 ClassLoader,并使用它来加载一个外部的类

    • 创建自定义 ClassLoaderCustomClassLoader.java

      // 引入必要的 File 和 FileInputStream 类用于文件操作
      import java.io.File;
      import java.io.FileInputStream;
      
      import java.io.IOException;
      
      // 定义一个 CustomClassLoader 类,继承自 ClassLoader
      public class CustomClassLoader extends ClassLoader {
          // 定义一个类路径的成员变量,用于存储类文件的目录路径
          private String classPath;
      
          // 构造函数,传入类路径
          // 接收一个类路径作为参数,并赋值给成员变量 classPath
          public CustomClassLoader(String classPath) {
              this.classPath = classPath;
          }
      
          // 写 ClassLoader 类的 findClass 方法,这个方法是我们自定义类加载的核心
          @Override
          protected Class<?> findClass(String name) throws ClassNotFoundException {
              // 将类名转换为文件路径形式
              // 将类的全限定名转换为文件系统路径。例如,com.example.MyClass 会转换为 com/example/MyClass.class
              String fileName = classPath + name.replace(".", "/") + ".class";
              try {
                  // 读取类文件
                  // 创建一个文件输入流,读取指定路径的 .class 文件
                  FileInputStream fis = new FileInputStream(new File(fileName));
                  //  创建一个字节数组,用于存储类文件的字节码
                  byte[] bytes = new byte[fis.available()];
                  fis.read(bytes); // 读取类文件的内容到字节数组中
                  fis.close(); // 然后关闭输入流
      
                  // 调用 defineClass 方法将字节码转换为 Class 对象。defineClass 是 ClassLoader 类的一个保护方法,用于将字节数组转换为 Class 对象
                  return defineClass(name, bytes, 0, bytes.length);
              } catch (IOException e) {	//  捕获文件读取异常,打印堆栈跟踪,并抛出 ClassNotFoundException 异常
                  e.printStackTrace();
                  // 找不到类时抛出异常
                  throw new ClassNotFoundException(name);
              }
          }
      }
      
    • 使用自定义 ClassLoader:使用 CustomClassLoader加载外部的类

      // 定义一个测试类 TestCustomClassLoader
      public class TestCustomClassLoader {
          // 主方法入口
          public static void main(String[] args) {
              // 指定外部类路径,例如 "/path/to/classes/"
              // 定义类文件的路径。注意,这个路径需要指向包含要加载的 .class 文件的目录
              String classPath = "/path/to/classes/";
      
              // 创建 CustomClassLoader 的实例,并传入类路径
              CustomClassLoader customClassLoader = new CustomClassLoader(classPath);
      
              try {
                  // 使用自定义类加载器加载指定的外部类 "com.example.MyClass"
                  Class<?> clazz = customClassLoader.loadClass("com.example.MyClass");
      
                  // 创建加载类的一个实例。注意:newInstance() 已经被弃用,推荐使用 clazz.getDeclaredConstructor().newInstance()
                  Object instance = clazz.newInstance();
      
                  // 输出加载类的名称
                  System.out.println("加载的类: " + clazz.getName());
                  // 输出实例对象
                  System.out.println("实例对象: " + instance); 
                  
              // 捕获各种可能的异常,如类找不到、实例化异常和非法访问异常,并打印异常信息
              } catch (ClassNotFoundException e) {
                  e.printStackTrace();
              } catch (InstantiationException e) {
                  e.printStackTrace();
              } catch (IllegalAccessException e) {
                  e.printStackTrace();
              }
          }
      }
      
  • 这种技术在安卓开发中的插件化和动态修复等场景中非常有用

总结

  • ClassLoader 扮演着重要的角色,提供了动态加载和运行时机制,使应用能够更加灵活动态化
posted @ 2024-08-31 15:26  阿俊学JAVA  阅读(54)  评论(0编辑  收藏  举报