• MultiDex原理


    MultiDex原理

    分包机制对于 Android 5 以下的手机耗时更长

    public static void install(Context context) {
            Log.i("MultiDex", "Installing application");
            if (IS_VM_MULTIDEX_CAPABLE) { //5.0 以上VM基本支持多dex,啥事都不用干
                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 {
                ...
                doInstallation(context, new File(applicationInfo.sourceDir), new File(applicationInfo.dataDir), "secondary-dexes", "", true);
                ...
                Log.i("MultiDex", "install done");
            }
    }
    

    可以发现,5.0以下的手机不支持加载多dex,需要调用 doInstallation 方法

    private static void doInstallation(Context mainContext, File sourceApk, File dataDir, String secondaryFolderName, String prefsKeyPrefix, boolean reinstallOnPatchRecoverableException) throws IOException, IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, SecurityException, ClassNotFoundException, InstantiationException {
        //获取非主dex文件
        File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName);
        MultiDexExtractor extractor = new MultiDexExtractor(sourceApk, dexDir);
        IOException closeException = null;
    
        try {
           // 1. 这个load方法,第一次没有缓存,会非常耗时
           List files = extractor.load(mainContext, prefsKeyPrefix, false);
           try {
           //2. 安装dex
               installSecondaryDexes(loader, dexDir, files);
           } 
        }
    }
    

    需要获取额外的非主dex文件,由MultiDexExtractor获取

    List<? extends File> load(Context context, String prefsKeyPrefix, boolean forceReload) throws IOException {
        if (!this.cacheLock.isValid()) {
            throw new IllegalStateException("MultiDexExtractor was closed");
        } else {
            List files;
            if (!forceReload && !isModified(context, this.sourceApk, this.sourceCrc, prefsKeyPrefix)) {
                try {
                    //读缓存的dex
                    files = this.loadExistingExtractions(context, prefsKeyPrefix);
                } catch (IOException var6) {
                    Log.w("MultiDex", "Failed to reload existing extracted secondary dex files, falling back to fresh extraction", var6);
                    //读取缓存的dex失败,可能是损坏了,那就重新去解压apk读取,跟else代码块一样
                    files = this.performExtractions();
                    //保存标志位到sp,下次进来就走if了,不走else
                    putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(this.sourceApk), this.sourceCrc, files);
                }
            } else {
                //没有缓存,解压apk读取
                files = this.performExtractions();
                //保存dex信息到sp,下次进来就走if了,不走else
                putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(this.sourceApk), this.sourceCrc, files);
            }
    
            Log.i("MultiDex", "load found " + files.size() + " secondary dex files");
            return files;
        }
    }
    

    获取dex文件时,有两个逻辑,一个是初次加载或者缓存读取失败调用performExtractions方法,缓存成功则调用loadExistingExtractions方法。实际上使用缓存的performExtractions方法是很耗时的

    private List<MultiDexExtractor.ExtractedDex> performExtractions() throws IOException {
        String extractedFilePrefix = this.sourceApk.getName() + ".classes";
        this.clearDexDir();
        List<MultiDexExtractor.ExtractedDex> files = new ArrayList();
        ZipFile apk = new ZipFile(this.sourceApk); // apk转为zip格式
    
        try {
            int secondaryNumber = 2;
            //apk已经是改为zip格式了,解压遍历zip文件,里面是dex文件,
            //名字有规律,如classes1.dex,class2.dex
            for(ZipEntry dexFile = apk.getEntry("classes" + secondaryNumber + ".dex"); dexFile != null; dexFile = apk.getEntry("classes" + secondaryNumber + ".dex")) {
                //文件名:xxx.classes1.zip
                String fileName = extractedFilePrefix + secondaryNumber + ".zip";
                //创建这个classes1.zip文件
                MultiDexExtractor.ExtractedDex extractedFile = new MultiDexExtractor.ExtractedDex(this.dexDir, fileName);
                //classes1.zip文件添加到list
                files.add(extractedFile);
                Log.i("MultiDex", "Extraction is needed for file " + extractedFile);
                int numAttempts = 0;
                boolean isExtractionSuccessful = false;
    
                while(numAttempts < 3 && !isExtractionSuccessful) {
                    ++numAttempts;
                    //这个方法是将classes1.dex文件写到压缩文件classes1.zip里去,最多重试三次
                    extract(apk, dexFile, extractedFile, extractedFilePrefix);
    
                 ...
                }
        //返回dex的压缩文件列表
        return files;
    }
    

    上面这个做的事就是解压APK,遍历dex文件,把遍历得到的又压缩成zip文件,最后返回一个zip文件列表

    只有第一个加载的时候才会调用上面这个解压和压缩的过程,第二次就可以读取sp中保存的dex信息,直接返回zip文件列表,就会直接调用installSecondaryDexes方法进行安装

    安装的实现:
    1.反射 ClassLoader 的 pathList 字段

    2.找到 pathList 字段对应的类的 makeDexElements 方法

    3.通过 MulitDex.expandFiledArray 方法扩展 dexElements 方法

    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)); //取出原来的dexElements 数组
            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); //dex2、dex3...拷贝到新的数组
            jlrField.set(instance, combined); //将dexElements 重新赋值为新的数组
        }
    

    就是创建一个新的数组,把原来数组内容(主dex)和要增加的内容(dex2、dex3...)拷贝进去,反射替换原来的dexElements为新的数组

    Tinker热修复的原理也是通过反射将修复后的dex添加到这个dex数组去,不同的是热修复是添加到数组最前面,而MultiDex是添加到数组后面。

    对于 MultiDex 在 5.0 以下的手机耗时的问题,今日头条给了一个优化方案

    1. 在主进程Application 的 attachBaseContext 方法中判断如果需要使用MultiDex,则创建一个临时文件,然后开一个进程(LoadDexActivity),显示Loading,异步执行MultiDex.install 逻辑,执行完就删除临时文件并finish自己。

    2. 主进程Application 的 attachBaseContext 进入while代码块,定时轮循临时文件是否被删除,如果被删除,说明MultiDex已经执行完,则跳出循环,继续正常的应用启动流程。

    3. MultiDex执行完之后主进程Application继续走,ContentProvider初始化和Application onCreate方法,也就是执行主进程正常的逻辑。

    注意:LoadDexActivity 必须要配置在main dex中。

  • 相关阅读:
    连接SDE数据库hl3292修改已解决
    ORACLE 10g 安装
    SharpMap相关的一些博客和网站
    Delphi 学习之函数 ②扩展的日期时间操作函数
    Delphi学习之函数 ⑾进制函数及过程
    Delphi学习之函数 ① 扩展的字符串操作函数
    html 初次接触html学习【1】
    Delphi 学习之函数 ③ 扩展的位操作函数
    Delphi学习之函数 ⑨汉字拼音功能函数
    Delphi学习之函数 ④扩展的文件及目录操作函数
  • 原文地址:https://www.cnblogs.com/huaranmeng/p/13131933.html
Copyright © 2020-2023  润新知