Dalvik,ART与ODEX相爱相生

如果你有这样的问题:
1.Dalvik和ART的区别
2.DEX在Dalvik转化为ODEX和ART中转化为ODEX的过程有上面区别
3.multidex在dalvik上起作用,ART上使用的也是multidex么(如果不是的话在application中写入multidex.install会对apk启动造成影响么)

如果你比较“懒”,,懒得看老罗的源码分析,,,长篇大论
请“简要”看完以下“简要”内容

一:Dalvik和ART的区别#

Dalvik: Dalvik是Google公司自己设计用于Android平台的Java虚拟机它可以支持已转换为 .dex(即Dalvik Executable)格式的Java应用程序的运行,.dex格式是专为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。执行的是字节码,它是依靠Just-In-Time (JIT)机制去解释字节码
ART:即Android Runtime,google为了替代Dalvik专门为Android研发的。Android KK为开发者推出,L版本正式上线。比替代品更高效省电,执行的是本地机器码(也就是linux的ELF文件格式),依靠Ahead-Of-Time (AOT)机制

二.在不同平台DEX转化为ODEX的过程#

简化流程如下:


打包安装运行简化流程.png

这里参考的是
http://blog.csdn.net/luoshengyang/article/details/18006645

android安装过程源码分析:http://blog.csdn.net/luoshengyang/article/details/6747696
简单来说,就是Android系统通过PackageManagerService来安装APK,在安装的过程,PackageManagerService会通过另外一个类Installer的成员函数dexopt来对APK里面的dex字节码进行优化:

public final class Installer {  
    ......  
  
    public int dexopt(String apkPath, int uid, boolean isPublic) {  
        StringBuilder builder = new StringBuilder("dexopt");  
        builder.append(' ');  
        builder.append(apkPath);  
        builder.append(' ');  
        builder.append(uid);  
        builder.append(isPublic ? " 1" : " 0");  
        return execute(builder.toString());  
    }  
  
    ......  
}  

这个函数定义在文件frameworks/base/services/java/com/android/server/pm/Installer.java中。Installer通过socket向守护进程installd发送一个dexopt请求,这个请求是由installd里面的函数dexopt来处理的。详细过程请移步Android ART运行时无缝替换Dalvik虚拟机的过程分析

int dexopt(const char *apk_path, uid_t uid, int is_public)
{
    struct utimbuf ut;
    struct stat apk_stat, dex_stat;
    char out_path[PKG_PATH_MAX];
    char dexopt_flags[PROPERTY_VALUE_MAX];
    char persist_sys_dalvik_vm_lib[PROPERTY_VALUE_MAX];
    char *end;
    int res, zip_fd=-1, out_fd=-1;

    ......

    /* The command to run depend ones the value of persist.sys.dalvik.vm.lib */
    property_get("persist.sys.dalvik.vm.lib", persist_sys_dalvik_vm_lib, "libdvm.so");

    /* Before anything else: is there a .odex file?  If so, we have
     * precompiled the apk and there is nothing to do here.
     */
    sprintf(out_path, "%s%s", apk_path, ".odex");
    if (stat(out_path, &dex_stat) == 0) {
        return 0;
    }

    if (create_cache_path(out_path, apk_path)) {
        return -1;
    }

    ......

    out_fd = open(out_path, O_RDWR | O_CREAT | O_EXCL, 0644);

    ......

    pid_t pid;
    pid = fork();
    if (pid == 0) {
        ......

        if (strncmp(persist_sys_dalvik_vm_lib, "libdvm", 6) == 0) {
            run_dexopt(zip_fd, out_fd, apk_path, out_path, dexopt_flags);
        } else if (strncmp(persist_sys_dalvik_vm_lib, "libart", 6) == 0) {
            run_dex2oat(zip_fd, out_fd, apk_path, out_path, dexopt_flags);
        } else {
            exit(69);   /* Unexpected persist.sys.dalvik.vm.lib value */
        }
        exit(68);   /* only get here on exec failure */
    } 

    ......
}
……
static void run_dexopt(int zip_fd, int odex_fd, const char* input_file_name,
    const char* output_file_name, const char* dexopt_flags)
{
    static const char* DEX_OPT_BIN = "/system/bin/dexopt";
    static const int MAX_INT_LEN = 12;      // '-'+10dig+'\0' -OR- 0x+8dig
    char zip_num[MAX_INT_LEN];
    char odex_num[MAX_INT_LEN];

    sprintf(zip_num, "%d", zip_fd);
    sprintf(odex_num, "%d", odex_fd);

    ALOGV("Running %s in=%s out=%s\n", DEX_OPT_BIN, input_file_name, output_file_name);
    execl(DEX_OPT_BIN, DEX_OPT_BIN, "--zip", zip_num, odex_num, input_file_name,
        dexopt_flags, (char*) NULL);
    ALOGE("execl(%s) failed: %s\n", DEX_OPT_BIN, strerror(errno));
}

static void run_dex2oat(int zip_fd, int oat_fd, const char* input_file_name,
    const char* output_file_name, const char* dexopt_flags)
{
    static const char* DEX2OAT_BIN = "/system/bin/dex2oat";
    static const int MAX_INT_LEN = 12;      // '-'+10dig+'\0' -OR- 0x+8dig
    char zip_fd_arg[strlen("--zip-fd=") + MAX_INT_LEN];
    char zip_location_arg[strlen("--zip-location=") + PKG_PATH_MAX];
    char oat_fd_arg[strlen("--oat-fd=") + MAX_INT_LEN];
    char oat_location_arg[strlen("--oat-name=") + PKG_PATH_MAX];

    sprintf(zip_fd_arg, "--zip-fd=%d", zip_fd);
    sprintf(zip_location_arg, "--zip-location=%s", input_file_name);
    sprintf(oat_fd_arg, "--oat-fd=%d", oat_fd);
    sprintf(oat_location_arg, "--oat-location=%s", output_file_name);

    ALOGV("Running %s in=%s out=%s\n", DEX2OAT_BIN, input_file_name, output_file_name);
    execl(DEX2OAT_BIN, DEX2OAT_BIN,
          zip_fd_arg, zip_location_arg,
          oat_fd_arg, oat_location_arg,
          (char*) NULL);
    ALOGE("execl(%s) failed: %s\n", DEX2OAT_BIN, strerror(errno));
}

函数定义在frameworks/native/cmds/installd/commands.c中 函数dexopt首先是读取系统属性persist.sys.dalvik.vm.lib的值,接着在/data/dalvik-cache目录中创建一个odex文件。这个odex文件就是作为dex文件优化后的输出文件。再接下来,函数dexopt通过fork来创建一个子进程。如果系统属性persist.sys.dalvik.vm.lib的值等于libdvm.so,那么该子进程就会调用函数run_dexopt来将dex文件优化成odex文件。另一方面,如果系统属性persist.sys.dalvik.vm.lib的值等于libart.so,那么该子进程就会调用函数run_dex2oat来将dex文件翻译成oat文件,实际上就是将dex字节码翻译成本地机器码,并且保存在一个oat文件中。
函数run_dexopt通过调用/system/bin/dexopt来对dex字节码进行优化,而函数run_dex2oat通过调用/system/bin/dex2oat来将dex字节码翻译成本地机器码。注意,无论是对dex字节码进行优化,还是将dex字节码翻译成本地机器码,最终得到的结果都是保存在相同名称的一个odex文件里面的,但是前者对应的是一个dey文件(表示这是一个优化过的dex),后者对应的是一个oat文件(实际上是一个自定义的elf文件,里面包含的都是本地机器指令)。通过这种方式,原来任何通过绝对路径引用了该odex文件的代码就都不需要修改了。

三.oat文件格式#

借助罗大神的图我们可以知道,OAT文件本质上是一个ELF文件,因此在最外层它具有一般ELF文件的结构,例如它有标准的ELF文件头以及通过段(Section)来描述文件内容。







Paste_Image.png

OAT文件包含有两个特殊的段oatdata和oatexec,前者包含有用来生成本地机器指令的dex文件内容,后者包含有生成的本地机器指令,它们之间的关系通过储存在oatdata段前面的oat头部描述。

APK安装过程中生成的OAT文件的输入只有一个DEX文件,也就是来自于打包在要安装的APK文件里面的classes.dex文件。实际上,一个OAT文件是可以由若干个DEX生成的。这意味着在生成的OAT文件的oatdata段中,包含有多个DEX文件。详细分析请移步Android运行时ART加载OAT文件的过程分析

四.multidex加载odex,multidex和oat的关系

MultiDex在dalvik虚拟机上的简要安装过程:
将/data/app/apkName.apk路径下解压得到的classes2.dex, …, classesN.dex,依次写入到/data/data/pkgName/code_cache/secondary-dexes/apkName.apk.classes2.zip等zip文件的classes.dex中,并返回这个zip列表。然后针对这个zip列表执行安装过程,具体过程是,将这个要安装的zip列表加入BaseDexClassLoader的pathList实例的dexElements数组中,其中会针对各dex文件进行dex2opt优化。一旦加入到了dexElements数组中,程序启动的时候,ClassLoader会加载dexElements数组中的元素,从而实现multi dex的安装。

上面提到OAT文件可以由若干个dex生成,也就是不需要multidex去进行安装,但是multidex是application中
进行Install的,跟虚拟机关系不大。

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    MultiDex.install(this);
}

在install函数中在执行从提取dex文件列表前会做一些校验操作,其中包含检查APK是否已安装,若APK已安装,则不进行后续操作。检查SDK版本号,版本号大于20不能保证MultiDex可正常Work

Set var2 = installedApk;
                synchronized(installedApk) {
                    String apkPath = e.sourceDir;
                    if(installedApk.contains(apkPath)) {
                        return;
                    }

所以multidex在ART上不会影响程序的逻辑,它和ART没有关系~。。。。
multidex源码分析:MultiDex安装过程源码分析

小结:

从安装过程上来看
Java的代码实际上需要两次“转换”才可以在android设备上运行
一.PC端:.class->.dex->.apk
二.phone:dex->odex

区别在于第二步。
ART : .dex->.odex(机器码)(AOT  Ahead-Of-Time)
Dalvik: .dex->.odex(字节码)(JIT Just-In-Time)
机器码可直接执行,而字节码每次启动都需要执行将优化过的odex字节码再转换成机器码

ART优缺####

系统性能大幅提升
App启动、运行更快
减少每次启动的编译增加电池续航
存储占用更大
安装时间更长



ART、JIT、AOT、Dalvik之间有什么关系?

JIT与Dalvik

JIT是"Just In Time Compiler"的缩写,就是"即时编译技术",与Dalvik虚拟机相关。

怎么理解这句话呢?这要从Android的一些特性说起。

JIT是在2.2版本提出的,目的是为了提高Android的运行速度,一直存活到4.4版本,因为在4.4之后的ROM中,就不存在Dalvik虚拟机了。

我们使用Java开发android,在编译打包APK文件时,会经过以下流程

  • Java编译器将应用中所有Java文件编译为class文件
  • dx工具将应用编译输出的类文件转换为Dalvik字节码,即dex文件

之后经过签名、对齐等操作变为APK文件。

Dalvik虚拟机可以看做是一个Java VM,他负责解释dex文件为机器码,如果我们不做处理的话,每次执行代码,都需要Dalvik将dex代码翻译为微处理器指令,然后交给系统处理,这样效率不高。

为了解决这个问题,Google在2.2版本添加了JIT编译器,当App运行时,每当遇到一个新类,JIT编译器就会对这个类进行编译,经过编译后的代码,会被优化成相当精简的原生型指令码(即native code),这样在下次执行到相同逻辑的时候,速度就会更快。

当然使用JIT也不一定加快执行速度,如果大部分代码的执行次数很少,那么编译花费的时间不一定少于执行dex的时间。Google当然也知道这一点,所以JIT不对所有dex代码进行编译,而是只编译执行次数较多的dex为本地机器码。

有一点需要注意,那就是dex字节码翻译成本地机器码是发生在应用程序的运行过程中的,并且应用程序每一次重新运行的时候,都要做重做这个翻译工作,所以这个工作并不是一劳永逸,每次重新打开App,都需要JIT编译。

另外,Dalvik虚拟机从Android一出生一直活到4.4版本,而JIT在Android刚发布的时候并不存在,在2.2之后才被添加到Dalvik中。

ART与AOT

AOT是"Ahead Of Time"的缩写,指的就是ART(Anroid RunTime)这种运行方式。

前面介绍过,JIT是运行时编译,这样可以对执行次数频繁的dex代码进行编译和优化,减少以后使用时的翻译时间,虽然可以加快Dalvik运行速度,但是还是有弊病,那就是将dex翻译为本地机器码也要占用时间,所以Google在4.4之后推出了ART,用来替换Dalvik。

在4.4版本上,两种运行时环境共存,可以相互切换,但是在5.0+,Dalvik虚拟机则被彻底的丢弃,全部采用ART。

ART的策略与Dalvik不同,在ART 环境中,应用在第一次安装的时候,字节码就会预先编译成机器码,使其成为真正的本地应用。之后打开App的时候,不需要额外的翻译工作,直接使用本地机器码运行,因此运行速度提高。

当然ART与Dalvik相比,还是有缺点的。

  • ART需要应用程序在安装时,就把程序代码转换成机器语言,所以这会消耗掉更多的存储空间,但消耗掉空间的增幅通常不会超过应用代码包大小的20%
  • 由于有了一个转码的过程,所以应用安装时间难免会延长

但是这些与更流畅的Android体验相比而言,不值一提。

总结

通过前面背景知识的介绍,我终于可以更简单的介绍这四个名词之间的关系了:

  • JIT代表运行时编译策略,也可以理解成一种运行时编译器,是为了加快Dalvik虚拟机解释dex速度提出的一种技术方案,来缓存频繁使用的本地机器码
  • ART和Dalvik都算是一种Android运行时环境,或者叫做虚拟机,用来解释dex类型文件。但是ART是安装时解释,Dalvik是运行时解释
  • AOT可以理解为一种编译策略,即运行前编译,ART虚拟机的主要特征就是AOT

复制粘贴+理解,,,希望以后忘了能回来看看~!~



作者:SYfarming
链接:https://www.jianshu.com/p/389911e2cdfb
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

猜你喜欢

转载自blog.csdn.net/c_z_w/article/details/79896001