DalvikバイトコードのMethod数65k制限について

Androidアプリケーションにはアプリケーションのメソッド数が65kを超えられないという制限があるのだけど、その詳細を知らなかったので調べた。

参考: Does the Android ART runtime have the same method limit limitations as Dalvik?

まとめると

  • メソッド数制限の実体は(VMとしての)DalvikではなくDalvik bytecodeのinvoke系で使うmethod idが16bit intであること
  • つまり、アプリケーションで定義したメソッド数が65kを超えるかどうかではなく、ひとつのdex fileで参照しているメソッド数が65kを超えるときに問題が起きる
  • ARTもDalvik bytecodeを一旦介する以上、同じ制限をもつ

という感じか。さらに手元で試してみたところ、問題が起きるケースではdx(1)が例外を吐くので、そもそもビルドが通らなくなる。メソッド数Android 2.3系でのみインストールできなくなるという問題があったと思ったが、最近のツールだとそもそもビルドが通らなくなるのでapkのなかのメソッド数をCIで数えてチェックする必要はないのかもしれない。


以下検証実験:

まず、ひとつのdexでメソッドを沢山定義するとdx(1)が以下のようなエラーを報告する。

https://github.com/gfx/Android-TooManyMethodsInDex/tree/over65k-definitions

$ ./gradlew assembleDebug

(snip)

:app:dexDebug

trouble writing output: Too many method references: 65569; max is 65536.
You may try using --multi-dex option.
References by package:
     2 android.app
     1 android.util
     1 android.widget
 65559 com.example.gfx.over65methods
     6 java.lang
:app:dexDebug FAILED

ひとつのdexでの定義数を65k未満に抑えつつ、Android frameworkのメソッドを参照してmethod ref idが65kを突破したケースも同様:

https://github.com/gfx/Android-TooManyMethodsInDex/tree/by-external-refs

:app:dexDebug

trouble writing output: Too many method references: 65537; max is 65536.
You may try using --multi-dex option.
References by package:
     2 android.app
     5 android.util
     1 android.widget
 65523 com.example.gfx.over65methods
     6 java.lang
:app:dexDebug FAILED

外部ライブラリによってメソッド数が増えすぎる場合、以下のような例外が出ることもあるようだ:

https://github.com/gfx/Android-TooManyMethodsInDex/tree/method-id-not-in-range

:app:dexDebug

UNEXPECTED TOP-LEVEL EXCEPTION:
java.lang.IllegalArgumentException: method ID not in [0, 0xffff]: 65536
	at com.android.dx.merge.DexMerger$6.updateIndex(DexMerger.java:501)
	at com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:276)
	at com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:490)
	at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:167)
	at com.android.dx.merge.DexMerger.merge(DexMerger.java:188)
	at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:439)
	at com.android.dx.command.dexer.Main.runMonoDex(Main.java:287)
	at com.android.dx.command.dexer.Main.run(Main.java:230)
	at com.android.dx.command.dexer.Main.main(Main.java:199)
	at com.android.dx.command.Main.main(Main.java:103)
:app:dexDebug FAILED