美文网首页
D8编译器下multi dex main dex 是如何创建的

D8编译器下multi dex main dex 是如何创建的

作者: 机智的黑猫 | 来源:发表于2019-08-16 17:08 被阅读0次

在tinker支持androidx之后,最近把公司的项目迁移到了androidx,也用r8替换了proguard(r8真香,这里提一个坑,动态代理的类在proguard里默认是不会被混淆的,但是在r8里会,并且r8会非常积极的优化方法数)。然后最近一次合并代码后发现打包失败。失败原因是方法数超标。。(突然冒出了3w多个方法)。
没办法只能去跟一下D8下maindex的生成逻辑
首先去先确认一下build/intermediates/legacy_multidex_aapt_derived_proguard_rules 下的manifest_keep.txt文件,这个是用来扫描所有dexclass并找出rootclass的rules,这些rules遵循proguard的语法逻辑。

build/intermediates/legacy_multidex_main_dex_list 下mainDexList.txt则是最终产物,这些class会被放到main dex里。

接下来我们结合gradle plugin的源代码去跟一下具体实现。
首先找到D8MainDexListTransform这个类

   override fun transform(invocation: TransformInvocation) {
        logger.verbose("Generating the main dex list using D8.")
        try {
            val inputs = getByInputType(invocation)
            val programFiles = inputs[ProguardInput.INPUT_JAR]!!
            val libraryFiles = inputs[ProguardInput.LIBRARY_JAR]!! + bootClasspath.get()
            logger.verbose("Program files: %s", programFiles.joinToString())
            logger.verbose("Library files: %s", libraryFiles.joinToString())
            logger.verbose(
                    "Proguard rule files: %s",
                    listOfNotNull(manifestProguardRules, userProguardRules).joinToString())
            val proguardRules =
                listOfNotNull(manifestProguardRules.singleFile().toPath(), userProguardRules)
            val mainDexClasses = mutableSetOf<String>()
            mainDexClasses.addAll(
                 //这里调用D8MainDexList.generate方法并返回计算出的所有classlist
                D8MainDexList.generate(
                    getPlatformRules(),
                    proguardRules,
                    programFiles,
                    libraryFiles,
                    messageReceiver
                )
            )

            if (userClasses != null) {
                mainDexClasses.addAll(Files.readAllLines(userClasses))
            }

            Files.deleteIfExists(outputMainDexList)
            Files.write(outputMainDexList, mainDexClasses)
        } catch (e: D8MainDexList.MainDexListException) {
            throw TransformException("Error while generating the main dex    list:${System.lineSeparator()}${e.message}", e)
        }
    }

这边可以看到实际上运行的是D8MainDexList这个类的generate方法,并传入相关的rules跟files。然而在gradle plugin的源码里并没有这个类。google了一下果然在D8的项目里

 private List<String> run(AndroidApp app, ExecutorService executor)
      throws IOException {
    try {
      DexApplication application =
          new ApplicationReader(app, options, timing).read(executor).toDirect();
      AppView<? extends AppInfoWithSubtyping> appView =
          AppView.createForR8(new AppInfoWithSubtyping(application), options);
      appView.setAppServices(AppServices.builder(appView).build());

    //这里通过rootsetbuilder 返回一个maindexrootset
      RootSet mainDexRootSet =
          new RootSetBuilder(appView, application, options.mainDexKeepRules).run(executor);

      GraphConsumer graphConsumer = options.mainDexKeptGraphConsumer;
      WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = null;
      if (!mainDexRootSet.reasonAsked.isEmpty()) {
        whyAreYouKeepingConsumer = new WhyAreYouKeepingConsumer(graphConsumer);
        graphConsumer = whyAreYouKeepingConsumer;
      }

      Enqueuer enqueuer = EnqueuerFactory.createForMainDexTracing(appView, graphConsumer);
      //这里通过Enqueuer 把mainDexRootSet转换成  Set<DexType> liveTypes
      Set<DexType> liveTypes = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
      // LiveTypes is the result.

     //这里再根据liveTypes生成MainDexClasses。这个里面就是我们maindex里所有的类了。也就是 mainDexList.txt里的数据
      MainDexClasses mainDexClasses = new MainDexListBuilder(liveTypes, application).run();

      List<String> result =
          mainDexClasses.getClasses().stream()
              .map(c -> c.toSourceString().replace('.', '/') + ".class")
              .sorted()
              .collect(Collectors.toList());

      if (options.mainDexListConsumer != null) {
        options.mainDexListConsumer.accept(String.join("\n", result), options.reporter);
      }

      R8.processWhyAreYouKeepingAndCheckDiscarded(
          mainDexRootSet,
          () -> {
            ArrayList<DexProgramClass> classes = new ArrayList<>();
            // TODO(b/131668850): This is not a deterministic order!
            mainDexClasses
                .getClasses()
                .forEach(
                    type -> {
                      DexClass clazz = appView.definitionFor(type);
                      assert clazz.isProgramClass();
                      classes.add(clazz.asProgramClass());
                    });
            return classes;
          },
          whyAreYouKeepingConsumer,
          appView,
          enqueuer,
          true,
          options,
          timing,
          executor);

      return result;
    } catch (ExecutionException e) {
      throw unwrapExecutionException(e);
    }
  }

这边先说一下生成maindexlist之后,同样是r8项目里的ApplicationReader类

  private void readMainDexList(DexApplication.Builder<?> builder, ExecutorService executorService,
      List<Future<?>> futures) {
    if (inputApp.hasMainDexList()) {
      futures.add(executorService.submit(() -> {
        for (StringResource resource : inputApp.getMainDexListResources()) {
          builder.addToMainDexList(MainDexList.parseList(resource, itemFactory));
        }
     这里就是往主dex里写class了
        builder.addToMainDexList(
            inputApp.getMainDexClasses()
                .stream()
                .map(clazz -> itemFactory.createType(DescriptorUtils.javaTypeToDescriptor(clazz)))
                .collect(Collectors.toList()));
      }));
    }
  }

这里参考一下D8的调用参数说明文档,--main-dex-list path指的就是刚生产的main dex list(D8内部的调用居然也是通过命令行来执行的)。

接下来让我们回到D8MainDexList类,看一下具体main dex的类是怎么计算出来的。
首先让我们看一下RootSetBuilder,这里会根据规则过滤所有的类生成mainDexRootSet

 if (rule instanceof ProguardKeepRule) {
        if (clazz.isNotProgramClass()) {
          return;
        }
        switch (((ProguardKeepRule) rule).getType()) {
          case KEEP_CLASS_MEMBERS:
            // Members mentioned at -keepclassmembers always depend on their holder.
            preconditionSupplier = ImmutableMap.of(definition -> true, clazz);
            markMatchingVisibleMethods(
                clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
            markMatchingVisibleFields(
                clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
            break;
          case KEEP_CLASSES_WITH_MEMBERS:
            if (!allRulesSatisfied(memberKeepRules, clazz)) {
              break;
            }
            // fallthrough;
          case KEEP:
            markClass(clazz, rule, ifRule);
            preconditionSupplier = new HashMap<>();
            if (ifRule != null) {
              // Static members in -keep are pinned no matter what.
              preconditionSupplier.put(DexDefinition::isStaticMember, null);
              // Instance members may need to be kept even though the holder is not instantiated.
              preconditionSupplier.put(definition -> !definition.isStaticMember(), clazz);
            } else {
              // Members mentioned at -keep should always be pinned as long as that -keep rule is
              // not triggered conditionally.
              preconditionSupplier.put((definition -> true), null);
            }
            markMatchingVisibleMethods(
                clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
            markMatchingVisibleFields(
                clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
            break;
          case CONDITIONAL:
            throw new Unreachable("-if rule will be evaluated separately, not here.");
        }
        return;
      }

核心代码是遍历所有的类文件根据manifest_keep.txt文件的规则,找出所有的类然后放到RootSet里,这里大部分代码是application下的(我这边由于使用了tinker,tinker会自己在transform时修改keep文件添加自己的相关类)同时比较神奇的是,D8自己把所有的annotation也keep下来了也就是说哪怕你的annotation级别是source 也会当做runtime被keep下来,不知道D8为什么要这么做。

接下来我们看Enqueuer类,这里会对基于RootSet里的所有类做分析,核心是trace方法跟markTypeAsLive方法。这个类的代码及其复杂,这里我也只是看了个大概用来解决问题,有兴趣的可以自己下载D8源码查看。
首先Enqueuer .traceMainDex方法会把之前RootSet里所有类的构造方法标记为alive并构造一个action放到workList里(Enqueuer这个类除了用来构造maindex还用来处理其他逻辑,但是我们这边只分析main dex相关代码)

  private void enqueueRootItems(Map<DexReference, Set<ProguardKeepRuleBase>> items) {
    items.entrySet().forEach(this::enqueueRootItem);
  }


 private void trace(ExecutorService executorService, Timing timing) throws ExecutionException {
    timing.begin("Grow the tree.");
    try {
      while (true) {
        long numOfLiveItems = (long) liveTypes.size();
        numOfLiveItems += (long) liveMethods.items.size();
        numOfLiveItems += (long) liveFields.items.size();
        while (!workList.isEmpty()) {
          Action action = workList.poll();
          switch (action.kind) {
            case MARK_INSTANTIATED:
              processNewlyInstantiatedClass((DexClass) action.target, action.reason);
              break;
            case MARK_REACHABLE_FIELD:
              markInstanceFieldAsReachable((DexField) action.target, action.reason);
              break;
            case MARK_REACHABLE_VIRTUAL:
              markVirtualMethodAsReachable((DexMethod) action.target, false, action.reason);
              break;
            case MARK_REACHABLE_INTERFACE:
              markVirtualMethodAsReachable((DexMethod) action.target, true, action.reason);
              break;
            case MARK_REACHABLE_SUPER:
              markSuperMethodAsReachable((DexMethod) action.target,
                  (DexEncodedMethod) action.context);
              break;
            case MARK_METHOD_KEPT:
              markMethodAsKept((DexEncodedMethod) action.target, action.reason);
              break;
            case MARK_FIELD_KEPT:
              markFieldAsKept((DexEncodedField) action.target, action.reason);
              break;
            case MARK_METHOD_LIVE:
              processNewlyLiveMethod(((DexEncodedMethod) action.target), action.reason);
              break;
            default:
              throw new IllegalArgumentException(action.kind.toString());
          }
        }

        // Continue fix-point processing if -if rules are enabled by items that newly became live.
        long numOfLiveItemsAfterProcessing = (long) liveTypes.size();
        numOfLiveItemsAfterProcessing += (long) liveMethods.items.size();
        numOfLiveItemsAfterProcessing += (long) liveFields.items.size();
        if (numOfLiveItemsAfterProcessing > numOfLiveItems) {
          RootSetBuilder consequentSetBuilder = new RootSetBuilder(appView, rootSet.ifRules);
          IfRuleEvaluator ifRuleEvaluator =
              consequentSetBuilder.getIfRuleEvaluator(
                  liveFields.getItems(),
                  liveMethods.getItems(),
                  liveTypes,
                  targetedMethods.getItems(),
                  executorService);
          ConsequentRootSet consequentRootSet = ifRuleEvaluator.run();
          // TODO(b/132600955): This modifies the root set. Should the consequent be persistent?
          rootSet.addConsequentRootSet(consequentRootSet);
          enqueueRootItems(consequentRootSet.noShrinking);
          // TODO(b/132828740): Seems incorrect that the precondition is not always met here.
          consequentRootSet.dependentNoShrinking.forEach(
              (precondition, dependentItems) -> enqueueRootItems(dependentItems));
          // Check for compatibility rules indicating that the holder must be implicitly kept.
          if (forceProguardCompatibility) {
            consequentRootSet.dependentKeepClassCompatRule.forEach(
                (precondition, compatRules) -> {
                  assert precondition.isDexType();
                  DexClass preconditionHolder = appView.definitionFor(precondition.asDexType());
                  compatEnqueueHolderIfDependentNonStaticMember(preconditionHolder, compatRules);
                });
          }
          if (!workList.isEmpty()) {
            continue;
          }
        }

        // Continue fix-point processing while there are additional work items to ensure items that
        // are passed to Java reflections are traced.
        if (!pendingReflectiveUses.isEmpty()) {
          pendingReflectiveUses.forEach(this::handleReflectiveBehavior);
          pendingReflectiveUses.clear();
        }
        if (!proguardCompatibilityWorkList.isEmpty()) {
          workList.addAll(proguardCompatibilityWorkList);
          proguardCompatibilityWorkList.clear();
        }
        if (!workList.isEmpty()) {
          continue;
        }

        // Notify each analysis that a fixpoint has been reached, and give each analysis an
        // opportunity to add items to the worklist.
        analyses.forEach(analysis -> analysis.notifyFixpoint(this, workList));
        if (!workList.isEmpty()) {
          continue;
        }

        // Reached the fixpoint.
        break;
      }

      if (Log.ENABLED) {
        Set<DexEncodedMethod> allLive = Sets.newIdentityHashSet();
        for (Entry<DexType, SetWithReason<DexEncodedMethod>> entry : reachableVirtualMethods
            .entrySet()) {
          allLive.addAll(entry.getValue().getItems());
        }
        Set<DexEncodedMethod> reachableNotLive = Sets.difference(allLive, liveMethods.getItems());
        Log.debug(getClass(), "%s methods are reachable but not live", reachableNotLive.size());
        Log.info(getClass(), "Only reachable: %s", reachableNotLive);
        Set<DexType> liveButNotInstantiated =
            Sets.difference(liveTypes, instantiatedTypes.getItems());
        Log.debug(getClass(), "%s classes are live but not instantiated",
            liveButNotInstantiated.size());
        Log.info(getClass(), "Live but not instantiated: %s", liveButNotInstantiated);
        SetView<DexEncodedMethod> targetedButNotLive = Sets
            .difference(targetedMethods.getItems(), liveMethods.getItems());
        Log.debug(getClass(), "%s methods are targeted but not live", targetedButNotLive.size());
        Log.info(getClass(), "Targeted but not live: %s", targetedButNotLive);
      }
      assert liveTypes.stream().allMatch(DexType::isClassType);
      assert instantiatedTypes.getItems().stream().allMatch(DexType::isClassType);
    } finally {
      timing.end();
    }
    unpinLambdaMethods();
  }

 private void markTypeAsLive(DexType type, ScopedDexMethodSet seen) {
    type = type.toBaseType(appView.dexItemFactory());
    if (!type.isClassType()) {
      // Ignore primitive types.
      return;
    }
    if (liveTypes.add(type)) {
      if (Log.ENABLED) {
        Log.verbose(getClass(), "Type `%s` has become live.", type);
      }
      DexClass holder = appView.definitionFor(type);
      if (holder == null) {
        reportMissingClass(type);
        return;
      }
      for (DexType iface : holder.interfaces.values) {
        markInterfaceTypeAsLiveViaInheritanceClause(iface);
      }
      if (holder.superType != null) {
        ScopedDexMethodSet seenForSuper =
            scopedMethodsForLiveTypes.computeIfAbsent(
                holder.superType, ignore -> new ScopedDexMethodSet());
        seen.setParent(seenForSuper);
        DexClass holderSuper = appView.definitionFor(holder.superType);
        if (holderSuper != null && holderSuper.isProgramClass()) {
          registerType(holder.superType, KeepReason.reachableFromLiveType(type));
        }
        markTypeAsLive(holder.superType, seenForSuper);
        if (holder.isNotProgramClass()) {
          // Library classes may only extend other implement library classes.
          ensureNotFromProgramOrThrow(holder.superType, type);
          for (DexType iface : holder.interfaces.values) {
            ensureNotFromProgramOrThrow(iface, type);
          }
        }
      }

      KeepReason reason = KeepReason.reachableFromLiveType(type);

      // We cannot remove virtual methods defined earlier in the type hierarchy if it is widening
      // access and is defined in an interface:
      //
      // public interface I {
      //   void clone();
      // }
      //
      // class Model implements I {
      //   public void clone() { ... } <-- this cannot be removed
      // }
      //
      // Any class loading of Model with Model.clone() removed will result in an illegal access
      // error because their exists an existing implementation (here it is Object.clone()). This is
      // only a problem in the DEX VM. We have to make this check no matter the output because
      // CF libraries can be used by Android apps. See b/136698023 for more information.
      holder
          .virtualMethods()
          .forEach(
              m -> {
                if (seen.addMethodIfMoreVisible(m)
                        == AddMethodIfMoreVisibleResult.ADDED_MORE_VISIBLE
                    && holder.isProgramClass()
                    && appView.appInfo().methodDefinedInInterfaces(m, holder.type)) {
                  markMethodAsTargeted(m, reason);
                }
              });

      // We also need to add the corresponding <clinit> to the set of live methods, as otherwise
      // static field initialization (and other class-load-time sideeffects) will not happen.
      if (holder.isProgramClass() && holder.hasClassInitializer()) {
        DexEncodedMethod clinit = holder.getClassInitializer();
        if (clinit != null && clinit.getOptimizationInfo().mayHaveSideEffects()) {
          assert clinit.method.holder == holder.type;
          markDirectStaticOrConstructorMethodAsLive(clinit, reason);
        }
      }

      if (holder.isProgramClass() && holder.isSerializable(appView)) {
        enqueueFirstNonSerializableClassInitializer(holder, reason);
      }

      if (holder.isProgramClass()) {
        if (!holder.annotations.isEmpty()) {
          processAnnotations(holder, holder.annotations.annotations);
        }
        // If this type has deferred annotations, we have to process those now, too.
        Set<DexAnnotation> annotations = deferredAnnotations.remove(type);
        if (annotations != null && !annotations.isEmpty()) {
          assert holder.accessFlags.isAnnotation();
          assert annotations.stream().allMatch(a -> a.annotation.type == holder.type);
          annotations.forEach(annotation -> handleAnnotation(holder, annotation));
        }
      } else {
        assert !deferredAnnotations.containsKey(holder.type);
      }
      rootSet.forEachDependentStaticMember(holder, appView, this::enqueueDependentItem);
      compatEnqueueHolderIfDependentNonStaticMember(
          holder, rootSet.getDependentKeepClassCompatRule(holder.getType()));
    }
  }

最后是MainDexDirectReferenceTracer类

 public void run(Set<DexType> roots) {
    for (DexType type : roots) {
      DexClass clazz = appInfo.definitionFor(type);
      // Should only happen for library classes, which are filtered out.
      assert clazz != null;
      consumer.accept(type);
      // Super and interfaces are live, no need to add them.
     //对当前类的返回类型跟参数类型添加到输出里
      traceAnnotationsDirectDependencies(clazz.annotations);
   //  对当前类的所有变量的类型添加到输出里
      clazz.forEachField(field -> consumer.accept(field.field.type));
   //  对当前类的所有方法添加返回类型跟参数到输出里
      clazz.forEachMethod(method -> {
        traceMethodDirectDependencies(method.method, consumer);
 //  对当前类的所有方法添加返回类型跟参数到输出里并且对当前method调用DirectReferencesCollector
        method.registerCodeReferences(codeDirectReferenceCollector);
      });
    }
  }

private void traceMethodDirectDependencies(DexMethod method, Consumer<DexType> consumer) {
    DexProto proto = method.proto;
 //添加该方法返回类型
    consumer.accept(proto.returnType);
    for (DexType parameterType : proto.parameters.values) {
    //添加该方法所有的参数类型
      consumer.accept(parameterType);
    }
  }

private class DirectReferencesCollector extends UseRegistry {

    private DirectReferencesCollector(DexItemFactory factory) {
      super(factory);
    }

    @Override
    public boolean registerInvokeVirtual(DexMethod method) {
      return registerInvoke(method);
    }

    @Override
    public boolean registerInvokeDirect(DexMethod method) {
      return registerInvoke(method);
    }

    @Override
    public boolean registerInvokeStatic(DexMethod method) {
      return registerInvoke(method);
    }

    @Override
    public boolean registerInvokeInterface(DexMethod method) {
      return registerInvoke(method);
    }

    @Override
    public boolean registerInvokeSuper(DexMethod method) {
      return registerInvoke(method);
    }

    protected boolean registerInvoke(DexMethod method) {
     //添加所有方法内方法的持有者类型
      consumer.accept(method.holder);
      traceMethodDirectDependencies(method, consumer);
      return true;
    }

    @Override
    public boolean registerInstanceFieldWrite(DexField field) {
      return registerFieldAccess(field);
    }

    @Override
    public boolean registerInstanceFieldRead(DexField field) {
      return registerFieldAccess(field);
    }

    @Override
    public boolean registerStaticFieldRead(DexField field) {
      return registerFieldAccess(field);
    }

    @Override
    public boolean registerStaticFieldWrite(DexField field) {
      return registerFieldAccess(field);
    }

    protected boolean registerFieldAccess(DexField field) {
  //添加所有变量的类的类型跟持有该变量的类的类型
      consumer.accept(field.holder);
      consumer.accept(field.type);
      return true;
    }

    @Override
    public boolean registerNewInstance(DexType type) {
    //添加所有实例的类
      consumer.accept(type);
      return true;
    }

    @Override
    public boolean registerTypeReference(DexType type) {
//添加声明的所有类型
      consumer.accept(type);
      return true;
    }
  }

以上添加类型时会同时添加当前类与其supertype
最后就把以上结果输出到最开始提到的mainDexList.txt里让后续D8根据这个来拆分dex。

相关文章

网友评论

      本文标题:D8编译器下multi dex main dex 是如何创建的

      本文链接:https://www.haomeiwen.com/subject/hvptsctx.html