Android Too many classes in--main-dex-listエラー原因とAndroidパケット原理
11786 ワード
[TOC]
エラー表現 appはパッケージングできません.ログは です.
エラーの原因
生成された最初のclasses.dexにおけるメソッド数は
AndroidがAPKツールチェーンを生成するdxソースには
createDexFileは、dexOutputArrays processAllFilesがdexOutputArraysに遭遇する新しいByte[]オブジェクトを作成します.size>0はDexExceptionを投げます
パケットプロセス解析
下請けの理由
Androidシステムが実行アプリケーションをインストールする際、dexを実行最適化し、実行効率を向上させる最適化の過程で、アセンブリファイルのロードを処理した最適化をdexOptと呼ぶ
dexOptの実行プロセスは、初めてDexファイルをロードしたときに実行されます.このプロセスは、
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より前に、 を反射的にロードする. Android5.0以降、 を実行する.
下請け案の隠れた危険性 複雑な依存する工事,パケット化後,異なる依存項目間のdexファイル関数が相互に呼び出され, は混同された工事を有しており、依存粘着(異なる依存項目間のdexファイルの同じクラス定義ツリー)が非常に発生しやすく、インストール時に を報告する.開発過程には下請けがあり、 低下した.パケットファイルが大きすぎて、インストール分割dexファイルはいくつかのデバイスでよくありません. . と同じですアプリケーションはmultiedexを使用すると反射が大量に使用され、比較的大きなメモリが使用されます. 工事が過大で、管理に依存して混乱している場合、主パケットはロードする必要があるため、方法数は65536を超え、 を招く.
Multiedexの隠れた危険性を解決する考え方無意味なコードを削除し、無意味なリソース 重複コードホイール を削除する.は重いモジュールを分割し、依存が多ければ多いほど を維持しやすくなる.エンジニアリング依存ループの複雑さを低減する(サイクル依存ではなく反射でデカップリングする) 開発とリリース構築を分離し、モジュール機能をできるだけ最小化し、モジュール自体のパケット化の可能性を低減する 混同を活用してdexファイルのサイズを下げる(混同は実際には安全な役割を果たせず、Dalvikアセンブリは解読混同を把握しやすい) .バイナリ最適化ツールを使用してdexのサイズを 小さくする業務が多すぎて、複数のAPP通信の方式をしたり、分子appを分解して を動的にロードしたりします.最適化主パケット依存 プライマリ・パケットの詳細
プライマリ・パケットはAndroidでコンパイルされ、リリースされ、実行時の地位が高いが、プライマリ・パケットの生成は分析された
maindexlist.txt分析の作成
ソースアドレス
android gradle plugin maindexlistの作成を担当するクラスがあります.txtは
ソースコード
https://android.googlesource.com/platform/tools/base/+/master/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/CreateManifestKeepList.groovy
また、
依存関係解析が終了するとmaindexlistが出力されます.txt
だから一言で
componentClasses.JAr生成分析
この中間生成された
もちろん方法が超過した場合、このパッケージは
gradle plugin 2.3.0以降位置が変動しますが、同じように見つけることができます
ろ過された
少なくともAndroidManifestはxmlで application activity service receiver provider instrumentation
この6種類のラベルのクラス
および継承 java.lang.annotation.Annotation android.app.backup.BackupAgent
のクラスはmaindexlistを生成するために使用されます.txt
まとめて、メインパケットに含まなければならないクラスは apkが実行するロードメカニズムに基づいて、Applicationの参照は、プライマリパッケージ内の に肯定的である. MultiDexパケットを開くと、 パケットでなければならない.は、メインパッケージ において にある必要があります.は、 なければならない.
エラー表現
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)
メソッド数が限界を超えた問題を解決するには、dexファイルを2つ以上に分解する必要があります.
Google公式ドキュメントhttps://developer.android.com/tools/building/multidex.html#about
ぶんかつプロセス
Android実行時にARTがOATファイルをロードするプロセス分析http://blog.csdn.net/luoshengyang/article/details/39307813
5.0システム前後の下請けサポート
Dalvik
方式で動作し、メインパケットをロードした後、残りのパケットART
方式で実行する、ARTプリコンパイル時に、メインパケットとサブパケットをスキャンし、.oat
ファイルを生成してユーザが下請け案の隠れた危険性
API14
Dalvik linearalloc bug
を招き、開発効率が ANR
(この問題5.0以降のデバイスはインストールに成功しません)
、6番目に分けた後に現れやすくて、原理は上の点OOM
Multiedexの隠れた危険性を解決する考え方
プライマリ・パケットは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
生成タスクproguardComponentsTask
manifest_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.txt
はCreateManifestKeepList
解析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で
この6種類のラベルのクラス
および継承
のクラスはmaindexlistを生成するために使用されます.txt
まとめて、メインパケットに含まなければならないクラスは
java.lang.annotation.Annotation
パケットは必ずメインパケット内のandroid.app.backup.BackupAgent
を継承する.AndroidManifest.xml
に登録されている4つのコンポーネントは、最初のパッケージinstrumentation
の試験技術を用いる、第1のパケット内に