web-dev-qa-db-ja.com

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

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

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

1)コードを縮小する

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

両方を調べて、メソッドカウントが非常に高くなる原因を見つけようとしました。 Google Drive APIは、グアバの依存関係が12,000を超えている最大の部分を占めています。 Drive API v2の合計ライブラリは23,000を超えています!

私が推測する私の質問は、私は何をすべきだと思いますか?アプリの機能としてGoogleドライブの統合を削除する必要がありますか? APIを縮小する方法はありますか(はい、私はproguardを使用しています)?複数のdexルートに行く必要がありますか(特にサードパーティのAPIを扱うのはかなり苦痛に見えます)?

90
Jared Rummler

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

65Kの参照制限について

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

Android 5.0より前のMultidexサポート

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

Android 5.0以降のMultidexサポート

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

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


Multidexサポートライブラリ

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

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

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

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


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

67
Jared Rummler

そのためにmultidexサポートライブラリを使用できます。multidexを有効にするには

1)依存関係に含める:

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

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

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

アプリにapplicationクラスがある場合は、次のように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)もしdo n'tがあればapplicationアプリケーションのクラスを登録してからAndroid.support.multidex.MultiDexApplication =マニフェストファイルのアプリケーションとして。このような:

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

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

52
Prakhar

Play Services 6.5+が役立ちます: http://Android-developers.blogspot.com/2014/12/google-play-services-and-dex-method.html

「バージョン6.5以降のGoogle Play開発者サービスでは、多数の個別の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
31
Csaba Toth

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

バージョン6.5以降では、代わりにGoogle PlayサービスAPIをアプリに選択的にコンパイルできます。たとえば、Google Fitおよび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'

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

9
akshay

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

あなたの場合は、commoninternalおよびdriveサブパッケージを除くすべてをGoogle Play Services jarからリッピングします。

7
pixel

未使用のメソッドは最終ビルドに含まれないため、proguardを使用してapkを軽量化します。 guavaでproguardを使用するために、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サポートライブラリに切り替えると、メソッドカウントが大幅に削減されます(個人的な経験に基づいて)。手順は次のとおりです。

7
petey

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

strip_play_services.sh by dextorer を使用します。

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

4
Brian White

長期的には、アプリを複数のdexに分割するのが最善の方法だと思います。

3
prmottajr

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

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

すべての利用可能なパッケージがあります APIを実行可能ファイルに選択的にコンパイルします

これで十分でない場合は、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'
2
Lemberg

マルチデックスのサポートは になるだろう この問題の公式ソリューション。詳細については、 私の答えはこちら を参照してください。

2
Alex Lipov

Google Play Servicesを使用している場合、2万以上のメソッドが追加されていることがわかります。既に述べたように、Android St​​udioには特定のサービスをモジュール化するオプションがありますが、Eclipseにこだわるユーザーはモジュール化を自分の手で行う必要があります:(

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

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

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

1
Tamir Gilany

Google Play Servicesを使用している場合、2万以上のメソッドが追加されていることがわかります。既に述べたように、Android St​​udioには特定のサービスをモジュール化するオプションがありますが、Eclipseにこだわるユーザーはモジュール化を自分の手で行う必要があります:(

幸いなことに シェルスクリプトがあります これにより、作業がかなり簡単になります。 google play services jarディレクトリに展開し、必要に応じて提供された.confファイルを編集して、シェルスクリプトを実行します。

その使用例は here です。

1
Tom