首页 > 其他分享 >Android开发 - ClassLoader 加载外部类解析

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

时间:2024-08-31 15:27:31浏览次数:13  
标签:dex 文件 自定义 ClassLoader Android class 加载

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 扮演着重要的角色,提供了动态加载和运行时机制,使应用能够更加灵活动态化

标签:dex,文件,自定义,ClassLoader,Android,class,加载
From: https://www.cnblogs.com/ajunjava/p/18390360

相关文章

  • 在Vue3中实现懒加载功能
    在Vue3中实现懒加载功能在现代前端开发中,懒加载是一种提高应用性能和用户体验的重要技术,尤其是在处理较大图片或长列表数据时。懒加载意味着仅在用户需要时才加载资源,这有助于减少初始加载时间和提升响应速度。本文将使用Vue3和其新推出的setup语法糖来实现懒加载......
  • 前端CSS:CSS雪碧图的作用以及加载方式
    前端CSS:CSS雪碧图的作用以及加载方式引言基本概念和作用说明基本概念作用说明示例一:基础雪碧图制作制作步骤代码示例HTML示例说明示例二:使用伪元素代码示例HTML示例说明示例三:响应式雪碧图代码示例说明示例四:雪碧图的动态加载代码示例说明示例五:雪碧图的自动工具......
  • 基于live555开发的多线程RTSPServer轻量级流媒体服务器EasyRTSPServer开源代码及其调
    EasyRTSPServer参考live555testProg中的testOnDemandRTSPServer示例程序,将一个live555testOnDemandRTSPServer封装在一个类中,例如,我们称为ClassEasyRTSPServer,在EasyRTSPServer_Create接口调用时,我们新建一个EasyRTSPServer对象,再通过调用EasyRTSPServer_Startup接口,将EasyRTSP......
  • 基于ssm+vue基于Android的大学校园车辆管理系统前【开题+程序+论文】
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着高校规模的不断扩大和师生人数的持续增长,大学校园内的车辆管理成为了一个日益严峻的问题。传统的人工管理方式不仅效率低下,而且难以应对日益复杂......
  • 一个操作系统的设计与实现——第20章 加载64位内核,64位显卡驱动与内存管理系统
    20.164位ELF格式在64位模式下,由于内存地址变宽,ELF格式中的内存地址也要跟着变宽。这并不是一个麻烦的问题,因为ELF格式的整体结构没有发生变化,仍然由一个文件头,加上若干程序头表组成。对于64位ELF格式的文件头,我们需要关注的信息如下表所示:偏移量字节数含义0x188程......
  • Android源码bta_gattc_start_discover剖析
    1.前言当BLE设备作为GATT客户端(GATTClient)连接到GATT服务器(GATTServer)后,它通常需要执行发现过程以了解服务器的GATT数据库结构。这包括服务(Services)、特征(Characteristics)和描述符等。bta_gattc_start_discover 函数正是用于启动这一过程的。2.工作流程连接建立:首先,GAT......
  • java类加载器
    类加载器一、类加载器【理解】作用负责将.class文件(存储的物理文件)加载在到内存中二、类加载的过程【理解】类加载时机创建类的实例(对象)调用类的类方法访问类或者接口的类变量,或者为该类变量赋值使用反射方式来强制创建某个类或接口对应的java.lang.Class对......
  • Android系统给所有apk默认权限
        Android系统的定制开发中,经常会有客户要求赋予他们提供的应用程序(APK)所默认的所有权限;百度上有多种版本可以给与默认权限,但是经过博主试验,都是比较复杂麻烦的操作;在这里博主给出一个简单方便方法,直接上代码;---a/frameworks/base/services/core/java/com/android/......
  • 使用ClassLoader.getSystemResource更新上线后空指针异常
     目录 问题描述:原问题代码:问题原因以及解决思路:解决方法:问题描述:项目中使用到一个功能,于是在资源路径下加了点依赖包:更新上线后,发现使用ClassLoader.getSystemResource("dependencies")找不到依赖包原问题代码:URLresourceURL=ClassLoader.getSystemResource(......
  • 在Android开发中,如何使用SharedPreferences(简称SP)一个轻量级的数据存储方式
    目录全局SharedPreferences工具类代码说明:如何使用这个工具类?在Android开发中,SharedPreferences(简称SP)是一个轻量级的数据存储方式,常用于保存应用的配置信息或少量的数据。为了便于在全局使用,可以将其封装到一个工具类中。以下是一个带有详细中文注释的全局SharedPrefere......