class, classloder, dex 详解

class, classloder, dex 详解

大家好,又见面了,我是全栈君。

class与dex文件

什么是class文件

class文件是一种能够被JVM识别,加载并且执行的文件格式。

 

class文件的作用

class文件的作用是记录一个类文件的所有信息。

例如记住了当前类的引用this、父类super等等。class文件记录的信息往往比java文件多。

 

class文件的结构

  • 8位字节的二进制流文件
  • 各个数据紧密排列,无间隙,减少了文件体积,加快加载速度
  • 每个类或者接口单独占据一个class文件,每个类单独管理,没有交叉

 

class文件的弊端

  • 内存占用大,不适合于移动端
  • 堆栈的加载模式导致加载速度慢
  • 文件IO操作多,类查找慢

 

什么是dex文件

能够被DVM或者Art虚拟机执行并且加载的文件格式。

 

dex文件的作用

dex文件的作用是记录整个工程(通常是一个Android工程)的所有类文件的信息。

 

dex文件的结构

  • 8位字节的二进制流文件
  • 各个数据紧密排列,无间隙,减少了文件体积,加快加载速度
  • 整个工程的类信息都存放在一个dex文件中(不考虑dex分包的情况下)

 

class文件与dex文件的比较

  • 本质上都是一样的,都是二进制流文件格式,dex文件是从class文件演变而来的
  • class文件存在冗余信息,dex文件则去掉了冗余,并且整合了整个工程的类信息。

结构对比图如下:

 
class, classloder, dex 详解

 

类加载机制是做热修复以及插件化的很重要的理论基础。

 

Java中的ClassLoader回顾

如下图所示:

 
class, classloder, dex 详解

ClassLoader的特性

ClassLoader的主要特性是双亲委托机制,即加载一个类的时候,先判断已经存在的类是否被加载过,如果没有,先去委托父亲去加载。如果父亲都没有加载成功的话,那么最终由自己加载。最终这个类最终没有合适的CLassLoader加载,那么就会抛出异常,相关的ClassLoader源码如下:

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
        // 先判断这个类是否已经被加载过
        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;
}

其中CLassLoader的findClass方法是空实现,需要自己继承然后实现:
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

 

这种双亲委托机制有两种好处:

  1. 实现类加载的共享功能,提升类加载的效率。

  2. 实现类加载的隔离功能,提升系统的安全性。比如,通过这种方式,系统的String类只能由系统的ClassLoader加载。

 

Android中的ClassLoader整体概述

Android中的ClassLoader的整体架构继承关系如下:

 
class, classloder, dex 详解

其中,ClassLoader分别为:

  • BootClassLoader:与Java中的Bootstrap ClassLoader类似,主要加载Android Framework中的字节码文件。
  • PathClassLoader:与Java中的App ClassLoader类似,主要加载已经安装到系统中的APK中的字节码文件。
  • DexClassLoader:与Java中的Customer ClassLoader类似,主要加载自定义路径下的APK或者JAR中的字节码文件(Android中主要是指dex文件,即classes.dex)。

其中,BaseDexClassLoader是PathClassLoader以及DexClassLoader的父类,PathClassLoader以及DexClassLoader的逻辑都在这个父类中实现。

BootClassLoader和PathClassLoader是一个普通的APP所需要的(定制ROM另外说),最基本的ClassLoader,通过下面的代码可以证明:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    ClassLoader cl = this.getClassLoader();
    Log.e(TAG, "onCreate: " + cl);
    while (cl.getParent() != null) {
        cl = cl.getParent();
        Log.e(TAG, "onCreate: " + cl);
    }

}

打印结果只有BootClassLoader和PathClassLoader:

09-06 14:29:01.571 3165-3165/com.nan.loadapkdemo E/MainActivity: onCreate: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.nan.loadapkdemo-1/base.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_dependencies_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_0_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_1_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_2_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_3_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_4_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_5_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_6_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_7_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_8_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_9_apk.apk"],nativeLibraryDirectories=[/data/app/com.nan.loadapkdemo-1/lib/x86, /system/lib, /vendor/lib]]]
09-06 14:29:01.571 3165-3165/com.nan.loadapkdemo E/MainActivity: onCreate: java.lang.BootClassLoader@b173b34

上面就分析完了classes.dex文件是如何通过ClassLoader的初始化装载进来的,下面继续分析一个类是如何通过PathClassLoader或者DexClassLoader进行加载的,这时候就需要看父类BaseDexClassLoader的findClass方法了:

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
    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;
}

这里面看到,实际上就是调用了PathList的findClass方法:

public Class findClass(String name, List<Throwable> suppressed) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;

        if (dex != null) {
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }
    }
    if (dexElementsSuppressedExceptions != null) {
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}

这个方法就是遍历初始化创建好的内部Element[]数组里面的DexFile对象,最终是通过DexFile的loadClassBinaryName进行加载的:

public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
    return defineClass(name, loader, mCookie, this, suppressed);
}

defineClass的实现如下:

private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                 DexFile dexFile, List<Throwable> suppressed) {
    Class result = null;
    try {
        result = defineClassNative(name, loader, cookie, dexFile);
    } catch (NoClassDefFoundError e) {
        if (suppressed != null) {
            suppressed.add(e);
        }
    } catch (ClassNotFoundException e) {
        if (suppressed != null) {
            suppressed.add(e);
        }
    }
    return result;
}

到最后,defineClassNative是一个native方法:

private static native Class defineClassNative(String name, ClassLoader loader, Object cookie, DexFile dexFile)

defineClassNative 是通过C/C++实现,这个方法去dex文件中查找指定name的类,并且拼接成Class字节码,返回给Java层。通过native实现是为了提高效率。

整个加载流程一气呵成,如下图所示:

class, classloder, dex 详解

实现动态加载

如下面所示,演示了最简单的动态加载的方案:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String apkPath = getExternalCacheDir().getAbsolutePath() + "/bundle.apk";
        loadApk(apkPath);
    }

    private void loadApk(String apkPath) {
        File optFile = getDir("opt", MODE_PRIVATE);
        // 通过DexClassLoader加载制定的APK文件
        DexClassLoader dexClassLoader = new DexClassLoader(apkPath, optFile.getAbsolutePath(), null, getClassLoader());
        try {
            // 通过反射去使用对象
            Class clz = dexClassLoader.loadClass("com.loubinfeng.www.boundle.Printer");
            if (clz != null) {
                Object instance = clz.newInstance();
                Method method = clz.getMethod("print");
                method.invoke(instance);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

实现动态加载的难点

实现动态加载难点比较多:

  • 资源的访问问题
  • 四大组件的生命周期管理问题
  • 插件ClassLoader的管理
  • so库的动态加载等等

参考文章:

http://www.jianshu.com/p/37cad7a901b1

http://www.jianshu.com/p/2eb518941681

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/107821.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 五大优秀的数据库设计工具[通俗易懂]

    五大优秀的数据库设计工具[通俗易懂]数据库的设计主要是针对一个特定的个环境,为了能够满足有效的数据存储和处理等要求,需要构造最优的数据库模式来建立数据库及其对应系统。数据库设计时根据用户的需求,在特定的数据库管理系统上设计和建立数据库的过程,是软件系统开发过程中的关键技术之一。在数据库领域内,通常把使用数据库的各类系统统称为数据库应用系统。数据库设计的目的是把软件系统中大量的数据按一定的模型组织起来,以实现方便、及时地存储、维护和检索等功能,是软件系统开发和建设的关键和重要组成部分之一,因此数据库设计往往比较复杂,最佳设计不可能一蹴而就,需

  • [Matlab]绘图颜色[通俗易懂]

    [Matlab]绘图颜色[通俗易懂][Matlab]绘图颜色修改或规定Matlab中几何图形的颜色,对颜色可以有四种描述方法,分别是:颜色名称、短名称、RGB三元组、十六进制颜色代码。Matlab中较美观的颜色(RGB三元组)%%适用于Matlab的RGB颜色[0.00,0.45,0.74]//蓝[0.85,0.33,0.10]//橙红[0.93,0.69,0.13]//橙黄[0.72,0.27,1]//淡紫[0.47,0.67,0.19]//淡绿……待补充Matlab语言%%对点scat

  • python lambda表达式 if_Python学习-lambda表达式

    python lambda表达式 if_Python学习-lambda表达式lambda只是一个表达式,函数体比def简单很多。lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。lambda表达式是起到一个函数速写的作用。允许在代码内嵌入一个函数的定义。#求三个数的和1f=lambdax,y,z:x+y+x2print(f(1,2,3))#434f=lambdax,y,z:x+y+z5print(…

    2022年10月18日
  • SpringBoot + mybatis 分页查询

    SpringBoot + mybatis 分页查询com.github.pagehelper.PageHelper是一款好用的开源免费的Mybatis第三方分页插件。使用的时候,只要简单配置,就可以在查询语句之后得到所需的分页信息。1:在pom.xml中引入依赖项。dependency>groupId>com.github.pagehelpergroupId>artifactId>pagehelperarti

  • linux crontab 每隔10秒执行一次[通俗易懂]

    linux crontab 每隔10秒执行一次[通俗易懂]linux下定时执行任务的方法在LINUX中你应该先输入crontab-e,然后就会有个vi编辑界面,再输入03**1/clearigame2内容到里面:wq保存退出。在LINUX中,周期执行的任务一般由cron这个守护进程来处理[ps-ef|grepcron]。cron读取一个或多个配置文件,这些配置文件中包含了命令行及其调用时间。cron的配置文件称为“…

    2022年10月21日
  • Java 正则表达式:语法讲解和常用表达式汇总

    Java 正则表达式:语法讲解和常用表达式汇总正则表达式定义了字符串的模式。正则表达式可以用来搜索、编辑或处理文本。正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别。

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号