安卓安全基础——android类加载的分析与实现
字数 1594 2025-08-22 12:23:30
Android类加载机制分析与实现
1. 前言
在Android系统中,通过使用动态加载类可以实现加载插件和第一代整体加壳。本文旨在深入分析动态加载dex的原理及实现方式。
2. 基础知识
2.1 ClassLoader体系
Android中的ClassLoader是一个用于加载类文件的对象,主要作用是将Java类的字节码加载到内存中,并创建对应的Class对象。Android系统中有三种基本类加载器:
-
BootClassLoader (启动类加载器)
- 负责加载Android系统启动时所需的核心类库
- 是系统级别的类加载器,开发者无法直接使用
-
PathClassLoader (路径类加载器)
- 主要用于加载Android应用程序的本地类和库文件
- 只能加载已经安装到设备上的类文件,不能加载外部存储中的类文件
-
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 双亲委派机制
双亲委派机制是指当要加载一个类时,先让父类加载器去尝试加载。如果父类加载器找不到这个类,子类加载器才会去加载。这种机制可以保证:
- 核心类优先被系统的类加载器加载
- 避免重复加载和混乱
- 增强系统稳定性和安全性
双亲委派验证代码
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. 关键问题解析
-
为什么直接使用DexClassLoader加载组件类没有生命周期?
- Android系统对四大组件有特定的启动流程和生命周期管理机制
- 直接使用DexClassLoader加载的类不会被系统纳入正常管理流程
- 替换LoadedApk中的mClassLoader后,系统会使用自定义加载器加载组件类,从而触发正常生命周期
-
父加载类与父类的区别
- PathClassLoader和DexClassLoader的父类是BaseDexClassLoader
- PathClassLoader的父加载类是BootClassLoader
- 双亲委派机制中"双亲"指的是父加载器,而非继承关系中的父类
5. 总结
Android类加载机制的核心要点:
- 三种基本类加载器各司其职,分工明确
- 双亲委派机制保障了类加载的安全性和稳定性
- 动态加载非组件类相对简单,直接使用DexClassLoader即可
- 动态加载组件类需要修改系统类加载机制,有两种主要实现方式
- 不同Android版本实现细节可能有所差异,需要注意兼容性
通过深入理解Android类加载机制,开发者可以实现插件化、热修复等高级功能,但同时也需要注意安全性和稳定性问题。