如何决定vm,android热修复相关之Multidex剖析

 一、简介

未经博主同意,不得转载该篇小说

从本篇文章起首,对classloader方案热修复的连带知识实行学习。这几个方案的源流是基于google为了化解方法数超过限度难点而引入的MultiDex技艺。关于艺命理术数超限难题,估量大家都负有理解,这里就相当少介绍了。

         Dalvik虚拟机支持一多元的命令行参数(使用adbshell dalvikvm
–help获取列表),可是不可能因此android应用运行时来传递自便参数,可是可以通过特定的系统参数来震慑虚拟机行为。

编造机概要


Android在5.0在此之前运用的是dalvik虚拟机,使用的是纯JIT编写翻译。在android4.4提议了art虚拟机,使用的是AOT编写翻译,并在android5.0全然代表dalvik。可是在android7.0应用了JIT,AOT,解释的插花编译方式。


不论研讨怎么样,都要从最原始的,最基础的东西都以看起。这里只表达dalvik的片段,涉及到dalvik虚拟机的源码。

​ 至于art和dalvik,JIT和AOT的区分,优化,不是本文的第一。传送门:ART 和
Dalvik

MultiDex的兑现分为两上边,一方面在编写翻译apk进程中,插件能将class文件打成多少个dex文件,另一方面需求在程序运维时,将classes2.dex,
classes3.dex加载进来。大家第一关怀dex加载进度,至于dex拆分进程,这里大概的说一下。
编写翻译apk进度中,android在5.0及其以上的SDK中dx工具协助multidex参数。

         对于下述全数参数,你都得以经过setprop来安装系统天性,shell命令如下:

类加载全体流程

  • 对dex文件进行求证并优化,并冒出Odex文件
  • 对Odex文件进行剖析,产出DexFile数据结构,将在文件格局的数据转变到内部存款和储蓄器中虚拟机可达的数量(假设切磋过android的ClassLoader源码,断定对DexFile不素不相识)
  • 对点名的类进行加载,在DexFile中领到对应类的字节码,产出ClassObject数据结构
  [--multi-dex [--main-dex-list=<file> [--minimal-main-dex]]

adbshell setprop <name> <value>

Dex文件的优化

参数表达:

         必须重启android运转时从而使得改换生效(adb shell stop:adb
shell
start)。那是因为,那几个设定在zygote进程中管理,而zygote最早运营并且永世存活。

概要


Dalivk中,dex的优化利用的是dexopt,将dex文件优化为Odex文件,最后交由给下一步的加载进程。而不是像art的dex2aot同样,dex2aot是直接将全部的dex文件编写翻译为native
code存款和储蓄。Odex文件的原形只是在原dex文件的基本功上进展优化,并生成.Odex文件举行仓库储存,以升高dalvik虚拟机械运输转的高效性和安全性。必要注意的是全体dex的优化过程都以在贰个新历程中张开的。

  • –multi-dex:多 dex 打包的开关
  • –main-dex-list=<file>:参数是四个类列表的文件,在该公文中的类会被打包在第3个dex 中
  • –minimal-main-dex:只有在–main-dex-list
    文件中钦点的类被打包在第八个 dex,其他的都在第贰个 dex
    文件中,首假如为了削减主dex的大大小小。

         你不得以以无特权用户的身份设定dalvik.*参数及重启系统。你能够在用户调节和测量检验版本的shell上利用adb
root抑或运维su命令来收获root权限,如有疑问,

首要的优化点如下:

  • 确立dex的类索引表——使虚拟机神速find dex中有些类的地方
  • 寄存器的内部存款和储蓄器映射——缩小odex->DexFile的内部存款和储蓄器映射操作
  • 增添重视库新闻——加多dex需求使用的本地函数库
  • dex中字节码的替换——举例类似编译中的内联优化

和Multidex相关的gradle职责如下:

**adbshell getprop <name>**

Dex和Odex文件结构比较图

图片 1

dalvik_1.png

Dalvik由于是使用JIT及时编写翻译,由此App在第三回被张开的时候会开始展览dexopt操作而致使运营不快。

    :transformClassesWithJarMergingForDebug UP-TO-DATE
    :collectDebugMultiDexComponents UP-TO-DATE
    :transformClassesWithMultidexlistForDebug UP-TO-DATE
    :transformClassesWithDexForDebug UP-TO-DATE

         能够告知您setprop是或不是爆发。

函数推行流程

PackageManagerService是用来保管选择设置,卸载,优化等工作的系统服务。和PMS的种种操作最后会经过Java层的Installer—>InstallerConnection—>Socket通信到native的installd.c服务(这一个套路在黑域中也可以有选择)。

InstallerConnection.connect():

图片 2

dalvik_2.png

InstallerConnection.dexopt():

图片 3

dalvik_3.png

最终会调用到dalvik/dexopt/OptMain.cpp

图片 4

dalvik_4.png

  • transformClassesWithJarMergingForDebug
    其一transform的功用是将所用到的 jar 调换至多个纯净的 Jar
    中,输出产物在 build/intermediates/transforms/jarMerging 目录下的
    combined.jar文件。
  • collectDebugMultiDexComponents
    该task扫描AndroidManifest.xml中的application、activity、receiver、provider、service等连锁类,并将这么些类的信息写入到manifest_keep.txt文件中,该文件位于目录build/intermediates/multi-dex/debug
  • transformClassesWithMultidexlistForDebug
    那几个transform依据在此之前的 mainfest_keep 及部分 proguard 文件来生成
    mainDex 中内定的类会集文件,对应生成的出口结果为
    maindexlist.txt,同不经常候生成componentClasses.jar文件,七个公文均位于build/intermediates/multi-dex/debug目录下。
  • transformClassesWithDexForDebug
    调用dx命令,实行dex生成,这里在拍卖主dex是透过遍历maindexlist.txt对应的class文件,读取class文件格式中常量池的始末,从而取得到重视类。

         假若您不想在道具重启之后特性消失,在/data/local.prop上加一行:

Dex文件的深入分析


虚拟机须求拜访到可读的Dex数据来进展类加载。由此大家需求将dex文件深入分析成DexFile的内存中的数据结构。其深入分析进度实际上是将DexFile数据结构中的各样成员变量与Dex文件的顺序数据部分相关联。

DexFile的结构体:

图片 5

dalvik_5.png


供给注意的是这一步是在Dex文件优化以往,所以从此间初阶波及的Dex文件都以Odex文件。具体的深入分析进程不汇报。接下来的长河就是要从DexFile中加载钦命的类,并将其装入虚拟机的运转时意况中。

透过以上的transform和连锁task,大家打出的apk会含有不只二个xx.dex。下边来说一下多dex运行的难点。这里须要大家对类加载器有所领会,能够参见笔者此前写过的一篇文章Android插件化框架类别之类加载器照旧查占星关文章展开学习。

<name>= <value>

运作时数据装载


到此处,我们必要抽象出另一个数据结构——ClassObject。大家到那边能够梳理一下流程:

图片 6

dalvik_6.png


至于ClassObject的数据结构,特其他长,有乐趣的同窗可以友善去源码看看。地点在
dalvik\vm\native\oo\Object.h中。

对于apk中多dex成功加载的难题,按虚拟机类型举行归类解析:

         重启之后那样的转移也会间接存在,但是一旦data分区被擦除了就流失了。(提醒:在职业台上创造三个local.prop,然后adb
push local.prop /data/,大概,使用类似于adb shell “echo name =value
>> /data/local.prop”的指令——注意,引号很关键)

重点!类加载Java到Native,揭秘unexpectedDEX异常

到此处,大家能够来贰遍从Java层的类加载到Native层的类加载调用流程整个深入分析了。若是不纯熟Android
Java层类加运载飞机制的可以看那篇博客:Android动态类记载

首先,路人皆知,DexClassLoder和PathClassLoder是咱们见的可比多的类。差异就在于前者能够加载肆意路线下的.jar或然.apk,而后者只能加载暗中同意路径下的dex文件,即/data/dalvik-cache。然后双方都一而再自BaseDexClassLoader。那那几个差别的缘由是如何吧?其实很简短:

图片 7

dalvik_7.png

图片 8

dalvik_8.png

不用解释都知情了,后者的构造函数不能够安装Odex文件的门道,因而一般用作系统类(其实谈起底是BootClassLoader加载的)和应用类。前者能够动态的安装Odex路线,所以常常在插件化中被应用。

两边其实就是二个空壳,真正的加载函数都以调用自BaseDexClassLoader的父类ClassLoader的loadClass函数:

图片 9

dalvik_9.png

能够见到,先调用了findLoadedClass()方法:

图片 10

dalvik_10.png

先判别虚拟机是或不是早就加载了那个class,假若加载了就能一贯重回。那也是怎么dex插桩流派的热修复必供给运用重启能力修复,因为倘若虚拟机加载了某些类,就不会再也再加载。

再看到前面,先调用了parent的loadClass()函数,假如为空才会调用自身的findClass()函数。对,没有错!优雅的大人民委员会派机制(义务链)以及模版方法的设计形式。Android提议大家毫不重写loadClass()方法,去重写findClass()方法,就是为了遵从那么些机制和生态。由此大家承袭追踪BaseDexClassLoader的findClass()方法:

图片 11

dalvik_11.png

观看会调用Dex帕特hList的findClass()方法,而以此DexList其实内部维护着一个DexFile的集聚。继续追踪:

图片 12

dalvik_12.png

可以看来,正是轻易的遍历DexFile集合,然后去轮询Class。其实熟练热修复的同校看来此间能够说是很自在的,因为Q空的插桩,微信的全量替换等之热修复技艺都是在这块做小说。所以持续追踪DexFile中去,那些实际便是前文提到的了:

图片 13

dalvik_13.png

额,其实没什么内容,然则敏感的开掘到,我们要进入到逼气十足的Native层了!defineClassNative(name,
loader,
cookie);。假设您的手上有Dalvik的源码,能够和本人联合深切进去。这几个函数在vm\native\dalivk_system_DexFile.cpp中的Dalvik_dalvik_system_DexFile_defineClass函数里。酷酷的。。:

图片 14

dalvik_14.png

此间只截取了一部分,其实很简短先调用dvmGetRawDexFileDex只怕dvmGetJarFileDex(假使是jar包)方法去给指向DexFile的指针赋值(其实DexFile是DvmDex的五个分子变量),然后将以此指针传递进dvmDefineClass()函数,而这几个函数最后调用了findClassNoInit()函数:

图片 15

dalvik_15.png

findClassNoInit()方法是入眼,里面先推断是或不是早就加载,若无加载会持续拓展加载,通过dexFindClass()方法,重返多少个DexClassDef数据结构,那么些数据结构是为了方便快捷稳固类在Dex中的地方,然后最终通过loadClassFromDex()方法给ClassObject指针赋值:

图片 16

dalvik_16.png

而loadClassFromDex()方法的源码看起来比较清淡,直接计算一下:

  • 为ClassObject申请内部存款和储蓄器
  • 安装字段音信
  • 为超类建设构造目录
  • 加载类接口
  • 加载类字段
  • 加载类方法

今后,我们仅仅完结了类加载的加载阶段。类加载实际上还恐怕有众多步骤。前面会对ClassObject举行更为的加工,前边随着调用了dvmLinkClass()方法举行Prepare
and
resolve,重要将符号援引调换到为直接援用,在里边会特别调用dvmResolveClass()方法,而这几个艺术其实是在条分缕析当前被加载类的父类以及接口:

图片 17

dalvik_17.png

此间只截取明白析父类的一部分,接口部分类似。能够见见,加载完理解后,就能将以此标志援引调换为直接援引,并对GC可知。而那些dvmResolveClass()方法正是unexpected
DEX分外接触的地点,先来看两段dvmResolveClass()方法的评释:

* Because the DexTypeId is associated with the referring class’ DEX
file,

* we may have to resolve the same class more than once if it’s
referred

* to from classes in multiple DEX files. This is a necessary property
for

* DEX files associated with different class loaders.

忽视正是被引述的类只怕是别的Dex文件里的,所以我们兴许会因为有些类被另二个Dex文件中的类给援引而招致重复深入分析这几个类。申明Dalvik会利用一种体制来制止这种光景,前面会提到。

* “fromUnverifiedConstant” should only be set if this call is the
direct

* result of executing a “const-class” or “instance-of” instruction,
which

* use class constants not resolved by the bytecode verifier.

忽略就是假如大家经过”const-class”(通过项目索引获取贰个类的引用赋值给寄存器,譬喻直接引用XX.class)

“instance-of”(判别寄存器中指标的引用是还是不是是内定类型)(均为互相Dalvik虚拟机的命令)指令去援引贰个类,那么fromUnverifiedConstant变量会被set为True。申明那么些变量在承袭代码中很关键。继续下去:

图片 18

dalvik_18.png

很好,终于见到了

dvmThrowIllegalAccessError(
                    "Class ref in pre-verified class resolved to unexpected "
                    "implementation");

与此同不平时候很显然的看到了触发unexpectedDEX卓殊的多少个条件:

  • fromUnverifiedConstant为False
  • 被解析的类,设置了CLASS_ISPREVERIFIED
  • referrer->pDvmDex != resClassCheck->pDvmDex
  • 被征引类的类加载器不是Null

大家入眼看前四个条件:

fromUnverifiedConstant 如果变量是被"const-class" or "instance-of"指令加载进来就是True否则为False
CLASS_ISPREVERIFIED 在Dex优化过程中引用其他Dex文件的类,被加载类不会设置该状态,否则会设置该状态
pDvmDex 如果被解析类和被引用类不在同一个Dex文件中就会触发异常

笔者们珍视看一下CLASS_ISPREVE路虎极光IFIED被安装的代码。前文有提到Dex文件的优化是在dalvik/dexopt/OptMain.cpp的extractAndProcessZip()作为入口起先的。通过笔者一步步的追踪,最终在dalvik\vm\analysis\DexPrepare.cpp中的verifyAndOptimizeClasses()函数找到了设置的逻辑:

图片 19

dalvik_19.png

而那么些函数的目标便是印证并优化Dex文件中的全体类,也正是在此间的表达进程,可能会给类打上CLASS_ISPREVERIFIED的Flag。

本着上述分析的八个点,咱们能够在热修复中做出分化的本事方案:

  • 自己的前东家手Q的QFix:通过native修改fromUnverifiedConstant变量
  • QQ空间:通过给每一个类引进几个单独Dex中的类来幸免CLASS_ISPREVERIFIED被设置
  • 微信Tinker:通过全量Dex替换到幸免pDvmDex不相同

Dalvik虚拟机

本着dalvik虚拟机,我们都知情google是依据Multidex库消除的。唯一的只怕正是dalvik无法从二个apk中加载多少个dex,大家去源码里证实一下,以4.2的源码为例,剖判一下classloader加载dex的流水生产线(PS:这里最首要为了深入分析classloader,所以并未有从apk安装起来,可以以为是在odex不设有的情况深入分析PathClassLoader加载dex的历程)大家看一下BaseDexClassLoader源码。
/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java

public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.originalPath = dexPath;
        this.pathList =
            new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class clazz = pathList.findClass(name);
        if (clazz == null) {
            throw new ClassNotFoundException(name);
        }
        return clazz;
    }

看得出大家传入apk路线后,在加载器创设时会营造多个DexPathList,其它什么都没做,跟进去看一下。
/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

public DexPathList(ClassLoader definingContext, String dexPath,
           String libraryPath, File optimizedDirectory) {
       if (definingContext == null) {
           throw new NullPointerException("definingContext == null");
       }

       if (dexPath == null) {
           throw new NullPointerException("dexPath == null");
       }

       if (optimizedDirectory != null) {
           if (!optimizedDirectory.exists())  {
               throw new IllegalArgumentException(
                       "optimizedDirectory doesn't exist: "
                       + optimizedDirectory);
           }

           if (!(optimizedDirectory.canRead()
                           && optimizedDirectory.canWrite())) {
               throw new IllegalArgumentException(
                       "optimizedDirectory not readable/writable: "
                       + optimizedDirectory);
           }
       }

       this.definingContext = definingContext;
       this.dexElements =
           makeDexElements(splitDexPath(dexPath), optimizedDirectory);
       this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
   }

那时候的dexPath是我们传入的apk路径,整个构造函数完成的职务正是填充dexElements,这么些dexElements是三个Element[]品类的变量,Element又是什么样?在dalvik下,大家得以这样精通,每种dex加载成功后,会对应成一个DexFile对象,这里权且能够以为Element正是DexFile的八个装进(不思量财富的情景下),也正是等同于二个dex。篇幅原因,直接开展讲解啊,splitDexPath担任分析传入路线,帮助路径中有八个dex或许压缩包,比方xxx.zip;xxx.zip;xxx.zip等,是利用File.pathSeparator实行私分的,各样路线会封装成一个File,最终产生叁个list,当然了,绝大多数情景下,大家传给类加载器的路径都以单纯的。

   private static Element[] makeDexElements(ArrayList<File> files,
            File optimizedDirectory) {
        ArrayList<Element> elements = new ArrayList<Element>();
        for (File file : files) {
            ZipFile zip = null;
            DexFile dex = null;
            String name = file.getName();
            if (name.endsWith(DEX_SUFFIX)) {
                // Raw dex file (not inside a zip/jar).
                try {
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException ex) {
                    System.logE("Unable to load dex file: " + file, ex);
                }
            } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
                    || name.endsWith(ZIP_SUFFIX)) {
                try {
                    zip = new ZipFile(file);
                } catch (IOException ex) {
                    System.logE("Unable to open zip file: " + file, ex);
                }
               try {
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException ignored) {                 
                }
            } else {
                System.logW("Unknown file type for: " + file);
            }
            if ((zip != null) || (dex != null)) {
                elements.add(new Element(file, zip, dex));
            }
        }
        return elements.toArray(new Element[elements.size()]);
    }

那没怎么好深入分析的,大家传入的是apk,能够看来,先是封装成贰个ZipFile,然后调用了loadDexFile方法。最后会调用到DexFile的openDexFile方法,该办法是一个native方法。源码在
/dalvik/vm/native/dalvik_system_DexFile.cpp

static void Dalvik_dalvik_system_DexFile_openDexFile(const u4* args,
    JValue* pResult)
{
    StringObject* sourceNameObj = (StringObject*) args[0];
    StringObject* outputNameObj = (StringObject*) args[1];
    DexOrJar* pDexOrJar = NULL;
    JarFile* pJarFile;
    RawDexFile* pRawDexFile;
    char* sourceName;
    char* outputName;
    ...
    ...
    if (hasDexExtension(sourceName)
            && dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) {
        LOGV("Opening DEX file '%s' (DEX)", sourceName);
        pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
        pDexOrJar->isDex = true;
        pDexOrJar->pRawDexFile = pRawDexFile;
        pDexOrJar->pDexMemory = NULL;
    } else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) {
        LOGV("Opening DEX file '%s' (Jar)", sourceName);
        pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
        pDexOrJar->isDex = false;
        pDexOrJar->pJarFile = pJarFile;
        pDexOrJar->pDexMemory = NULL;
    } else {
        LOGV("Unable to open DEX file '%s'", sourceName);
        dvmThrowIOException("unable to open DEX file");
    }
    if (pDexOrJar != NULL) {
        pDexOrJar->fileName = sourceName;
        addToDexFileTable(pDexOrJar);
    } else {
        free(sourceName);
    }
    RETURN_PTR(pDexOrJar);
}

hasDexExtension剖断是不是是.dex,大家这里是.apk,显著会走到dvmJarFileOpen方法中。dvmJarFileOpen方法在/dalvik/vm/JarFile.cpp中

int dvmJarFileOpen(const char* fileName, const char* odexOutputName,
    JarFile** ppJarFile, bool isBootstrap)
{
    ...
    ... 
    fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
    if (fd >= 0) {
        LOGV("Using alternate file (odex) for %s ...", fileName);
        if (!dvmCheckOptHeaderAndDependencies(fd, false, 0, 0, true, true)) {
            LOGE("%s odex has stale dependencies", fileName);
            free(cachedName);
            cachedName = NULL;
            close(fd);
            fd = -1;
            goto tryArchive;
        } else {
            LOGV("%s odex has good dependencies", fileName);
            //TODO: make sure that the .odex actually corresponds
            //      to the classes.dex inside the archive (if present).
            //      For typical use there will be no classes.dex.
        }
    } else {
        ZipEntry entry;

tryArchive:
        entry = dexZipFindEntry(&archive, kDexInJarName);
        if (entry != NULL) {
            bool newFile = false;
            if (odexOutputName == NULL) {
                cachedName = dexOptGenerateCacheFileName(fileName,
                                kDexInJarName);
                if (cachedName == NULL)
                    goto bail;
            } else {
                cachedName = strdup(odexOutputName);
            }
            LOGV("dvmJarFileOpen: Checking cache for %s (%s)",
                fileName, cachedName);
            fd = dvmOpenCachedDexFile(fileName, cachedName,
                    dexGetZipEntryModTime(&archive, entry),
                    dexGetZipEntryCrc32(&archive, entry),
                    isBootstrap, &newFile, /*createIfMissing=*/true);
            if (fd < 0) {
                LOGI("Unable to open or create cache for %s (%s)",
                    fileName, cachedName);
                goto bail;
            }
            locked = true;
        ....
        ....
    return result;
}

先是openAlternateSuffix检查是或不是早已存在了相应的odex,要是存在,在dvmCheckOptHeaderAndDependencies中开始展览opt格式校验,即使不设有odex或许存在无效odex时,会利用dexzipFindEntry函数去追寻相称对应的dex,而kDexInJarName的值为常量,那就解释了我们的标题,dalvik虚拟机中只会对名称为“classes.dex”的dex文件进行加载,其他的均不会加载。当找到dex后,会调用dvmOpenCachedDexFile函数,在函数内部会有运行实行dexopt相关的代码,进而施行dexopt进程,那些暂且不做剖析。

static const char* kDexInJarName = "classes.dex";

到这里大家就从源码角度表明了何以dalvik虚拟机只可以加载apk包的贰个dex,而且必须为classes.dex。整个流程也是为了记录下android类加载器加载流程,上面分析到art虚拟机的时候一般流程会跳过。因为apk中的classes2.dex,…等dex均不能加载,应用运转时肯定会报找不到类的百般。Multidex的服从即是想方法把classes2.dex,classes3.dex尽或许早的加载进来。

Multidex的连带源码深入分析的稿子诸多,大家能够自动查看,这里只看一下中心代码。基本上能够综合成两步:

    1. 将apk中的classes2.dex,classes3.dex…拷贝到目录/data/data/pkgName/code_cache/secondary-dexes/下,命名为/data/data/pkgName/code_cache/secondary-dexes/apkName.apk.classesN.zip,具体拷贝的经过在MultiDexExtractor的extract方法中,能够观察,相当于重命名称叫classes.dex压缩到了一个zip中。和前面源码深入分析的符合,即收缩包中的classes.dex。

        ZipEntry classesDex = new ZipEntry("classes.dex");
    1. 运用反射,将持有的zip放到Dex帕特hList的Elements数组中,并开始展览调用。

private static final class V14 {
    private V14() {
    }
    private static void install(ClassLoader loader, List<File> additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
      Field pathListField = MultiDex.findField(loader, "pathList");
      Object dexPathList = pathListField.get(loader);
      MultiDex.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory));
    }
    private static Object[] makeDexElements(Object dexPathList, ArrayList<File> files, File optimizedDirectory) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
      Method makeDexElements = MultiDex.findMethod(dexPathList, "makeDexElements", new Class[]{ArrayList.class, File.class});
      return (Object[])((Object[])makeDexElements.invoke(dexPathList, new Object[]{files, optimizedDirectory}));
    }
  }

这段是宗旨代码,也是具有classloader热修复方案的常有来源。大家来轻巧分析一下这段代码。additionalClassPathEntries是大家拷贝过来的富有的zip包列表,通过反射调用makeDexElements函数,获得新的Elements数组,然后调用用expandFieldArray函数,将多少个Elements数组开始展览统一。那样应用的pathClassLoader中的elements数组就包蕴八个dex文件了当大家查找类的时候便是遍历elements中的dex,从种种dex中逐条查找。

 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);
  }

顺便说一下MultiDex中可能遭受的标题,因为大家项目是用的插件化框架,方法数纵然超乎了65536,可是主项目方法数当先的并非常少,线上并未监测到AN索罗德难题,不过从常理上来说,dalvik下,apk安装时只对主dex实行了dexopt,而从dex都以在首先次运转时,进行dexopt操作的,具体机缘是在makeDexElements函数被反射调用时开始展览的,至于dexopt为何耗费时间,后边的稿子会进展辨析。所以尽管Classes2.dex十分的大,也许从dex好些个,加载进程将一定耗费时间,确实很有比相当的大概率出现AN路虎极光,基本上海南大学学家的减轻方案都以采纳异步加载,做个等待页面来解决。

 

结束语

前段时间刚离职回高校,买了有个别本新书希图啃一啃,充实一下要好的技艺栈。近几天在商量Dalvik的源码和编写制定,发掘好些个在此以前看似空洞的东西,其实都在源码能够一探终究,这种感到真是太棒了。

ART虚拟机

ok,分析完了dalvik下多dex加载,大家都驾驭Multidex库对于API20上述是无需的,art虚拟机进行了相关的内建支持。来看一下art的类加载器在这一块的拍卖,以android6.0源码为例,java层代码大致一模二样,直接看native层代码:
/art/runtime/native/dalvik_system_DexFile.cc

static jobject DexFile_openDexFileNative(
    JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
  ScopedUtfChars sourceName(env, javaSourceName);
  ClassLinker* linker = Runtime::Current()->GetClassLinker();
  std::vector<std::unique_ptr<const DexFile>> dex_files;
  std::vector<std::string> error_msgs;
  dex_files = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs);
  if (!dex_files.empty()) {
    jlongArray array = ConvertNativeToJavaArray(env, dex_files);
    if (array == nullptr) {
      ScopedObjectAccess soa(env);
      for (auto& dex_file : dex_files) {
        if (Runtime::Current()->GetClassLinker()->IsDexFileRegistered(*dex_file)) {
          dex_file.release();
        }
      }
    }
    return array;
  } else {
    ScopedObjectAccess soa(env);
    CHECK(!error_msgs.empty());
    // The most important message is at the end. So set up nesting by going forward, which will
    // wrap the existing exception as a cause for the following one.
    auto it = error_msgs.begin();
    auto itEnd = error_msgs.end();
    for ( ; it != itEnd; ++it) {
      ThrowWrappedIOException("%s", it->c_str());
    }
    return nullptr;
  }
}

我们关切那三句就全知晓了,dex_files是贰个Vector对象,然后通过OpenDexFilesFromOat去加载apk中的全数dex,保存在dex_files中,然后通过ConvertNativeToJavaArray函数转化成jlongArray重返java端,保存在了DexFile的mCookie变量中。

  std::vector<std::unique_ptr<const DexFile>> dex_files;
  dex_files = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs);
  jlongArray array = ConvertNativeToJavaArray(env, dex_files);

接下去的逻辑我们就不一一去追踪了,OpenDexFilesFromOat函数首先去看清有未有生成oat文件,若无,会先试行dexoat进程,生成oat文件,然后从oat文件中研究dex,最后会走到/art/runtime/dex_file.cc的OpenFromZip函数中

bool DexFile::OpenFromZip(const ZipArchive& zip_archive, const std::string& location,
                        std::string* error_msg,
                        std::vector<std::unique_ptr<const DexFile>>* dex_files) {
  for (size_t i = 1; ; ++i) {
    std::string name = GetMultiDexClassesDexName(i);
    std::string fake_location = GetMultiDexLocation(i, location.c_str());
    std::unique_ptr<const DexFile> next_dex_file(Open(zip_archive, name.c_str(), fake_location,
                                                      error_msg, &error_code));
    if (next_dex_file.get() == nullptr) {
      if (error_code != ZipOpenErrorCode::kEntryNotFound) {
        LOG(WARNING) << error_msg;
      }
      break;
    } else {
      dex_files->push_back(std::move(next_dex_file));
    }
    if (i == std::numeric_limits<size_t>::max()) {
      LOG(ERROR) << "Overflow in number of dex files!";
      break;
    }
  }
  return true;
}
}

std::string DexFile::GetMultiDexClassesDexName(size_t index) {
  if (index == 0) {
    return "classes.dex";
  } else {
    return StringPrintf("classes%zu.dex", index + 1);
  }
}

ok,看到GetMultiDexClassesDexName函数就无需表明怎样了。

完全有一些乱,轻易总计一下,在art虚拟机中,Multidex是内建支持的,在apk设置时就完了了具备dex的dexoat进度。而dalvik下,apk安装时dalvik虚拟机只可以对classes.dex举办管理,借助于MultiDex库反射elements数组开始展览dex增加完结的。

那篇文章写的指标一是为着引出classloader热修复方案,二是探听一下dalvik和art在类加载器方面包车型地铁分化。

参考:
1.http://blog.csdn.net/jiangwei0910410003/article/details/50799573
2.http://blog.csdn.net/richie0006/article/details/51103976

二、扩展的JNI检测

         JNI(Java Native
Interface),java本地接口,提供了java语言程序调用本地(C/C++)代码的章程。扩充的JNI检验会挑起系统运行更加慢,不过能够开掘一七种的抵触的bug,制止他们爆发难题。

         有多少个种类参数影响这么些效应,这么些意义能够透过-Xcheck:jni命令行参数来激活。第二个参数是ro.kernel.android.checkjni,那是通过android编译系统对development的编译来安装的(也得以经过android模拟器设置,除非通过模拟器命令行置了-nojni标记位)。因为那是三个”ro.”脾气,设备运营之后参数就不能够变了。

         为了能触发CheckJNI标记位,第三种特色是dalvik.vm.checkjni,它的值覆盖了ro.kernel.android.checkjni的值。

         借使那几个特性未有被定义,dalvik.vm.checkjni也尚未安装成false,那么-Xcheck:jni标记位就从不传来,JNI检查实验也就从未使能。

         要开拓JNI检验,使用以下命令:

adbshell setprop dalvik.vm.checkjni true

         也能够经过系统天性将JNI检查测验选项传递给虚拟机,dalvik.vm.jniopts的值能够由此-Xjniopts参数字传送入,举个例子:

adb shellsetprop dalvik.vm.jniopts forcecopy

         更加的多音讯见JNI建议。

 

三、断言

         dalvik虚拟机支持java编制程序语言的预知表明式,暗中同意它是停业的,但是能够透过-ea参数的措施(dalvikvm
–ea …..
)设置dalvik.vm.enableassertions特性。

         在别的桌面虚拟机中这几个参数同样生效,通过提供class名、package名(后跟“…”),只怕非常值“all”。比如:

adbshell setprop dalvik.vm.enableassertion all

就能够在富有非系统class中使能断言。

         那几个系统性格比全命令行更受限制,不得以经过-ea入口设置越来越多,而且从不点名-da入口的格局,而且未来也未有-esa/-dsa等价的东西。

 

四、字节码校验和优化

         系统尝试预校验dex文件中的全体类,从而下跌class的担任,从而能够利用一多重的优化来升高运作质量。那么些都以通过dexopt命令来完结的,不论是在编写翻译系统中依然在装置上。在付出设备上,dexopt大概在dex文件首先次被利用时运营,而任由它依然它的借助是不是更新过(Just-in-time优化和校验,JIT)。

         有三个指令行标识位调节JIT优化和校验,-Xverify和-Xdexopt。andorid框架基于dalvik.vm.dexopt-flags个性来配置那俩参数,要是您设定:

adbshell setprop dalvik.vm.dexopt-flags v=a o=v

         那么android框架会将-Xverify:all-Xdexopt:verified传递给虚拟机,那将使能校验并且只优化校验成功的class。那是最安全的设定,也是私下认可的。

         你也得以设定dalvik.vm.dexopt-flags
v=n
使得框架传输-Xverify:none
–Xdexopt:verified从而不使能校验(大家得以传输-Xdexopt:all从而允许优化,可是那并不可能优化越来越多代码,因为从没经过校验的class大概被优化器以同一的说辞跳过)。那时class不会被dexopt校验,而没被校验的代码很祸患以实行。

         使能校验会使得dexopt命令鲜明开支更多日子,因为校验进度相对不快,一旦校验和优化过的dex文件计划伏贴,校验就不会据有额外的支出除非在加载预校验战败的class。

         假若您的dex文件的校验关闭了,而后来又开拓了校验器,应用加载会鲜明变慢(差不离十分之六之上)因为class会在第二遍被调用的时候校验。

         为了最棒功用,当特性别变化化的时候你应当为dex文件强制重新调用dexopt,即:

adbshell “rm /data/dalvik-cache/*”

         它删除了暂存的dex文件,记住要暂停再张开运营时(adb shell
stop:adb shell start
)。

(老的运营时版本帮助布尔型的dalvik.vm.verify-bytecode个性,不过被dalvik.vm.dexopt-flags替代了)

 

五、运营情势

此时此刻dalvik
vm的完成蕴含七个单身的解释根本:“火速”(fast)、“可移植”(portable)、“调节和测量试验”(debug)。快捷解释器是为当下平台优化的,大概包蕴手动优化的汇编文件;相对的,可移植解释器是用C写的,可在科学普及的平台上应用;调试解释器是可移植解释器的变种,包括了帮忙程序深入分析(profiling)和单步。

vm可能也支撑just-in-time编写翻译,严谨的说它并不是另二个解释器,JIT编写翻译器也得以被同一的声明位使能/不使能(查看dalvik
–help
的输出音信来查阅JIT编写翻译器是还是不是在您的虚拟机里面使能)。

vm允许你在全速、可移植和jit中选拔,通过运用-Xint参数的恢弘来贯彻,该参数的值能够通过dalvik.vm.execution-mode系统天性来设置。为了选拔可移植解释器,你应有用:
adb shell setpropdalvik.vm.execution-mode int:portable

借使该参数未有一些名,系统会自动选用最合适的编写翻译器,不经常候机器也许允许采纳别的方式,比方jit编写翻译器。

不是有所的平台都有优化的完结,不经常候,快捷编写翻译器是由一密密麻麻的c落成的,这几个结果会比可移植编写翻译器还慢(当大家对持有流行平台都有优化版本的时候,这一个命名“赶快”就勘误确了)。

要是程序解析使能也许调节和测验器连接了,vm会变为调节和测量试验解释器。当程序深入分析甘休只怕调节和测量检验器中断连接,就能够东山复起原本的解释器。(用调试解释器会显著变慢,那是在评估数据时要牢记的)

JIT编写翻译器可以通过在运用程序AndroidManifest.xml中参预android:vmSafeMode=”true”来不使能,你困惑JIT编写翻译器会使得你的行使运转不健康的时候能够动用。

 

六、死锁预测

         借使虚拟机以WITH_DEADLOCK_PREDICTION参数编译,那么死锁预测器会在-Xdeadlockpredict参数中使能。(dalvikvm
–help会告诉你虚拟机是还是不是编写翻译正确——在Configured中按行查找deadlock_prediction)这么些特点会让虚拟机一直追踪对象的锁获取的一一,假使程序试图以与事先看到差异的逐一获取一些锁,虚拟机缘log四个warning并有选择的抛出极其。

         命令行参数是依赖dalvik.vm.deadlock-predict本性设置的,正确的值是off代表不使能它(默许),warn表示log难点只是继续实行,err意味着从monitor-enter指令中引发二个dalvik.system.PotentialDeadlockError十分,abort意味着终止整个虚拟机。

         你司空眼惯能够如此使用:

adbshell setprop dalvik.vm.deadlock-predict err

         除非你能够在log新闻滚动的时候一贯关切着。

         注意那么些特点是死锁预测,不是死锁检查实验——在这两天兑现中,在锁被拿走之后才会开始展览总计(那缓慢化解了代码,下降了互斥音信外的冗余)。在挂起的历程中执行kill
-3时能够发现三个死锁,并且能够在log新闻中检查评定到。

         那无非思量了监督程序,本地的互斥量和其余财富也会引起死锁,而且不会被它检验到。

 

七、dump仓库追踪

         和其余桌面虚拟机同样,dalvik虚拟机械收割到SIGQUIT(Ctrl-\ 只怕kill
-3)时,会为具有的现存dump全部的货仓追踪。它默许写入Android 的log,不过也能够写入三个文书。

         dalvik.vm.stack-trace-file特征允许你钦赐要将线程货仓追踪写入的文书名,如若不存在,将开创,新的新闻将扩张到文件尾,文件名经过-Xstacktracefile参数写入虚拟机。举个例子:

adbshell setprop dalvik.vm.stack-trace-file /tmp/stack-traces.txt

         假诺那本个性未有被定义,虚拟机遇在接收这么些数字信号时将货仓跟踪音信写入android
log。

 

八、dex文件和校验

         出于质量思索,优化过的dex文件的和校验被撤废了,这一般叫安全,因为文件是在设备上产生的,并且有取缔修改的权位。

         然则即使设备的存款和储蓄器不可相信,就能够产生多少损坏,这一般表现为再一次的虚拟机崩溃。为了飞速会诊这种战败,虚拟机提供了-Xcheckdexsum参数,假设设置了,在剧情被应用在此之前全部的dex文件都会举行和校验。

         如果dalvik.vm.check-dex-sum天性被使能,那么应用框架会在虚拟机创造时提供这几个参数。

         为了使能额外的dex和校验,能够:

adbshell setprop dalvik.vm.check-dex-sum true

         不正确的和校验会协会dex数据的接纳,发生错误并写入log文件,假若设备已经有过如此的难题,那么将以此特性写入/data/local.prop很有用。

         注意dexdump工具每回都会进展dex和校验,它也得以用来检查评定多量的文本。

 

九、发生标识位

         在“Honeycomb”版本中引进了一多种的汇编,它们经过标记位写入虚拟机:

adb shell setprop dalvik.vm.extra-opts “flag1flag2 … flagN”

         这一个标记位期间用空格隔开分离。你能够钦定大肆多的注脚位只要它们在系统特性值的尺寸限制内(近些日子是九十几个字符)。

         这么些额外的标记位会被加到命令行的底端,意味着它们会覆盖以前的设定。那些能够用于举个例子测量检验差别的-Xmx的值就是android框架层已经设定过了。

 ———————————————–

jni check的法子,可以对违规的jni调用做check, 

在/data/local.prop里丰硕dalvik.vm.checkjni=true, 然后重启

一经未有/data/local.prop文件则要好创办贰个放进去

一贯不root的能够尝试下边包车型大巴,重启你的浏览器就行了

adb shell setprop debug.checkjni 1

用adb shell getprop dalvik.vm.checkjni看看打字与印刷的是还是不是true

相关文章