安卓安全基础——android类加载的分析与实现
字数 1594 2025-08-22 12:23:30

Android类加载机制分析与实现

1. 前言

在Android系统中,通过使用动态加载类可以实现加载插件和第一代整体加壳。本文旨在深入分析动态加载dex的原理及实现方式。

2. 基础知识

2.1 ClassLoader体系

Android中的ClassLoader是一个用于加载类文件的对象,主要作用是将Java类的字节码加载到内存中,并创建对应的Class对象。Android系统中有三种基本类加载器:

  1. BootClassLoader (启动类加载器)

    • 负责加载Android系统启动时所需的核心类库
    • 是系统级别的类加载器,开发者无法直接使用
  2. PathClassLoader (路径类加载器)

    • 主要用于加载Android应用程序的本地类和库文件
    • 只能加载已经安装到设备上的类文件,不能加载外部存储中的类文件
  3. DexClassLoader (动态类加载器)

    • 可以加载外部存储中的dex文件、jar文件或包含dex文件的zip压缩包
    • 相比PathClassLoader更加灵活,可以加载来自不同来源的类文件

类加载器验证代码

public void testClassLoader() {
    // 获取MainActivity的类加载器
    ClassLoader pathClassLoader = MainActivity.class.getClassLoader();
    Log.d("xz_aliyun", pathClassLoader.toString());
    
    // 获取String类的类加载器
    ClassLoader bootClassLoader = String.class.getClassLoader();
    Log.d("xz_aliyun", bootClassLoader.toString());
}

运行结果:

  • MainActivity的加载器是PathClassLoader
  • String类的加载器是BootClassLoader

类加载器层级遍历

public void testClassLoader() {
    ClassLoader classLoader = MainActivity.class.getClassLoader();
    Log.d("xz_aliyun", classLoader.toString());
    
    ClassLoader parent = classLoader.getParent();
    while (parent != null) {
        Log.d("xz_aliyun", "parent->" + parent.toString());
        parent = parent.getParent();
    }
}

DexClassLoader创建

DexClassLoader构造函数需要四个参数:

  • String dexPath: 指定要加载的类所在的dex文件位置
  • String optimizedDirectory: 指定优化后的dex文件的输出目录路径
  • String librarySearchPath: 指定要搜索的本地库的路径
  • ClassLoader parent: 指定DexClassLoader的父类加载器

创建示例:

public void testClassLoader(Context context, String dexfilepath) {
    File optfile = context.getDir("opt_dex", Context.MODE_PRIVATE);
    File libfile = context.getDir("lib_path", Context.MODE_PRIVATE);
    
    DexClassLoader dexClassLoader = new DexClassLoader(
        dexfilepath, 
        optfile.getAbsolutePath(), 
        libfile.getAbsolutePath(), 
        MainActivity.class.getClassLoader()
    );
}

2.2 双亲委派机制

双亲委派机制是指当要加载一个类时,先让父类加载器去尝试加载。如果父类加载器找不到这个类,子类加载器才会去加载。这种机制可以保证:

  1. 核心类优先被系统的类加载器加载
  2. 避免重复加载和混乱
  3. 增强系统稳定性和安全性

双亲委派验证代码

public void testParentDelegationMechanism() {
    ClassLoader classloader = MainActivity.class.getClassLoader();
    try {
        Class StringClass = classloader.loadClass("java.lang.String");
        Log.d("xz_aliyun", "load StringClass success!" + classloader.toString());
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        Log.d("xz_aliyun", "load StringClass fail!" + classloader.toString());
    }
}

运行结果:PathClassLoader可以成功加载String.class类,因为双亲委派机制下,请求会被委派给BootClassLoader处理。

3. 动态加载类实现

3.1 动态加载非组件类

插件类示例

public class TestClass {
    public void testClassLoader() {
        Log.d("xz_aliyun", "=testClassLoader is called=");
    }
}

动态加载实现

public void testClassLoader(Context context, String dex_file_path) {
    File opt_file = context.getDir("opt_dex", Context.MODE_PRIVATE);
    File lib_file = context.getDir("lib_path", Context.MODE_PRIVATE);
    
    // 实例化DexClassLoader
    DexClassLoader dexClassLoader = new DexClassLoader(
        dex_file_path, 
        opt_file.getAbsolutePath(), 
        lib_file.getAbsolutePath(), 
        MainActivity.class.getClassLoader()
    );
    
    try {
        // 加载类
        Class<?> clazz = dexClassLoader.loadClass("com.demo.testdex.TestClass");
        // 创建实例
        Object obj = clazz.newInstance();
        // 获取方法
        Method testClassLoader = clazz.getDeclaredMethod("testClassLoader");
        // 调用方法
        testClassLoader.invoke(obj);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

权限处理

Android 6.0+需要处理存储权限:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
private boolean checkExternalStoragePermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) 
            != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(
                new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 
                REQUEST_READ_EXTERNAL_STORAGE
            );
            return false;
        }
    }
    return true;
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_READ_EXTERNAL_STORAGE) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            Log.d(TAG, "Permission granted.");
            Context applicationContext = getApplicationContext();
            testClassLoader(applicationContext, "/sdcard/class.dex");
        } else {
            Log.e(TAG, "Permission denied.");
        }
    }
}

3.2 动态加载组件类

方法一:替换LoadedApk中的mClassLoader

public void replaceClassLoader(ClassLoader classLoader) {
    try {
        // 加载ActivityThread类
        Class<?> activityThreadClazz = classLoader.loadClass("android.app.ActivityThread");
        // 获取currentActivityThread方法
        Method currentActivityThread = activityThreadClazz.getDeclaredMethod("currentActivityThread");
        currentActivityThread.setAccessible(true);
        // 获取ActivityThread对象
        Object activityThreadObj = currentActivityThread.invoke(null);
        // 获取mPackages字段
        Field mPackages = activityThreadClazz.getDeclaredField("mPackages");
        mPackages.setAccessible(true);
        ArrayMap mPobj = (ArrayMap) mPackages.get(activityThreadObj);
        // 获取LoadedApk对象
        WeakReference wr = (WeakReference) mPobj.get(this.getPackageName());
        Object loadedApkobj = wr.get();
        // 获取LoadedApk类
        Class<?> loadedApkClazz = classLoader.loadClass("android.app.LoadedApk");
        // 获取mClassLoader字段
        Field mClassLoader = loadedApkClazz.getDeclaredField("mClassLoader");
        mClassLoader.setAccessible(true);
        // 替换类加载器
        mClassLoader.set(loadedApkobj, classLoader);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

使用方式:

public void testClassLoader(Context context, String dex_file_path) {
    File opt_file = context.getDir("opt_dex", Context.MODE_PRIVATE);
    File lib_file = context.getDir("lib_path", Context.MODE_PRIVATE);
    
    // 实例化DexClassLoader
    DexClassLoader dexClassLoader = new DexClassLoader(
        dex_file_path, 
        opt_file.getAbsolutePath(), 
        lib_file.getAbsolutePath(), 
        MainActivity.class.getClassLoader()
    );
    
    // 替换类加载器
    replaceClassLoader(dexClassLoader);
    
    // 加载并启动Activity
    Class<?> secondActivityclazz = dexClassLoader.loadClass("com.demo.testdex.SecondActivity");
    Intent intent = new Intent(MainActivity.this, secondActivityclazz);
    startActivity(intent);
}

方法二:修改类加载器层级

public void startTestActivity(Context context, String dex_file_path) {
    File opt_file = context.getDir("opt_dex", 0);
    File lib_file = context.getDir("lib_path", 0);
    
    ClassLoader pathClassLoader = MainActivity.class.getClassLoader();
    ClassLoader bootClassLoader = MainActivity.class.getClassLoader().getParent();
    
    // 实例化DexClassLoader,父加载器设为BootClassLoader
    DexClassLoader dexClassLoader = new DexClassLoader(
        dex_file_path, 
        opt_file.getAbsolutePath(), 
        lib_file.getAbsolutePath(), 
        bootClassLoader
    );
    
    // 修改PathClassLoader的父加载器为DexClassLoader
    try {
        Field parent = ClassLoader.class.getDeclaredField("parent");
        parent.setAccessible(true);
        parent.set(pathClassLoader, dexClassLoader);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    
    // 加载并启动Activity
    Class<?> clazz = dexClassLoader.loadClass("com.demo.testdex.SecondActivity");
    startActivity(new Intent(MainActivity.this, clazz));
}

4. 关键问题解析

  1. 为什么直接使用DexClassLoader加载组件类没有生命周期?

    • Android系统对四大组件有特定的启动流程和生命周期管理机制
    • 直接使用DexClassLoader加载的类不会被系统纳入正常管理流程
    • 替换LoadedApk中的mClassLoader后,系统会使用自定义加载器加载组件类,从而触发正常生命周期
  2. 父加载类与父类的区别

    • PathClassLoader和DexClassLoader的父类是BaseDexClassLoader
    • PathClassLoader的父加载类是BootClassLoader
    • 双亲委派机制中"双亲"指的是父加载器,而非继承关系中的父类

5. 总结

Android类加载机制的核心要点:

  1. 三种基本类加载器各司其职,分工明确
  2. 双亲委派机制保障了类加载的安全性和稳定性
  3. 动态加载非组件类相对简单,直接使用DexClassLoader即可
  4. 动态加载组件类需要修改系统类加载机制,有两种主要实现方式
  5. 不同Android版本实现细节可能有所差异,需要注意兼容性

通过深入理解Android类加载机制,开发者可以实现插件化、热修复等高级功能,但同时也需要注意安全性和稳定性问题。

Android类加载机制分析与实现 1. 前言 在Android系统中,通过使用动态加载类可以实现加载插件和第一代整体加壳。本文旨在深入分析动态加载dex的原理及实现方式。 2. 基础知识 2.1 ClassLoader体系 Android中的ClassLoader是一个用于加载类文件的对象,主要作用是将Java类的字节码加载到内存中,并创建对应的Class对象。Android系统中有三种基本类加载器: BootClassLoader (启动类加载器) 负责加载Android系统启动时所需的核心类库 是系统级别的类加载器,开发者无法直接使用 PathClassLoader (路径类加载器) 主要用于加载Android应用程序的本地类和库文件 只能加载已经安装到设备上的类文件,不能加载外部存储中的类文件 DexClassLoader (动态类加载器) 可以加载外部存储中的dex文件、jar文件或包含dex文件的zip压缩包 相比PathClassLoader更加灵活,可以加载来自不同来源的类文件 类加载器验证代码 运行结果: MainActivity的加载器是PathClassLoader String类的加载器是BootClassLoader 类加载器层级遍历 DexClassLoader创建 DexClassLoader构造函数需要四个参数: String dexPath : 指定要加载的类所在的dex文件位置 String optimizedDirectory : 指定优化后的dex文件的输出目录路径 String librarySearchPath : 指定要搜索的本地库的路径 ClassLoader parent : 指定DexClassLoader的父类加载器 创建示例: 2.2 双亲委派机制 双亲委派机制是指当要加载一个类时,先让父类加载器去尝试加载。如果父类加载器找不到这个类,子类加载器才会去加载。这种机制可以保证: 核心类优先被系统的类加载器加载 避免重复加载和混乱 增强系统稳定性和安全性 双亲委派验证代码 运行结果:PathClassLoader可以成功加载String.class类,因为双亲委派机制下,请求会被委派给BootClassLoader处理。 3. 动态加载类实现 3.1 动态加载非组件类 插件类示例 动态加载实现 权限处理 Android 6.0+需要处理存储权限: 3.2 动态加载组件类 方法一:替换LoadedApk中的mClassLoader 使用方式: 方法二:修改类加载器层级 4. 关键问题解析 为什么直接使用DexClassLoader加载组件类没有生命周期? Android系统对四大组件有特定的启动流程和生命周期管理机制 直接使用DexClassLoader加载的类不会被系统纳入正常管理流程 替换LoadedApk中的mClassLoader后,系统会使用自定义加载器加载组件类,从而触发正常生命周期 父加载类与父类的区别 PathClassLoader和DexClassLoader的父类是BaseDexClassLoader PathClassLoader的父加载类是BootClassLoader 双亲委派机制中"双亲"指的是父加载器,而非继承关系中的父类 5. 总结 Android类加载机制的核心要点: 三种基本类加载器各司其职,分工明确 双亲委派机制保障了类加载的安全性和稳定性 动态加载非组件类相对简单,直接使用DexClassLoader即可 动态加载组件类需要修改系统类加载机制,有两种主要实现方式 不同Android版本实现细节可能有所差异,需要注意兼容性 通过深入理解Android类加载机制,开发者可以实现插件化、热修复等高级功能,但同时也需要注意安全性和稳定性问题。