Android ClassLoader介绍

Android ClassLoader介绍,第1张

概述文章目录AndroidClassLoader介绍BaseDexClassLoaderPathClassLoader和DexClassLoader为什么要支持MultiDex?MultiDex打包安装加载参考文档AndroidClassLoader介绍什么是classloader,对于Android来说,classloader主要做两件事情加载dex文件根据classpath加载并返回对

文章目录Android ClassLoader介绍BaseDexClassLoaderPathClassLoader和DexClassLoader为什么要支持MultiDex?MultiDex打包安装加载参考文档

AndroID ClassLoader介绍

什么是class loader,对于AndroID来说,class loader主要做两件事情

加载dex文件根据class path加载并返回对应的Class<?>对象BaseDexClassLoader

AndroID类加载的基础实现类其实是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介绍所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1109107.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-29
下一篇 2022-05-29

发表评论

登录后才能评论

评论列表(0条)

保存