4人参与 • 2025-07-08 • Android
在android开发中,classloader(类加载器)扮演着至关重要的角色,它负责将class文件加载到android虚拟机(art/dalvik)中,使得程序能够运行这些类。
理解classloader的加载机制,对于解决类冲突、实现热修复、插件化等高级功能有着重要意义。
类加载是java和android运行时环境的一个重要环节。
当程序需要使用某个类时,如果该类还没有被加载到内存中,classloader就会负责将该类的字节码(.class文件)从文件系统、网络或其他来源加载到内存中,并生成对应的class对象。
android的classloader机制基于java,但又有一些区别:
android中的classloader继承体系主要包括以下几个核心类:
这是所有类加载器的抽象基类,定义了类加载的基本接口和方法。
这是android中所有dex类加载器的基类,它扩展了classloader,专门用于加载dex文件或包含dex文件的apk、jar文件。
dexclassloader是最常用的类加载器之一,它可以从指定的路径加载dex文件,支持从sd卡等外部存储加载类,非常适合实现插件化和热修复功能。
pathclassloader是android应用默认使用的类加载器,它只能加载已经安装到系统中的apk文件(即/data/app目录下的apk),主要用于加载应用自身的类。
bootclassloader是android系统的根类加载器,它负责加载android系统核心库,如java.lang、android.os等包中的类。它是classloader的一个内部类,并且是单例的。
下面是android中主要classloader的继承关系图:
classloader └── basedexclassloader ├── dexclassloader └── pathclassloader
其中,bootclassloader是classloader的内部实现,没有显式地出现在这个继承链中。
android的classloader采用双亲委派模型来加载类,其工作流程如下:
这种模型的优点是:
下面是classloader中实现双亲委派模型的核心代码:
protected class<?> loadclass(string name, boolean resolve) throws classnotfoundexception { // 首先检查类是否已经被加载 class<?> c = findloadedclass(name); if (c == null) { try { // 如果父类加载器不为空,则委派给父类加载器加载 if (parent != null) { c = parent.loadclass(name, false); } else { // 如果父类加载器为空,则委派给bootclassloader加载 c = findbootstrapclassornull(name); } } catch (classnotfoundexception e) { // 父类加载器无法加载时,捕获异常但不做处理 } if (c == null) { // 父类加载器无法加载时,调用自身的findclass方法加载 c = findclass(name); } } return c; }
从这段代码可以看出,classloader在加载类时,首先会检查该类是否已经被加载,如果没有,则优先委派给父类加载器加载,只有当父类加载器无法加载时,才会调用自身的findclass方法进行加载。
在classloader中,findclass方法是一个模板方法,默认实现只是抛出classnotfoundexception,具体的类加载逻辑由子类实现:
protected class<?> findclass(string name) throws classnotfoundexception { throw new classnotfoundexception(name); }
例如,basedexclassloader重写了findclass方法,它通过dexpathlist对象来查找和加载类:
@override protected class<?> findclass(string name) throws classnotfoundexception { list<throwable> suppressedexceptions = new arraylist<throwable>(); // 通过pathlist查找类 class c = pathlist.findclass(name, suppressedexceptions); if (c == null) { classnotfoundexception cnfe = new classnotfoundexception("didn't find class \"" + name + "\" on path: " + pathlist); for (throwable t : suppressedexceptions) { cnfe.addsuppressed(t); } throw cnfe; } return c; }
basedexclassloader通过dexpathlist对象来管理和查找dex文件。dexpathlist内部维护了一个element数组,每个element对象代表一个dex文件或包含dex文件的目录。
dexelement是dexpathlist的内部类,它封装了一个dex文件或包含dex文件的目录。当需要加载某个类时,dexpathlist会按顺序遍历element数组,尝试在每个element中查找该类。
下面是dexpathlist中findclass方法的实现:
public class<?> findclass(string name, list<throwable> suppressed) { // 遍历dexelements数组 for (element element : dexelements) { class<?> clazz = element.findclass(name, definingcontext, suppressed); if (clazz != null) { return clazz; } } if (dexelementssuppressedexceptions != null) { suppressed.addall(arrays.aslist(dexelementssuppressedexceptions)); } return null; }
从这段代码可以看出,dexpathlist会按顺序遍历dexelements数组,调用每个element的findclass方法来查找类,一旦找到就立即返回,否则继续查找下一个element。
在实际开发中,我们可能需要自定义classloader来实现一些特殊需求,例如:
下面是一个简单的自定义classloader示例:
import dalvik.system.dexclassloader; import java.io.file; public class customclassloader extends dexclassloader { public customclassloader(string dexpath, string optimizeddirectory, string librarysearchpath, classloader parent) { super(dexpath, optimizeddirectory, librarysearchpath, parent); } @override protected class<?> loadclass(string name, boolean resolve) throws classnotfoundexception { // 打破双亲委派模型,优先加载指定包名下的类 if (name.startswith("com.example.plugin.")) { return findclass(name); } // 其他类仍然遵循双亲委派模型 return super.loadclass(name, resolve); } @override protected class<?> findclass(string name) throws classnotfoundexception { try { // 尝试从自定义路径加载类 return super.findclass(name); } catch (classnotfoundexception e) { // 自定义加载失败时,交给父类加载器处理 return getparent().loadclass(name); } } }
这个自定义classloader打破了双亲委派模型,优先加载指定包名下的类,其他类仍然遵循双亲委派模型。
下面是使用自定义classloader加载类的示例代码:
import java.io.file; public class classloaderexample { public static void main(string[] args) { try { // 插件apk文件路径 file apkfile = new file("/sdcard/plugin.apk"); // 优化后的dex文件存储路径 file optimizeddirectory = new file("/data/data/com.example.app/dex"); if (!optimizeddirectory.exists()) { optimizeddirectory.mkdirs(); } // 库文件搜索路径 string librarysearchpath = null; // 创建自定义classloader customclassloader classloader = new customclassloader( apkfile.getabsolutepath(), optimizeddirectory.getabsolutepath(), librarysearchpath, classloaderexample.class.getclassloader() ); // 加载插件类 class<?> pluginclass = classloader.loadclass("com.example.plugin.pluginclass"); // 创建实例并调用方法 object instance = pluginclass.newinstance(); java.lang.reflect.method method = pluginclass.getmethod("dosomething"); method.invoke(instance); } catch (exception e) { e.printstacktrace(); } } }
这个示例展示了如何使用自定义classloader加载外部apk中的类,并通过反射调用其方法。
热修复的核心思想是通过自定义classloader加载修复后的类,替换有问题的类。具体实现方式有多种,其中一种常见的方式是利用dexpathlist的dexelements数组:
下面是一个简单的热修复示例代码:
import java.lang.reflect.array; import java.lang.reflect.field; import dalvik.system.dexclassloader; import dalvik.system.pathclassloader; public class hotfixutil { public static void fix(context context, file dexfile) { try { // 获取应用的pathclassloader pathclassloader pathclassloader = (pathclassloader) context.getclassloader(); // 创建修复dex的dexclassloader file optimizeddirectory = new file(context.getcachedir(), "hotfix"); if (!optimizeddirectory.exists()) { optimizeddirectory.mkdirs(); } dexclassloader dexclassloader = new dexclassloader( dexfile.getabsolutepath(), optimizeddirectory.getabsolutepath(), null, pathclassloader ); // 获取pathclassloader的pathlist字段 field pathlistfield = getfield(pathclassloader.getclass(), "pathlist"); object pathlist = pathlistfield.get(pathclassloader); // 获取dexclassloader的pathlist字段 object fixpathlist = pathlistfield.get(dexclassloader); // 获取pathlist中的dexelements字段 field dexelementsfield = getfield(pathlist.getclass(), "dexelements"); object dexelements = dexelementsfield.get(pathlist); object fixdexelements = dexelementsfield.get(fixpathlist); // 合并dexelements数组,将修复的dex放在前面 object mergedelements = combinearray(fixdexelements, dexelements); // 将合并后的数组设置回pathlist dexelementsfield.set(pathlist, mergedelements); } catch (exception e) { e.printstacktrace(); } } private static field getfield(class<?> clazz, string fieldname) throws nosuchfieldexception { field field = clazz.getdeclaredfield(fieldname); field.setaccessible(true); return field; } private static object combinearray(object array1, object array2) { int length1 = array.getlength(array1); int length2 = array.getlength(array2); int newlength = length1 + length2; class<?> componenttype = array1.getclass().getcomponenttype(); object newarray = array.newinstance(componenttype, newlength); for (int i = 0; i < newlength; i++) { if (i < length1) { array.set(newarray, i, array.get(array1, i)); } else { array.set(newarray, i, array.get(array2, i - length1)); } } return newarray; } }
插件化的实现原理与热修复类似,也是通过自定义classloader加载外部插件apk中的类。不同的是,插件化需要解决更多的问题,如资源加载、activity生命周期管理等。
下面是一个简单的插件化框架核心代码示例:
import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.res.assetmanager; import android.content.res.resources; import dalvik.system.dexclassloader; import java.io.file; import java.lang.reflect.method; public class pluginmanager { private static pluginmanager instance; private context context; private dexclassloader dexclassloader; private resources resources; private packageinfo packageinfo; private pluginmanager(context context) { this.context = context.getapplicationcontext(); } public static pluginmanager getinstance(context context) { if (instance == null) { synchronized (pluginmanager.class) { if (instance == null) { instance = new pluginmanager(context); } } } return instance; } public void loadplugin(string pluginpath) { try { file pluginfile = new file(pluginpath); if (!pluginfile.exists()) { return; } // 创建插件的dexclassloader file optimizeddirectory = new file(context.getcachedir(), "plugin_dex"); if (!optimizeddirectory.exists()) { optimizeddirectory.mkdirs(); } dexclassloader = new dexclassloader( pluginpath, optimizeddirectory.getabsolutepath(), null, context.getclassloader() ); // 加载插件的资源 assetmanager assetmanager = assetmanager.class.newinstance(); method addassetpath = assetmanager.getclass().getmethod("addassetpath", string.class); addassetpath.invoke(assetmanager, pluginpath); resources = new resources( assetmanager, context.getresources().getdisplaymetrics(), context.getresources().getconfiguration() ); // 获取插件的packageinfo packagemanager packagemanager = context.getpackagemanager(); packageinfo = packagemanager.getpackagearchiveinfo( pluginpath, packagemanager.get_activities | packagemanager.get_services ); } catch (exception e) { e.printstacktrace(); } } public dexclassloader getclassloader() { return dexclassloader; } public resources getresources() { return resources; } public packageinfo getpackageinfo() { return packageinfo; } }
当不同的dex文件中存在相同包名和类名的类时,就会发生类冲突。解决类冲突的方法有:
不正确使用classloader可能会导致内存泄漏,特别是在activity中使用自定义classloader时。为避免内存泄漏,应注意:
不同android版本的classloader实现可能有所不同,特别是在处理dex文件和优化文件方面。在实现热修复和插件化时,需要考虑不同版本的兼容性问题。
android的classloader加载机制是一个复杂而重要的系统,它为应用的动态加载、热修复和插件化等高级功能提供了基础。理解classloader的工作原理和双亲委派模型,掌握dexpathlist和dexelement的结构,能够帮助我们更好地解决开发中的类加载问题,实现各种高级功能。
在实际开发中,我们可以根据具体需求自定义classloader,打破双亲委派模型,实现类的隔离和动态加载。同时,我们也要注意classloader可能带来的类冲突、内存泄漏和兼容性等问题。
到此这篇关于android classloader加载机制详解的文章就介绍到这了,更多相关android classloader加载内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论