2.31 MultiDex原理

6 minute read

2.31.1 编译流程原理

  • MultiDexTransform(gradle中将multiDexEnabled设置为true, 则执行)

    public void transform(@NonNull TransformInvocation invocation)
         throws IOException, TransformException, InterruptedException {
       ...
       // 核实输入文件
       File input = verifyInputs(invocation.getReferencedInputs());
       // 处理混淆
       shrinkWithProguard(input);
       // 计算生成mainDexList.txt
       computeList(input);
     }
    
     private void shrinkWithProguard(@NonNull File input) throws IOException, ParseException {
       ...
    
       // 把manifest_keep.txt文件的混淆规则添加进来
       applyConfigurationFile(manifestKeepListProguardFile);
       ...
       // 将shrinkedAndroid.jar放到classpath
       libraryJar(findShrinkedAndroidJar());
    
       // 输出ComponentClassed.kar
       outJar(variantScope.getProguardComponentsJarFile());
       ...
       // run proguard
       runProguard();
     }
    
     private void computeList(File _allClassesJarFile) throws ProcessException, IOException {
       Set<String> mainDexClasses = callDx(
               _allClassesJarFile,
               variantScope.getProguardComponentsJarFile());
       ...
       Files.write(fileContent, mainDexListFile, Charsets.UTF_8);
    }
    
    private Set<String> callDx(File allClassesJarFile, File jarOfRoots) throws ProcessException {
       EnumSet<AndroidBuilder.MainDexListOption> mainDexListOptions =
               EnumSet.noneOf(AndroidBuilder.MainDexListOption.class);
       ...
       return variantScope.getGlobalScope().getAndroidBuilder().createMainDexList(
               allClassesJarFile, jarOfRoots, mainDexListOptions);
    }
    
    public Set<String> createMainDexList(
         @NonNull File allClassesJarFile,
         @NonNull File jarOfRoots,
         @NonNull EnumSet<MainDexListOption> options) throws ProcessException {
       ...
       String dx = buildToolInfo.getPath(BuildToolInfo.PathId.DX_JAR);
       builder.setClasspath(dx);
       // 调用ClassReferenceListBuilder#main方法
       builder.setMain("com.android.multidex.ClassReferenceListBuilder");
       ...
       builder.addArgs(jarOfRoots.getAbsolutePath());
       builder.addArgs(allClassesJarFile.getAbsolutePath());
       CachedProcessOutputHandler processOutputHandler = new CachedProcessOutputHandler();
       mJavaProcessExecutor.execute(builder.createJavaProcess(), processOutputHandler)
               .rethrowFailure()
               .assertNormalExitValue();
       ...
       return ImmutableSet.copyOf(lineCollector.getResult());
    }
    
    public static void main(String[] args) {
       int argIndex = 0;
       ...
       MainDexListBuilder builder = new MainDexListBuilder(keepAnnotated, args[argIndex],
               args[argIndex + 1]);
      // 取出MainDexList并输出
       Set<String> toKeep = builder.getMainDexList();
       printList(toKeep);
    }
    
    public MainDexListBuilder(boolean keepAnnotated, String rootJar, String pathString)
         throws IOException {
       ZipFile jarOfRoots = null;
       Path path = null;
       jarOfRoots = new ZipFile(rootJar);
       path = new Path(pathString);
       ClassReferenceListBuilder mainListBuilder = new ClassReferenceListBuilder(path);
       mainListBuilder.addRoots(jarOfRoots);
       // filesToKeep保存computeList中的mainDexClasses结果
       for (String className : mainListBuilder.getClassNames()) {
           filesToKeep.add(className + CLASS_EXTENSION);
       }
       if (keepAnnotated) {
           keepAnnotated(path);
       }
     }
    
     public void addRoots(ZipFile jarOfRoots) throws IOException {
       // keep roots
       for (Enumeration<? extends ZipEntry> entries = jarOfRoots.entries();
               entries.hasMoreElements();) {
           ZipEntry entry = entries.nextElement();
           String name = entry.getName();
           if (name.endsWith(CLASS_EXTENSION)) {
               ...
               // 收集符合要求的classes, 包括jarOfRoots的root class和root class的直接引用
               classNames.add(name.substring(0, name.length() - CLASS_EXTENSION.length()));
           }
       }
       ...
      }
    
  • DexTransform

     // 在createPostCompilationTasks()可看出, MultiDexTransform执行即执行DexTransform
     // DexTransform.java
     public void transform(@NonNull TransformInvocation transformInvocation)
          throws TransformException, IOException, InterruptedException {
          ...
          androidBuilder.convertByteCode(
          inputFiles,
          outputDir,
          multiDex,
          mainDexListFile,
          dexOptions,
          outputHandler);
          ...
     }
    
     // 直接看com.android.dx.command.dexer.Main#run
     public int run(Arguments arguments) throws IOException {
       ...
       args = arguments;
       ...
       if (args.multiDex) {
           return runMultiDex();
       } else {
           return runMonoDex();
       }
     }
    
     private int runMultiDex() throws IOException {
       ...
       // 将MultiDexTransform生成的mainDexList.txt存储到classesInMainDex
       classesInMainDex = new HashSet<String>();
       readPathsFromFile(args.mainDexListFile, classesInMainDex);
       ...
       // => 3
       if (!processAllFiles()) {
           return 1;
       }
       ...
    
       // 每个线程生成的dex字节流存储到dexOutputArrays
       for (Future<byte[]> f : dexOutputFutures) {
           dexOutputArrays.add(f.get());
       }
       ...
    
       // 依次输出classes.dex, classes2.dex
       for (int i = 0; i < dexOutputArrays.size(); i++) {
           OutputStream out = new FileOutputStream(new File(outDir, getDexFileName(i)));
           ...
           out.write(dexOutputArrays.get(i));
           ...
       }
       return 0;
    }
    
    private boolean processAllFiles() {
       createDexFile();
       ...
       // 将mainDexList.txt的class打进mainDex
       if (args.mainDexListFile != null) {
           // with --main-dex-list
           FileNameFilter mainPassFilter = args.strictNameCheck ? new MainDexListFilter() :
               new BestEffortMainDexListFilter();
           // forced in main dex
           for (int i = 0; i < fileNames.length; i++) {
               processOne(fileNames[i], mainPassFilter);
           }
           ...
    
           // remaining files
           for (int i = 0; i < fileNames.length; i++) {
               processOne(fileNames[i], new NotFilter(mainPassFilter));
           }
       }
       ...
       return true;
    }
    

2.31.2 MultiDex#install()流程原理

  public static void install(Context context) {
    ...
    ApplicationInfo applicationInfo = getApplicationInfo(context);
    ...
    synchronized(installedApk) {
      String apkPath = applicationInfo.sourceDir;
      ...
      ClassLoader loader = context.getClassLoader();
      ...
      // 获取缓存目录
      File dexDir = getDexDir(context, applicationInfo);
      // 加载缓存文件
      List<File> files = MultiDexExtractor.load(context, applicationInfo, dexDir, false);
      ...
      // 安装提取dex
      installSecondaryDexes(loader, dexDir, files);
    }
  }

  // MultiDexExtractor#load
  // 获取dex文件列表
  static List<File> load(Context context, ApplicationInfo applicationInfo, File dexDir,  boolean forceReload) {
    ...
    files = loadExistingExtractions(context, sourceApk, dexDir);
    ...
  }

  private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<File> files) {
    if (!files.isEmpty()) {
            if (Build.VERSION.SDK_INT >= 19) {
                V19.install(loader, files, dexDir);

            } else if (Build.VERSION.SDK_INT >= 14) {
                V14.install(loader, files, dexDir);
            } else {
                V4.install(loader, files);
            }
        }
  }

  /**
 * Installer for platform versions 14, 15, 16, 17 and 18.
 */
private static final class V14 {
    private static void install(ClassLoader loader, List<File> additionalClassPathEntries, File optimizedDirectory){
        // 扩展ClassLoader实例的"pathList"字段。
        Field pathListField = findField(loader, "pathList");
        Object dexPathList = pathListField.get(loader);
        expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
                new ArrayList<File>(additionalClassPathEntries), optimizedDirectory));
    }
    private static Object[] makeDexElements(
            Object dexPathList, ArrayList<File> files, File optimizedDirectory)
                    throws IllegalAccessException, InvocationTargetException,
                    NoSuchMethodException {
        Method makeDexElements =
                findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class);
        return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory);
    }
}

final class DexPathList {
    private static final String DEX_SUFFIX = ".dex";
    private static final String JAR_SUFFIX = ".jar";
    private static final String ZIP_SUFFIX = ".zip";
    private static final String APK_SUFFIX = ".apk";


    // 加载解压dex文件, 并放到elements数组
    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).
                ...
                dex = loadDexFile(file, optimizedDirectory);
                ...
            }
            ...
            elements.add(new Element(file, zip, dex));
        }
        return elements.toArray(new Element[elements.size()]);
    }


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

  // sourceName: apk/dex路径; outputName: dex优化后的输出路径
  private DexFile(String sourceName, String outputName, int flags) throws IOException {
       ...
       mCookie = openDexFile(sourceName, outputName, flags);
       ...
  }

  /*
     * Open a DEX file.  The value returned is a magic VM cookie.  On
     * failure, an IOException is thrown.
     */
    private static int openDexFile(String sourceName, String outputName,
        int flags) throws IOException {
        return openDexFileNative(new File(sourceName).getCanonicalPath(),
                                 (outputName == null) ? null : new File(outputName).getCanonicalPath(),
                                 flags);
    }
    native private static int openDexFileNative(String sourceName, String outputName,
        int flags) throws IOException;

   // 查看art/runtime/native/dalvik_system_DexFile.cc的实现
   // 关键逻辑
   bool success = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs,
                                             dex_files.get());

   // Multidex files make it possible that some, but not all, dex files can be broken/outdated. This
   // complicates the loading process, as we should not use an iterative loading process, because that
   // would register the oat file and dex files that come before the broken one. Instead, check all
   // multidex ahead of time.
   bool ClassLinker::OpenDexFilesFromOat(const char* dex_location, const char* oat_location,
                                         std::vector<std::string>* error_msgs,
                                         std::vector<const DexFile*>* dex_files) {

     ...
     //查看目标Oat文件是否已经被加载过了
     const OatFile::OatDexFile* oat_dex_file = FindOpenedOatDexFile(oat_location, dex_location, dex_location_checksum_pointer);
     ...
     bool success = LoadMultiDexFilesFromOatFile(open_oat_file.get(), dex_location,
                                                 dex_location_checksum_pointer,
                                                 false, error_msgs, dex_files);
     ...
   }

   // Loads all multi dex files from the given oat file returning true on success.
  //
  // Parameters:
  //   oat_file - the oat file to load from
  //   dex_location - the dex location used to generate the oat file
  //   dex_location_checksum - the checksum of the dex_location (may be null for pre-opted files)
  //   generated - whether or not the oat_file existed before or was just (re)generated
  //   error_msgs - any error messages will be appended here
  //   dex_files - the loaded dex_files will be appended here (only if the loading succeeds)
  static bool LoadMultiDexFilesFromOatFile(const OatFile* oat_file,
                                           const char* dex_location,
                                           const uint32_t* dex_location_checksum,
                                           bool generated,
                                           std::vector<std::string>* error_msgs,
                                           std::vector<const DexFile*>* dex_files) {
      ...
      // 尝试寻找对应的OAT文件
      const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(next_name, nullptr, false);
      ...
      const DexFile* dex_file = oat_dex_file->OpenDexFile(&error_msg);
      ...
   }    

   // art/runtime/oat_file.cc
   const DexFile* OatFile::OatDexFile::OpenDexFile(std::string* error_msg) const {
      return DexFile::Open(dex_file_pointer_, FileSize(), dex_file_location_,
                           dex_file_location_checksum_, error_msg);
    }                                  

    // art/runtime/dex_file.cc
    bool DexFile::Open(const char* filename, const char* location, std::string* error_msg,
                   std::vector<const DexFile*>* dex_files) {
      uint32_t magic;
      ...
      if (IsZipMagic(magic)) {
       //因为传入的是APK,所以进到这里
        return DexFile::OpenZip(fd.release(), location, error_msg, dex_files);
      }
      ...
    }

    bool DexFile::OpenZip(int fd, const std::string& location, std::string* error_msg,
                          std::vector<const  DexFile*>* dex_files) {
      ...
      return DexFile::OpenFromZip(*zip_archive, location, error_msg, dex_files);
    }

    bool DexFile::OpenFromZip(const ZipArchive& zip_archive, const std::string& location,
                          std::string* error_msg, std::vector<const DexFile*>* dex_files) {
      ZipOpenErrorCode error_code;
      std::unique_ptr<const DexFile> dex_file(Open(zip_archive, kClassesDex, location, error_msg,
                                                   &error_code));
      if (dex_file.get() == nullptr) {
        return false;
      } else {
        // Had at least classes.dex.
        dex_files->push_back(dex_file.release());
        // Now try some more.
        size_t i = 2;
        // We could try to avoid std::string allocations by working on a char array directly. As we
        // do not expect a lot of iterations, this seems too involved and brittle.
        while (i < 100) {
          // 找到编译器生成多个dexclasses2.dex...
          std::string name = StringPrintf("classes%zu.dex", i);
          std::string fake_location = location + kMultiDexSeparator + name;
          std::unique_ptr<const DexFile> next_dex_file(Open(zip_archive, name.c_str(), fake_location,error_msg, &error_code));
          ...
          dex_files->push_back(next_dex_file.release());
          i++;
        }
        return true;
      }
    }

参考