知识总结之 插件化基础ClassLoader

安卓插件化技术已经作为一个优秀的合格研发必备要求,学习和掌握现有不同种类动态加载方案 是提升个人技术深度有效途径。

插件化基础 ClassLoader

ClassLoader是什么?

ClassLoader 是将java编译后的字节码加载到虚拟机内存中的用到工具类。

Android平台虚拟机Dalvik/ART可以运行的字节码为.dex文件,Java平台JVM虚拟机可以被加载的字节码为.class文件。针对Android平台,若我们用ClassLoader加载指定地方的.dex,并且可以扩展ClassLoader实现定制加载器,就可以实现动态加载代码的目的。

dex文件也是由jar转化而来,android提供了转化命令:

dx --dex --output=target.dex origin.jar // target.dex就是我们要的了

ClassLoader实例

Java虚拟机中类加载器:

系统默认三个主要的类加载器,每个类负责加载特定位置的类:
BootStrap,ExtClassLoader,AppClassLoader

  • BootStrap 引导类加载器,用来加载java核心库(jre/lib/rt.jar),并非继承ClassLoader,C代码实现。
  • ExtClassLoader 扩展类加载器,用来加载java扩展库中的类(jre/lib/ext/*.jar)。
  • AppClassLoader 系统类加载器,根据类路径(ClassPath)来加载java类,java应用层类都是该类加载。

用IntelliJ 创建java虚拟机代码

public class Run {
    public static void main(String args[]){
        StringBuilder stringBuilder = new StringBuilder();
        ClassLoader loader = Run.class. getClassLoader();
        while (loader != null){
            stringBuilder.append(loader.toString());
            stringBuilder.append("\n##");
            loader = loader.getParent();
        }
        System.out.println("ClassLoader:"+stringBuilder);
    }
}

打印信息中,可以看到加载器类的层级关系。

##ClassLoader:sun.misc.Launcher$AppClassLoader@18b4aac2
##sun.misc.Launcher$ExtClassLoader@511d50c0

Process finished with exit code 0

安卓虚拟机Dalvik/ART类加载器

image.png

安卓平台虚拟机的类加载器实例和java不同,但都是继承自ClassLoader的。

  • BootClassLoader
    和java虚拟机中不同的是BootClassLoader是ClassLoader内部类,由java代码实现而不是c++实现,是Android平台上所有ClassLoader的最终parent,这个内部类是包内可见,所以我们没法使用。

  • URLClassLoader
    只能用于加载jar文件,但是由于 dalvik 不能直接识别jar,所以在 Android 中无法使用这个加载器。

  • BaseDexClassLoader
    PathClassLoader和DexClassLoader都继承自BaseDexClassLoader,其中的主要逻辑都是在BaseDexClassLoader完成的。这些源码在java/dalvik/system中。

  • DexClassLoader
    DexClassLoader支持加载APK、DEX和JAR,也可以从SD卡进行加载。动态加载主要用到了该类特性,来动态加载不同类型压缩包里的或这直接dex文件代码。

  • PathClassLoader

public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }

    public PathClassLoader(String dexPath, String libraryPath,
            ClassLoader parent) {
        super(dexPath, null, libraryPath, parent);
    }
    
     private static DexFile loadDexFile(File file, File optimizedDirectory)
            throws IOException {
        if (optimizedDirectory == null) {
            return new DexFile(file);
        } else {
            String optimizedPath = optimizedPathFor(file, optimizedDirectory);
            return DexFile.loadDex(file.getPath(), optimizedPath, 0);
        }
    }
}

由于构造参数optimizedDirectory指定为null,会直接使用dex文件原有的路径来创建DexFile对象。也就是说在dalvik虚假机上PathClassLoader无法加载外部的动态代码。

类加载过程分析

JVM中ClassLoader通过defineClass方法加载jar里面的Class,而Android平台中这个方法被弃用了。取而代之的是loadClass方法。

    @Deprecated
    protected final Class<?> defineClass(byte[] b, int off, int len)
        throws ClassFormatError{
        throw new UnsupportedOperationException("can't load this type of class file");
    }

分析loadClass

     protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                }
            }
            return c;
    }

loadClass代码分析可以看出,加载前会看缓存中是否已经加载,没有加载则委托为parent,如果parent没有加载到,这child加载器开始findClass加载。这种加载方式被称为双亲代理模型加载

特点:如果一个类被位于树根的ClassLoader加载过,那么在以后整个系统的生命周期内,这个类永远不会被重新加载。共享功能,一些Framework层级的类一旦被顶层的ClassLoader加载过就缓存在内存里面,以后任何地方用到都不需要重新加载。

除此之外还有隔离功能,不同继承路线上的ClassLoader加载的类肯定不是同一个类,这样的限制避免了用户自己的代码冒充核心类库的类访问核心类库包可见成员的情况。

image.png

层级关系中父类没有加载到时,子类开始findClass

@Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class clazz = pathList.findClass(name);
        if (clazz == null) {
            throw new ClassNotFoundException(name);
        }
        return clazz;
    }
    
    public Class findClass(String name) {
        for (Element element : dexElements) {
            DexFile dex = element.dexFile;
            if (dex != null) {
                Class clazz = dex.loadClassBinaryName(name, definingContext);
                if (clazz != null) {
                    return clazz;
                }
            }
        }
        return null;
    }
    
    public Class loadClassBinaryName(String name, ClassLoader loader) {
        return defineClass(name, loader, mCookie);
    }
    
    private native static Class defineClass(String name, ClassLoader loader, int cookie);

可以看出,BaseDexClassLoader中有个pathList对象,pathList中包含一个DexFile的数组dexElements,热修复原理也是修改该数组加载顺序来实现,dexPath传入的原始dex(.apk,.zip,.jar等)文件在optimizedDirectory文件夹中生成相应的优化后的odex文件,dexElements数组就是这些odex文件的集合,如果不分包一般这个数组只有一个Element元素,也就只有一个DexFile文件,而对于类加载呢,就是遍历这个集合,通过DexFile去寻找。最终调用native方法的defineClass。

扩展ClassLoader实现定制化

可以重载loadClass方法并改写类的加载逻辑,我们可以通过重写loadClass方法避开双亲代理的框架,这样一来就可以在重新加载已经加载过的类,也可以在加载类的时候注入一些代码。

安卓平台特有组件(activity,service等)的加载是否可以直接加载呢?答案肯定是否的,这些组件都是需要在清单文件中注册,然后才会被系统反射加载,并基于回调给予组件生命周期。

那我们想要加载一个安卓特有组件时,需要解决生命周期管理问题,才能正确的被系统调起来。

——————
欢迎转载,请标明出处:常兴E站 www.canking.win

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容