「65Kメソッドで誰でも十分」またはAndroidのDEXメソッドの制限に対処する方法

それは突然起こりました。 Android用のアプリケーションのコードを書いただけで、気に入って、そのプロセスを楽しんでいます。 追加の機能を取得し、よりシンプルなコードを作成するためのクールなライブラリを追加しました。 しかし、動作するアプリケーションの代わりに、出力は恐ろしい碑文です。



Unable to execute dex: method ID not in [0, 0xffff]: 65536





Conversion to Dalvik format failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536







そして、あなたは麻痺状態にあり、APKのDEXファイルを作成できません。 これが何であり、それをどのように修正するかはわかりません。 そして、あなたが何をするにしても、それはあなたを最も論理的な状態: PANICに導きます。



敵に会う



通常のトリックをすべて試した後(次の順序で、プロジェクトのクリーニング、IDEの再起動、コマンドラインからのビルド、ラップトップの再起動、友人への電話、問題が自然に消えることを願って10分間の休憩さえ)、残酷な真実を認めざるを得ません。 :問題はまだここにあります。 したがって、何が原因であるかを知ることは素晴らしいことです。



この問題に関するインターネット上の多くの投稿があります: ここにそれらの 1つもう1つもう 1つがあります。 ああ、 これ もこれも 。 それで何が起こったのですか? 基本的に、Dalvik 65Kメソッド制限と呼ばれることが多い(そして誤って)何かに直面しているように見えます。 簡単に説明すると( faddenに感謝):



DEXファイルでは非常に多くのメソッドを参照できますが、メソッド呼び出し命令用に持っているすべてのメモリであるため、最初の65536のみを呼び出すことができます。

[...]参照できるメソッドの数は制限されており、定義するメソッドの数ではありません。 つまり、DEXファイルに少数のメソッドしか含まれていないが、それらが一緒に70,000の外部定義されたメソッドを呼び出す場合、制限を超えます。



何がありますか? アプリケーションに記述されたメソッドまたはライブラリJARファイルに囲まれたメソッドが多すぎます。 このため、dxツールは、DEXファイル(コンパイルされたJavaクラスを含む)でこのために定義されたフィールドに収まらないため、一部のメソッドのアドレスを書き込むことができません。 そして、それが問題をDEX 65Kメソッド制限と呼ぶべき理由です。



はい、あなたは正しく理解しました。 この問題は、GoogleがDEX形式を「修正」または他の形式に置き換えることを決定するまで、Androidが新しいARTランタイムに切り替えられても消えません。







数えましょう



恐らく、65,000を超えるメソッドを貴重なAPKに埋め込んだことに驚くでしょう。 またはそうでないかもしれません。 いずれにせよ、これらのメソッドをカウントしてソースを特定する方法が必要です。



DEXファイルのヘッダーには、メソッド参照の数に関する情報が既に含まれています(明確にするために、この番号は、各メソッドの呼び出しの合計ではなく、一意のメソッド参照を表します)。 しかし、どのパッケージがこれらのメソッドの途方もない数を追加するのかを見たいとも思っています。



私が見つけた最良のツールは、 dex-method-countsと呼ばれる単純なbashスクリプトを書いたMihai Parparitaによって作成されました。 このスクリプトは非常に高速で、パッケージの名前とそのメソッドの数を一致させるXMLを生成します。 もう1つのソリューションはJake Whartonに属し、まったく同じ結果が得られますが、再帰的な性質があるため、さらに時間がかかります(Jakeもこのトピックに関する興味深い記事を書いています)。



優れたツールが手に入りましたので、小さなテストアプリケーションでテストしてみませんか? SampleAppと呼びましょう。 簡単なアクティビティに含まれるコードは次のとおりです。



 package com.whyme.example; import android.app.Activity; import android.os.Bundle; public class SampleActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); dummyMethod(); } private void dummyMethod() { System.out.println(“Oh gosh.”); } }
      
      







このライブラリはまったく使用していませんが、アプリケーションにGoogle Play Services 5.0.77も含めました。 私の行動の意味は、以下の数行で明らかにされます。 次に、dex-method-countsの出力を分析します(簡単にするために圧縮されています)。



 Read in 20332 method IDs. <root>: 20332 : 2 android: 834 […] com: 18802 google: 18788 […] whyme: 14 example: 14 dalvik: 2 system: 2 java: 624 […] javax: 5 […] org: 63 apache: 24 […] json: 39
      
      







ちょっと待って 何? アクティビティでメソッドを1つ定義しただけで、DEXファイルにはすでに20,000のメソッドがありますか?







犯人は明らかです。



Google Playサービス:さまざまな感情



Google Play開発者サービスに精通している可能性はゼロではありません。 Googleは、Android 2.3 Gingerbreadまでの多くのサービスのAPIをサポートし、6週間ごとに新しいリリースを提供しています。



ただし、すべてに価格があります。この場合、Play Servicesに搭載されている膨大な数のメソッドの形式で表示されます。 約19,000のメソッドについて話している。 そして、65536の制限により、これは、含めることができるすべてのメソッドの3分の1がすでに使い果たされていることを意味します。 行くぞ



さて、この問題の「解決策」に進む前に、少し考えが適切だと思います。 Googleの内外の一部の開発者は、「制限に直面した場合、あなたは悪い、非常に悪い開発者であり、罰せられるに値する(そして地獄で燃えなければならない)」という共通の観点を共有しています。 より公平な観点から言えば、これは、怠tooすぎるか、アプリケーションが多すぎるために、無数にライブラリをオンにしすぎたことを意味します。 個人的に、私はこの観点を共有しません。 これらは、2つの簡単なことの言い訳に過ぎないと考えています。



1.問題を認めたくない/修正したくない

2.私は制限を超えなかったので、私はクールな開発者であり、あなたは何か間違ったことをしています。



たぶん、この制限は次の理由で設定されました。「この制限は、すべての将来の世代の良いプログラマーと悪いプログラマーの間の境界線を決定します。 ”しかし、何か、私はそれを疑います。



不要なものをすべて捨てる



正義が勝利する世界では、Googleは問題の深さを認識し、巨大なPlayサービスを、アプリケーションが使用する機能に応じて含めることができるモジュールに分割することで、被害を軽減することにしました。 たとえば、アプリケーションがゲームに関連していない場合、Play Games APIをすべて含めることはまったく意味がありません。 これは、実行および実行可能な最も合理的な動きです。



ただし、Googleは問題を完全に理解していないため、独自に解決する別の方法を見つけた方がよいでしょう。 既に述べたように、Googleが絶対に必要としないAPIの部分であるgoogle-play-services.jarの部分は破棄します。



これを行うにはいくつかの方法があります。 手動で実行できますが、6週間ごとに実行する必要があることに注意してください。



JarJarライブラリを使用すると、jarファイルから目的のパッケージを削除できます。 明確でシンプル。



私と同じように古き良きコマンドラインを好むなら、私が自分のプロジェクトに使用しているスクリプトを使用できます。このスクリプトはstrip_play_services.shと呼ばれます(信じられないでしょう)。 付属のstrip.conf構成ファイルを使用して、スクリプトを制御できます。 これを使用して、無効にするPlay Servicesライブラリモジュールを選択できます。 基本的に、スクリプトは次のことを行います。



1. google-play-services.jarからコンテンツを抽出します

2. strip.confが存在するかどうかを確認し、存在する場合は構成に使用し、存在しない場合は新しいものを作成し、そこにPlay Servicesライブラリからすべてのモジュールを書き込みます

3. strip.confに基づいて、ライブラリから不要なモジュールを削除します

4. google-play-services-STRIPPED.jarファイルの残りのモジュールを再パッケージ化します



簡単なstrip.confファイルの構成を次に示します。



アクション= true

ads = true

分析= true

appindexing = true

appstate = true

auth = true

キャスト= true

共通= true

ドライブ= false

ダイナミック= true

ゲーム= false

gcm = true

アイデンティティ= true

内部= true

場所= false

maps = false

パノラマ= false

plus = true

セキュリティ= true

tagmanager = true

ウォレット= false

ウェアラブル= true




そしてそれだけです。 次に、例として、テストアプリケーションのメソッドの数を再度計算して、何が変わったのかを見てみましょう。



 Read in 11216 method IDs. <root>: 11216 [...]
      
      







これはすでに何かです。 アプリケーションのニーズに基づいて適切な構成を選択することにより、Google Play Servicesの「厚さ」を制御し、(おそらく)本当に必要なメソッドのためのスペースを空けることができます。



しかし、あなたも非常に注意する必要があります。 Play Services Libraryは(モジュール性の点で)非常によく構築されており、「maps」モジュールに影響を与えることなく「games」モジュールを安全に削除できます。 ただし、他のユーザーが使用しているモジュール(内部、単に配置)を削除すると、何も機能しません。 試行錯誤を使用してください!







他の方法はありますか?



もちろんあります。 アプリケーションでProGuardを実行すると、コードから不要なメソッドが削除され、APKのサイズが小さくなるため、役立ちます。 しかし、大きな削減を期待しないでください、これは起こりません。



別の解決策は、コードの一部を含む2番目のDEXファイルを作成することです。このファイルには、インターフェイス/リフレクションを介してアクセスし、カスタムClassLoader( 詳細 )を介してロードします。 このソリューションは、場合によってはより効果的です。 ただし、このパスは非常に重要です。



親友のダリオ・マルカートの別の解決策を見つけました。 彼はGradleタスクを作成して、不要なモジュールを削除しました。 Gradleを使用している場合は、ぜひ試してみてください!



あとがき



これはSebastiano Gottardoによるオリジナル記事の翻訳ですか[DEX] Skyは限界ですか? いいえ、65Kメソッドはです。 翻訳があなたにとって「不器用」であると思われる場合、またはテキストにスペル/構文/事実上の誤りを見つけた場合は、PMで私に書いてください。



All Articles