什么是class loader,对于AndroID来说,class loader主要做两件事情
加载dex文件根据class path加载并返回对应的Class<?>对象BaseDexClassLoaderAndroID类加载的基础实现类其实是BaseDexClassLoader,看其构造函数
public BaseDexClassLoader(String dexPath, file optimizedDirectory, String libraryPath, ClassLoader parent) { super(parent); this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); }
dexPath 要加载的dex文件列表,多个文件路径用 : 分割optimizedDirectory 存放dex优化后的odex文件的目录libraryPath 库路径,多个也是用 :分割,如果app没有native library,则为nullparent 父class loader,用于双亲委派(parent delegation)在构造函数内,基于如上参数创建DexPathList,DexPathList内部主要做了:
调用makeDexElements,根据上面提供的dex文件列表依次创建Dexfile和Element,并保存到数组BaseDexClassLoader创建完成后,就可以调用loadClass按如下顺序加载对应的class了
如果parent delegation不为null,尝试调用parent.loadClass(name, false)获取接着调用self.findClass继续调用this.pathList.findClass, pathList会依次从dexElements获取Dexfile来尝试获取class,如果成功就返回PathClassLoader和DexClassLoader这两个类都派生子BaseDexClassLoader,有什么区别?看构造函数就知道了
//PathClassLoader constructor public PathClassLoader(String dexPath, ClassLoader parent) { super(dexPath, null, null, parent); } //DexClassLoader constructor public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, new file(optimizedDirectory), libraryPath, parent); }
二者主要区别是,而DexClassLoader可以设置optimizedDirectory,而PathClassLoader不能,必须使用默认的,也就是系统默认路径/data/dalvik-cache
所以,DexClassLoader能加载并optimize任意的dex文件,PathClassLoader则用于加载系统和已安装app的dex文件
为什么要支持MultIDex?看了上面的介绍,我们知道androID class loader默认是支持从多个dexes文件列表中依次尝试加载class类的,为什么要支持multi dex加载呢?
这个是历史遗留问题
Unable to execute dex: method ID not in [0, 0xffff]: 65536Conversion to Dalvik format Failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536
AndroID VM在加载并解析dex文件后,在执行method时所需的某个变量(具体我也不清楚)类型为short,也就是16bit, 这就限制了dex包含的method数量为65536,超过就不行了(是不是觉得好坑?你只能说近十来年,硬件的进化远远超出了当时设计者的想象)
随着app功能越来越多,生成的dex method总数终归是要超过64k的,所以,为了解决这个限制,必须要将生成的dex做拆分,这就是class loader支持MultIDex的原因
MultIDex打包Dex打包拆分目前通用的解决方案是使用官方的multIDex support library
defaultConfig { ... // Enabling multIDex support. multIDexEnabled true}
添加依赖
implementation com.androID.support:multIDex:1.0.2
将multIDexEnabled开关打开,这样在编译打包时,如果生成的dex method数量超过65536,会自动将其拆分,然后打包到apk的根目录
classes.dexclasses1.dexclasses(1…N).dex其中classes.dex是main dex,其他的是secondary dexes,很明显,classes.dex会作为入口dex被加载,这里又产生一个疑问,在编译时,如何确定哪些类会被打入classes.dex?
在AndroID sdk build tool目录下,会存在mainDexClasses脚本,用于配置哪些类需要被打入classes.dex
下面是build-tools/22.0.1/mainDexClasses.rules的内容
-keep public class * extends androID.app.Instrumentation { <init>(); } -keep public class * extends androID.app.Application { <init>(); voID attachBaseContext(androID.content.Context); } -keep public class * extends androID.app.Activity { <init>(); } -keep public class * extends androID.app.Service { <init>(); } -keep public class * extends androID.content.ContentProvIDer { <init>(); } -keep public class * extends androID.content.broadcastReceiver { <init>(); } -keep public class * extends androID.app.backup.BackupAgent { <init>(); }# We need to keep all annotation classes because proguard does not trace annotation attribute# it just filter the annotation attributes according to annotation classes it already kept. -keep public class * extends java.lang.annotation.Annotation { *; }
很明显,四大组件和Application等都被打入了main dex
安装加载在5.0系统之后,AndroID系统在安装apk时,就会对MultIDex做合成并放置到
/data/dalvik-cache/apk***@classes.dex
这样App启动时就不需要再做MultIDex装载,这在一定程度上会提高App的启动速度
不过在5.0系统以前,如果apk包含多dex,在安装完成后dex分布如下
classes.dex main dex文件放置于/data/dalvik-cache/apk***@classes.dexsecondary dexes会被放置于 /data/data/package/code_cache/secondary-dexes/下然后在apk启动时,通过在调用MultIDex.install(this)来完成对secondary dexes的加载
public class HelloMultIDexApplication extends Application { @OverrIDe protected voID attachBaseContext(Context base) { super.attachBaseContext(base); MultIDex.install(this); }}
在MultIDex.install(this)成功之前,任何对secondary-dexes内类的访问,都会触发class not found exception
接着简单看下MultIDex.install(this)实现原理
public static voID install(Context context) { Log.i("MultIDex", "Installing application"); if (IS_VM_MulTIDEX_CAPABLE) { Log.i("MultIDex", "VM has multIDex support, MultIDex support library is Disabled."); } else if (VERSION.SDK_INT < 4) { throw new RuntimeException("MultIDex installation Failed. SDK " + VERSION.SDK_INT + " is unsupported. Min SDK version is " + 4 + "."); } else { try { ApplicationInfo applicationInfo = getApplicationInfo(context); if (applicationInfo == null) { Log.i("MultIDex", "No ApplicationInfo available, i.e. running on a test Context: MultIDex support library is Disabled."); return; } doInstallation(context, new file(applicationInfo.sourceDir), new file(applicationInfo.dataDir), "secondary-dexes", ""); } catch (Exception var2) { Log.e("MultIDex", "MultIDex installation failure", var2); throw new RuntimeException("MultIDex installation Failed (" + var2.getMessage() + ")."); } Log.i("MultIDex", "install done"); } }
doInstallation主要调用getDexDir拿到/data/data/package/code_cache目录,接着调用installSecondaryDexes安装该目录下的所有dex,原理也很简单,最终就是通过反射拿到当前class loader的pathList(DexPathList),接着调用其静态函数makeDexElements根据如上dex dir目录下dex List创建Element数组,最后将其追加到pathList已有Element数组
private static voID expandFIEldArray(Object instance, String fIEldname, Object[] extraElements) throws NoSuchFIEldException, IllegalArgumentException, illegalaccessexception { FIEld jlrFIEld = findFIEld(instance, fIEldname); Object[] original = (Object[])((Object[])jlrFIEld.get(instance)); Object[] combined = (Object[])((Object[])Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length)); System.arraycopy(original, 0, combined, 0, original.length); System.arraycopy(extraElements, 0, combined, original.length, extraElements.length); jlrFIEld.set(instance, combined); }
参考文档Android拆分与加载Dex的多种方案对比
預防 Android Dex 64k Method Size Limit
以上是内存溢出为你收集整理的Android ClassLoader介绍全部内容,希望文章能够帮你解决Android ClassLoader介绍所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)