Android Too many classes in--main-dex-listエラー原因とAndroidパケット原理

11786 ワード

[TOC]
エラー表現
  • appはパッケージングできません.ログは
  • です.
    com.android.dex.DexException:Too many classes in --main-dex-list, main dex capacity exceeded
    

    エラーの原因
    生成された最初のclasses.dexにおけるメソッド数は65535,すなわちShort.MAX_VALUEである.
    AndroidがAPKツールチェーンを生成するdxソースにはdalvik/dx/src/com/android/dx/command/dexer/Main.java
    if (args.mainDexListFile != null) {
      // with --main-dex-list
      // ...
      // forced in main dex
      for (int i = 0; i < fileNames.length; i++) {
        // call processClass
        processOne(fileNames[i], mainPassFilter);
      }
      if (dexOutputArrays.size() > 0) {
        throw new DexException("Too many classes in " + Arguments.MAIN_DEX_LIST_OPTION
          + ", main dex capacity exceeded");
      }
    }
    
    processClass
    private static boolean processClass(String name, byte[] bytes) {
      int numMethodIds = outputDex.getMethodIds().items().size();
      int numFieldIds = outputDex.getFieldIds().items().size();
      int constantPoolSize = cf.getConstantPool().size();
      int maxMethodIdsInDex = numMethodIds + constantPoolSize + cf.getMethods().size() +
      MAX_METHOD_ADDED_DURING_DEX_CREATION;
      int maxFieldIdsInDex = numFieldIds + constantPoolSize + cf.getFields().size() +
      MAX_FIELD_ADDED_DURING_DEX_CREATION;
      if (args.multiDex
        && (outputDex.getClassDefs().items().size() > 0)
        && ((maxMethodIdsInDex > args.maxNumberOfIdxPerDex) ||
          (maxFieldIdsInDex > args.maxNumberOfIdxPerDex))) {
        DexFile completeDex = outputDex;
      createDexFile();
    }
    

    createDexFileは、dexOutputArrays processAllFilesがdexOutputArraysに遭遇する新しいByte[]オブジェクトを作成します.size>0はDexExceptionを投げます
    パケットプロセス解析
    下請けの理由
    Androidシステムが実行アプリケーションをインストールする際、dexを実行最適化し、実行効率を向上させる最適化の過程で、アセンブリファイルのロードを処理した最適化をdexOptと呼ぶ
    dexOptの実行プロセスは、初めてDexファイルをロードしたときに実行されます.このプロセスは、odexファイルを生成します.つまり、Optimised dex odexの用途は、プログラムリソースと実行可能ファイルを分離し、odex ファイルをプリコンパイル処理して実行する効率が、dex jar ファイルを直接実行する効率よりもずっと高いです.
    dexOptは、クラスごとのメソッドidを検索するように設計されており、チェーンテーブル構造に存在するこのチェーンテーブルの長さはshortタイプで保存されており、メソッドidの数が65536を超えない(Short.MAX_VALUE)
  • Dalvikモードでは、dexOptは
  • をオンにします.
  • ARTモードでは、JITではないが、この最適化ポリシー
  • が多重化されている.
    メソッド数が限界を超えた問題を解決するには、dexファイルを2つ以上に分解する必要があります.
    Google公式ドキュメントhttps://developer.android.com/tools/building/multidex.html#about
    ぶんかつプロセス
  • は、優先的にロードすべきクラスがプライマリパケット
  • にあることを発見する.
  • 残りのクラスは参照順に残りのパケット
  • にランダムに移動する.
  • パッケージ構築APPリリース
  • パッケージを実行するAPPは、Applicaionがインスタンス化された後、システムバージョンがmultidex
  • をサポートしているかどうかを確認します.
  • 実行ローディングメインパケットDalvikモードサブパケットをアプリケーションの砂箱ディレクトリARTにコピーするメインパケットを実行し、サブパケットを混合するoatファイル
  • を生成する.
  • Dalvik実行時、コンパイル実行メインパッケージを実行し、その後、反射によってサブdexを現在のclassloaderに注入する
  • .
  • ARTモードは、複数のdexを含むoatファイルをロードし、その後、実行パケットを解釈し、反射によってサブdexを現在のclassloaderに注入する
  • を説明する.
    Android実行時にARTがOATファイルをロードするプロセス分析http://blog.csdn.net/luoshengyang/article/details/39307813
    5.0システム前後の下請けサポート
  • Android5.0より前に、Dalvik方式で動作し、メインパケットをロードした後、残りのパケット
  • を反射的にロードする.
  • Android5.0以降、ART方式で実行する、ARTプリコンパイル時に、メインパケットとサブパケットをスキャンし、.oatファイルを生成してユーザが
  • を実行する.
    下請け案の隠れた危険性
  • API14 Dalvik linearalloc bug
  • 複雑な依存する工事,パケット化後,異なる依存項目間のdexファイル関数が相互に呼び出され,
  • は混同された工事を有しており、依存粘着(異なる依存項目間のdexファイルの同じクラス定義ツリー)が非常に発生しやすく、インストール時に
  • を報告する.
  • 開発過程には下請けがあり、 を招き、開発効率が
  • 低下した.
  • パケットファイルが大きすぎて、インストール分割dexファイルはいくつかのデバイスでよくありません. ANR(この問題5.0以降のデバイスはインストールに成功しません)
  • .
  • 、6番目に分けた後に現れやすくて、原理は上の点
  • と同じです
  • アプリケーションはmultiedexを使用すると反射が大量に使用され、比較的大きなメモリが使用されます.OOM
  • 工事が過大で、管理に依存して混乱している場合、主パケットはロードする必要があるため、方法数は65536を超え、
  • を招く.
    Multiedexの隠れた危険性を解決する考え方
  • 無意味なコードを削除し、無意味なリソース
  • 重複コードホイール
  • を削除する.
  • は重いモジュールを分割し、依存が多ければ多いほど
  • を維持しやすくなる.
  • エンジニアリング依存ループの複雑さを低減する(サイクル依存ではなく反射でデカップリングする)
  • 開発とリリース構築を分離し、モジュール機能をできるだけ最小化し、モジュール自体のパケット化の可能性を低減する
  • 混同を活用してdexファイルのサイズを下げる(混同は実際には安全な役割を果たせず、Dalvikアセンブリは解読混同を把握しやすい)
  • .
  • バイナリ最適化ツールを使用してdexのサイズを
  • 小さくする
  • 業務が多すぎて、複数のAPP通信の方式をしたり、分子appを分解して
  • を動的にロードしたりします.
  • 最適化主パケット依存
  • プライマリ・パケットの詳細
    プライマリ・パケットはAndroidでコンパイルされ、リリースされ、実行時の地位が高いが、プライマリ・パケットの生成は分析されたmaindexlist.txtによって生成される.
    maindexlist.txt分析の作成
    ソースアドレス
    android gradle plugin maindexlistの作成を担当するクラスがあります.txtはCreateMainDexListと呼ばれています
    ソースコードtools/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/CreateMainDexList.groovy
    https://android.googlesource.com/platform/tools/base/+/master/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/CreateManifestKeepList.groovy
    @TaskAction
    void output() {
        if (getAllClassesJarFile() == null) {
            throw new NullPointerException("No input file")
        }
    
        // manifest components plus immediate dependencies must be in the main dex.
        File _allClassesJarFile = getAllClassesJarFile()
        Set mainDexClasses = callDx(_allClassesJarFile, getComponentsJarFile())
        ...
    }
    
    callDx最終呼び出しAndroidBuilder.createMainDexList実際には、バックグラウンドプロセス実行ClassReferenceListBuilder.main分析クラスの依存関係を開くことによって、maindexlist.txtを生成する
    public void addRoots(ZipFile jarOfRoots) throws IOException {
        ...
        for (Enumeration extends ZipEntry> entries = jarOfRoots.entries();
          entries.hasMoreElements();) {
          ZipEntry entry = entries.nextElement();
          String name = entry.getName();
          if (name.endsWith(CLASS_EXTENSION)) {
              DirectClassFile classFile;
              ...
              classFile = path.getClass(name);
              ...
              addDependencies(classFile.getConstantPool());
          }
      }
    }
    
    ClassReferenceListBuilder.addRootsこのクラスの依存関係を解析するために、componentClasses.jarの各entryを読み取りファイルを巡回することによって、addDependenciesを呼び出します.
    private void addDependencies(ConstantPool pool) {
        for (Constant constant : pool.getEntries()) {
            if (constant instanceof CstType) {
                Type type = ((CstType) constant).getClassType();
                String descriptor = type.getDescriptor();
                if (descriptor.endsWith(";")) {
                    int lastBrace = descriptor.lastIndexOf('[');
                    if (lastBrace < 0) {
                        addClassWithHierachy(descriptor.substring(1, descriptor.length()-1));
                    } else {
                        assert descriptor.length() > lastBrace + 3
                        && descriptor.charAt(lastBrace + 1) == 'L';
                        addClassWithHierachy(descriptor.substring(lastBrace + 2,
                                descriptor.length() - 1));
                    }
                }
            }
        }
    }
    
    addDependencies ConstantPoolからimport が得られ、addClassWithHierachy が呼び出される
    また、javap -verboseで先に逆アセンブリし、一致"=class"の文字列を解析することでデバッグすることもできます.
    依存関係解析が終了するとmaindexlistが出力されます.txt
    だから一言でcomponentClasses.jarは最終的にmaindexlist.txtの大きさを決定しました
    componentClasses.JAr生成分析
    この中間生成されたcomponentClasses.jarは、最終的にパッケージングに成功した後に削除されます.
    もちろん方法が超過した場合、このパッケージはmoduel/build/intermediates/multi-dex/対応ルートの中にあります.
    gradle plugin 2.3.0以降位置が変動しますが、同じように見つけることができますcomponentClasses.jar生成タスクproguardComponentsTaskmanifest_keep.txtによりallclasses.jarから抽出されて生成され、manifest_keep.txtの内容は一般的に
    -keep class com.xxx.app.XXXXApplication {
      ();
      void attachBaseContext(android.content.Context);
    }
    -keep class com.xxx.splash.XXXXActivity { (); }
    -keep class com.xxx.app.MainActivity { (); }
    -keep class com.xxx.login.xxxx.LoginActivity { (); }
    -keep class com.xxx.sidebar.account.XXAccountActivity { (); }
    ...
    
    manifest_keep.txtCreateManifestKeepList解析AndroidManifest.xml により得られた./tools/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/CreateManifestKeepList.groovy
        @TaskAction
        void generateKeepListFromManifest() {
            SAXParser parser = SAXParserFactory.newInstance().newSAXParser()
            Writer out = new BufferedWriter(new FileWriter(getOutputFile()))
            try {
                parser.parse(getManifest(), new ManifestHandler(out))
                // add a couple of rules that cannot be easily parsed from the manifest.
                out.write(
    """-keep public class * extends android.app.backup.BackupAgent {
        ();
    }
    -keep public class * extends java.lang.annotation.Annotation {
        *;
    }
    """)
                if (proguardFile != null) {
                    out.write(Files.toString(proguardFile, Charsets.UTF_8))
                }
            } finally {
                out.close()
            }
        }
        ...
    
    CreateManifestKeepListプライベート内部クラスManifestHandler CreateManifestKeepList.KEEP_SPECS[qName] manifest_keep.txtでどのクラスを入れる必要があるかを決定するKEEP_SPECS
    private class ManifestHandler extends DefaultHandler {
        ...
        @Override
        void startElement(String uri, String localName, String qName, Attributes attr) {
            String keepSpec = CreateManifestKeepList.KEEP_SPECS[qName]
            if (keepSpec) {
                boolean keepIt = true
                if (CreateManifestKeepList.this.filter) {
                    Map attrMap = [:]
                    for (int i = 0; i < attr.getLength(); i++) {
                        attrMap[attr.getQName(i)] = attr.getValue(i)
                    }
                    keepIt = CreateManifestKeepList.this.filter(qName, attrMap)
                }
    
                if (keepIt) {
                    String nameValue = attr.getValue('android:name')
                    if (nameValue != null) {
                        out.write((String) "-keep class ${nameValue} $keepSpec
    ") }

    ろ過されたandroid.support.multidex
        private static String DEFAULT_KEEP_SPEC = "{ (); }"
        private static Map KEEP_SPECS = [
            'application' : """{
        ();
        void attachBaseContext(android.content.Context);
    }""",
            'activity' : DEFAULT_KEEP_SPEC,
            'service' : DEFAULT_KEEP_SPEC,
            'receiver' : DEFAULT_KEEP_SPEC,
            'provider' : DEFAULT_KEEP_SPEC,
            'instrumentation' : DEFAULT_KEEP_SPEC,
        ]
    

    少なくともAndroidManifestはxmlで
  • application
  • activity
  • service
  • receiver
  • provider
  • instrumentation

  • この6種類のラベルのクラス
    および継承
  • java.lang.annotation.Annotation
  • android.app.backup.BackupAgent

  • のクラスはmaindexlistを生成するために使用されます.txt
    まとめて、メインパケットに含まなければならないクラスは
  • apkが実行するロードメカニズムに基づいて、Applicationの参照は、プライマリパッケージ内の
  • に肯定的である.
  • MultiDexパケットを開くと、java.lang.annotation.Annotationパケットは必ずメインパケット内の
  • パケットでなければならない.
  • は、メインパッケージ
  • においてandroid.app.backup.BackupAgentを継承する.
  • AndroidManifest.xmlに登録されている4つのコンポーネントは、最初のパッケージ
  • にある必要があります.
  • は、instrumentationの試験技術を用いる、第1のパケット内に
  • なければならない.