コードを縮小する方法-dexでの65kメソッドの制限


91

多くのライブラリプロジェクトに依存するかなり大きなAndroidアプリがあります。Androidコンパイラには、.dexファイルごとに65536メソッドの制限があり、その数を超えています。

メソッドの制限に達したときに選択できるパスは、基本的に2つあります(少なくとも私が知る限り)。

1)コードを縮小する

2)複数のdexファイルを作成します(このブログ投稿を参照

私は両方を調べて、メソッド数が非常に高くなる原因を見つけようとしました。Google Drive APIは、12,000以上のGuava依存関係を持つ最大のチャンクです。Drive API v2の合計ライブラリは23,000以上に達します!

私の質問では、どうすればよいと思いますか。アプリの機能としてGoogleドライブの統合を削除する必要がありますか?APIを縮小する方法はありますか(はい、私はプロガードを使用しています)?複数のdexルートを使用する必要がありますか(特にサードパーティのAPIを扱う場合)、かなり苦痛です。


2
私はたまたまあなたのアプリが大好きです。疑似apk形式ですべての追加ライブラリの必須ダウンロードを作成することを考えましたか?私は個人的にドライブの統合を見たいと思っています
JBirdVegas 2013年

8
Facebookは最近、Androidアプリでほぼ同じ問題のように思われる問題に対する回避策を文書化しました。:役に立つかもしれfacebook.com/notes/facebook-engineering/...
ルーベンScratton

4
複数のdexルートに向かい始めます。Googleドライブで使用するセカンダリdexファイルを作成しました。私は依存関係としてグアバを必要とする人にとっては悪い気分です。:Pそれは私にとってはまだかなり大きな問題です
Jared Rummler 2013年

4
メソッドをどのようにカウントしますか?
Bri6ko

1
ここでのいくつかの追加メモ:stackoverflow.com/questions/21490382(APKのメソッド参照をリストするユーティリティへのリンクを含む)。64Kの制限は、いくつかのコメントがリンクされているFacebookの問題とは無関係であることに注意してください。
2014年

回答:


69

Googleはついにdexファイルの65Kメソッドの制限を超えるための回避策/修正を実装したようです。

65K参照制限について

Androidアプリケーション(APK)ファイルには、Dalvik Executable(DEX)ファイルの形式で実行可能なバイトコードファイルが含まれています。このファイルには、アプリの実行に使用されるコンパイル済みコードが含まれています。Dalvik実行可能ファイルの仕様では、Androidフレームワークメソッド、ライブラリメソッド、独自のコード内のメソッドなど、単一のDEXファイル内で参照できるメソッドの総数を65,536に制限しています。この制限を超えるには、multidex構成と呼ばれる複数のDEXファイルを生成するようにアプリのビルドプロセスを構成する必要があります。

Android 5.0より前のMultidexサポート

Android 5.0より前のバージョンのプラットフォームでは、アプリコードの実行にDalvikランタイムが使用されています。デフォルトでは、DalvikはアプリをAPKごとに単一のclasses.dexバイトコードファイルに制限します。この制限を回避するには、multidexサポートライブラリを使用します。これは、アプリのプライマリDEXファイルの一部になり、追加のDEXファイルとそれらに含まれるコードへのアクセスを管理します。

Android 5.0以降のMultidexサポート

Android 5.0以降では、ARTと呼ばれるランタイムを使用しています。このランタイムは、アプリケーションAPKファイルから複数のdexファイルの読み込みをネイティブでサポートしています。ARTはアプリケーションのインストール時にプリコンパイルを実行し、classs(.. N).dexファイルをスキャンして、Androidデバイスで実行するためにそれらを単一の.oatファイルにコンパイルします。Android 5.0ランタイムの詳細については、ARTの紹介を参照してください。

参照:65,000を超えるメソッドを使用したアプリの構築


Multidexサポートライブラリ

このライブラリは、複数のDalvik実行可能(DEX)ファイルを使用してアプリを構築するためのサポートを提供します。65536を超えるメソッドを参照するアプリは、multidex構成を使用する必要があります。multidexの使用の詳細については、65K以上のメソッドを使用したアプリの構築を参照してください。

このライブラリは、Androidサポートライブラリをダウンロードした後、/ extras / android / support / multidex /ディレクトリにあります。ライブラリにはユーザーインターフェイスリソースは含まれていません。これをアプリケーションプロジェクトに含めるには、リソースなしでライブラリ追加する手順に従ってください

このライブラリのGradleビルドスクリプト依存関係識別子は次のとおりです。

com.android.support:multidex:1.0.+この依存関係表記は、リリースバージョン1.0.0以降を指定します。


プロガードを積極的に使用して依存関係を確認することにより、65Kのメソッド制限に達するのを回避する必要があります。


6
+1、なぜ同じ人から回答されたときに、人々が正解に賛成しないのですか?
Pacerier 2014年

最小APIレベルは14になります!
Vihaan Verma

5
各ビルドでの現在のメソッド数を提供する小さなGradleプラグインを作成しました。-ライブラリを管理するために、私たちに役に立ちましたgithub.com/KeepSafe/dexcount-gradle-plugin
フィリップ・

53

あなたは有効にするには、そのためmultidexサポートライブラリを使用することができますmultidex

1)依存関係に含めます:

dependencies {
  ...
  compile 'com.android.support:multidex:1.0.0'
}

2)アプリで有効にします。

defaultConfig {
    ...
    minSdkVersion 14
    targetSdkVersion 21
    ....
    multiDexEnabled true
}

3)アプリのアプリケーションクラスがある場合は、次のようにattachBaseContextメソッドをオーバーライドします。

package ....;
...
import android.support.multidex.MultiDex;

public class MyApplication extends Application {
  ....
   @Override
   protected void attachBaseContext(Context context) {
    super.attachBaseContext(context);
    MultiDex.install(this);
   }
}

4)アプリケーションのアプリケーションクラスがない場合は、android.support.multidex.MultiDexApplicationをアプリケーションとしてマニフェストファイルに登録します。このような:

<application
    ...
    android:name="android.support.multidex.MultiDexApplication">
    ...
</application>

そしてそれはうまくいくはずです!


31

Play Services6.5以降のヘルプ:http ://android-developers.blogspot.com/2014/12/google-play-services-and-dex-method.html

「Google Play開発者サービスのバージョン6.5以降では、多数の個別のAPIから選択できるようになり、表示できます。」

...

「これには、すべてのAPIで使用される「ベース」ライブラリが推移的に含まれます。」

これは良いニュースです。たとえば、単純なゲームの場合、おそらく必要なのはbasegamesそしておそらくdriveです。

「API名の完全なリストは以下のとおりです。詳細については、Androidデベロッパーサイトをご覧ください。:

  • com.google.android.gms:play-services-base:6.5.87
  • com.google.android.gms:play-services-ads:6.5.87
  • com.google.android.gms:play-services-appindexing:6.5.87
  • com.google.android.gms:play-services-maps:6.5.87
  • com.google.android.gms:play-services-location:6.5.87
  • com.google.android.gms:play-services-fitness:6.5.87
  • com.google.android.gms:play-services-panorama:6.5.87
  • com.google.android.gms:play-services-drive:6.5.87
  • com.google.android.gms:play-services-games:6.5.87
  • com.google.android.gms:play-services-wallet:6.5.87
  • com.google.android.gms:play-services-identity:6.5.87
  • com.google.android.gms:play-services-cast:6.5.87
  • com.google.android.gms:play-services-plus:6.5.87
  • com.google.android.gms:play-services-appstate:6.5.87
  • com.google.android.gms:play-services-wearable:6.5.87
  • com.google.android.gms:play-services-all-wear:6.5.87

Eclipseプロジェクト内でそれを行う方法に関する情報はありますか?
ブライアンホワイト

私はまだそのバージョンにアップグレードする立場にはありません。しかし、プロジェクトがMavenベースの場合、うまくいけば、あなたはあなたのmaven pomでそれを解決する必要があります。
Csaba Toth 2015年

@ webo80ええと、これはバージョン6.5.87までの場合にのみ役立ちます。proguardが未使用の関数を削除するので、peteyの答えについて疑問に思います。それには、サードパーティのライブラリも関係しているのでしょうか、それとも自分のものだけなのでしょうか。プロガードについてもっと読むべきだ。
Csaba Toth 2015年

今、いくつかの外部ツールとの.jarファイルを取り除くように見えるための唯一の解決策を@BrianWhite ...
milosmns

私はこのツールを使用してしまいました:gist.github.com/dextorer/a32cad7819b7f272239b
ブライアンホワイト

9

6.5より前のバージョンのGoogle Play開発者サービスでは、APIのパッケージ全体をアプリにコンパイルする必要がありました。場合によっては、そうすることで、アプリ内のメソッド(フレームワークAPI、ライブラリメソッド、独自のコードを含む)の数を65,536の制限内に収めることが困難になります。

バージョン6.5以降、代わりにGoogle PlayサービスAPIをアプリに選択的にコンパイルできます。たとえば、Google Fit APIとAndroid Wear APIのみを含めるには、build.gradleファイルの次の行を置き換えます。

compile 'com.google.android.gms:play-services:6.5.87'

これらの行で:

compile 'com.google.android.gms:play-services-fitness:6.5.87'
compile 'com.google.android.gms:play-services-wearable:6.5.87'

詳細については、ここをクリックしてください


日食でそれを行う方法?
Hardik9850 2015

7

使用されていないメソッドは最終ビルドに含まれないため、プロガードを使用してAPKを軽量化します。proguard設定ファイルでguavaでproguardを使用するために以下を確認します(すでにお持ちの場合は申し訳ありませんが、執筆時点ではわかりませんでした)。

# Guava exclusions (http://code.google.com/p/guava-libraries/wiki/UsingProGuardWithGuava)
-dontwarn sun.misc.Unsafe
-dontwarn com.google.common.collect.MinMaxPriorityQueue
-keepclasseswithmembers public class * {
    public static void main(java.lang.String[]);
} 

# Guava depends on the annotation and inject packages for its annotations, keep them both
-keep public class javax.annotation.**
-keep public class javax.inject.**

さらに、ActionbarSherlockを使用している場合、v7 appcompatサポートライブラリに切り替えると、メソッド数が大幅に削減されます(個人的な経験に基づく)。手順は次のとおりです。


これはWarning: butterknife.internal.ButterKnifeProcessor: can't find superclass or interface javax.annotation.processing.AbstractProcessor./gradlew :myapp:proguardDevDebug
約束の

1
ただし、開発中は通常、proguardは実行されず(最終的にはEclipseでは実行されません)、リリースビルドを実行するまで縮小のメリットを享受できません。
ブライアンホワイト

7

Jar Jar Linksを使用して、Google Play Services(16Kメソッド!)などの巨大な外部ライブラリを縮小できます。

あなたのケースではcommon internal、とdriveサブパッケージを除いて、Google Play Services jarからすべてをリッピングするだけです。


4

Gradleを使用していないEclipseユーザーのために、Google Play Services jarを分解し、必要な部分だけで再構築するツールがあります。

私はdextorerのstrip_play_services.shを使用しています

いくつかの内部依存関係があるため、どのサービスを含めるかを正確に知るのは難しい場合がありますが、必要なものが不足していることが判明した場合は、小規模から開始して構成に追加できます。




2

構築プロセスを非常に遅くするmultidexを使用しない場合。次のことができます。yahska言及した使用特定のGoogle Playのサービスライブラリ。ほとんどの場合、これだけが必要です。

compile 'com.google.android.gms:play-services-base:6.5.+'

利用可能なすべてのパッケージを以下に示します。

これで十分でない場合は、gradleスクリプトを使用できます。このコードをファイル「strip_play_services.gradle」に入れます

def toCamelCase(String string) {
String result = ""
string.findAll("[^\\W]+") { String word ->
    result += word.capitalize()
}
return result
}

afterEvaluate { project ->
Configuration runtimeConfiguration = project.configurations.getByName('compile')
println runtimeConfiguration
ResolutionResult resolution = runtimeConfiguration.incoming.resolutionResult
// Forces resolve of configuration
ModuleVersionIdentifier module = resolution.getAllComponents().find {
    it.moduleVersion.name.equals("play-services")
}.moduleVersion


def playServicesLibName = toCamelCase("${module.group} ${module.name} ${module.version}")
String prepareTaskName = "prepare${playServicesLibName}Library"
File playServiceRootFolder = project.tasks.find { it.name.equals(prepareTaskName) }.explodedDir


def tmpDir = new File(project.buildDir, 'intermediates/tmp')
tmpDir.mkdirs()
def libFile = new File(tmpDir, "${playServicesLibName}.marker")

def strippedClassFileName = "${playServicesLibName}.jar"
def classesStrippedJar = new File(tmpDir, strippedClassFileName)

def packageToExclude = ["com/google/ads/**",
                        "com/google/android/gms/actions/**",
                        "com/google/android/gms/ads/**",
                        // "com/google/android/gms/analytics/**",
                        "com/google/android/gms/appindexing/**",
                        "com/google/android/gms/appstate/**",
                        "com/google/android/gms/auth/**",
                        "com/google/android/gms/cast/**",
                        "com/google/android/gms/drive/**",
                        "com/google/android/gms/fitness/**",
                        "com/google/android/gms/games/**",
                        "com/google/android/gms/gcm/**",
                        "com/google/android/gms/identity/**",
                        "com/google/android/gms/location/**",
                        "com/google/android/gms/maps/**",
                        "com/google/android/gms/panorama/**",
                        "com/google/android/gms/plus/**",
                        "com/google/android/gms/security/**",
                        "com/google/android/gms/tagmanager/**",
                        "com/google/android/gms/wallet/**",
                        "com/google/android/gms/wearable/**"]

Task stripPlayServices = project.tasks.create(name: 'stripPlayServices', group: "Strip") {
    inputs.files new File(playServiceRootFolder, "classes.jar")
    outputs.dir playServiceRootFolder
    description 'Strip useless packages from Google Play Services library to avoid reaching dex limit'

    doLast {
        def packageExcludesAsString = packageToExclude.join(",")
        if (libFile.exists()
                && libFile.text == packageExcludesAsString
                && classesStrippedJar.exists()) {
            println "Play services already stripped"
            copy {
                from(file(classesStrippedJar))
                into(file(playServiceRootFolder))
                rename { fileName ->
                    fileName = "classes.jar"
                }
            }
        } else {
            copy {
                from(file(new File(playServiceRootFolder, "classes.jar")))
                into(file(playServiceRootFolder))
                rename { fileName ->
                    fileName = "classes_orig.jar"
                }
            }
            tasks.create(name: "stripPlayServices" + module.version, type: Jar) {
                destinationDir = playServiceRootFolder
                archiveName = "classes.jar"
                from(zipTree(new File(playServiceRootFolder, "classes_orig.jar"))) {
                    exclude packageToExclude
                }
            }.execute()
            delete file(new File(playServiceRootFolder, "classes_orig.jar"))
            copy {
                from(file(new File(playServiceRootFolder, "classes.jar")))
                into(file(tmpDir))
                rename { fileName ->
                    fileName = strippedClassFileName
                }
            }
            libFile.text = packageExcludesAsString
        }
    }
}

project.tasks.findAll {
    it.name.startsWith('prepare') && it.name.endsWith('Dependencies')
}.each { Task task ->
    task.dependsOn stripPlayServices
}
project.tasks.findAll { it.name.contains(prepareTaskName) }.each { Task task ->
    stripPlayServices.mustRunAfter task
}

}

次に、このスクリプトを次のようにbuild.gradleに適用します

apply plugin: 'com.android.application'
apply from: 'strip_play_services.gradle'

1

Google Play開発者サービスを使用している場合、2万以上のメソッドが追加されることをご存知かもしれません。すでに述べたように、Android Studioには特定のサービスをモジュール化して組み込むオプションがありますが、Eclipseにこだわっているユーザーはモジュール化を自分の手で行わなければなりません:(

幸い、ジョブをかなり簡単にするシェルスクリプトがあります。google play services jarディレクトリに抽出し、必要に応じて提供された.confファイルを編集して、シェルスクリプトを実行します。

その使用例はこちらです。


1

Google Play開発者サービスを使用している場合、2万以上のメソッドが追加されていることをご存知かもしれません。すでに述べたように、Android Studioには特定のサービスをモジュール化して組み込むオプションがありますが、Eclipseにこだわっているユーザーはモジュール化を自分の手で行う必要があります:(

幸い、ジョブをかなり簡単にするシェルスクリプトがあります。google play services jarディレクトリに抽出し、必要に応じて提供された.confファイルを編集して、シェルスクリプトを実行します。

その使用例はこちらです。

彼が言ったように、私はcompile 'com.google.android.gms:play-services:9.0.0'私が必要とするライブラリーだけを置き換え、それは機能しました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.