dexIndexOverFlowが出た場合の対処


自分はもっぱらスクリプトバインディング派なのだが
今回は途中まで出来たプロジェクトを
移管して受け継いだものだから
C++でゲームを作らなきゃいけなくなった。

それにしてもコンパイル言語で実装までするというのは
不便すぎる上に、開発効率が悪い。

特に以下の点で最悪だ

1. 変更のたびにビルドをしなければならない
2. Xcodeのマイナーアップデートでたまに未知のエラーに遭遇する
3. コンパイル言語なのにAndroidに関しては難読化しないといけない
4. Androidのコンパイル言語に対する制約が厄介すぎる(今回の内容である)
5. 気軽にプロジェクトの移植ができない

まあ文句を言えばまだまだあるが、
特に最後の問題に関しては、
現場で１日以上工数を取られたので、
とりあえず対処ができた方法をメモする

Androidではコンパイル時に
メソッドをclasses.dexってファイルに
押し込める

そして、このdexIndexOverFlowの
例外が発生する場合は、
その押し込めるメソッドの数が
65536以上に達した時だ。

ちなみに今回の問題を対処するために
たくさんのstackOverFlowのQAに
お世話になったが、
公式(?)の対策が載っているページは以下である

https://developer.android.com/studio/build/multidex.html

これらの対処法には、
以下の３つがあげられる

1. MultiDexに対応する
2. メソッドにproguardをかける
3. 使用していないライブラリを削除する

ちなみに最終的に自分がとった策は3.である

以下にその経緯をメモする

# 環境

+ mac
+ gradle2.1.1(AndroidStudioは使用しない)
+ Android 4.4 ~ 5.0

# MultiDexに対応する

具体的にどういうことかというと
classes.dexに登録されるメソッド数が
65535に達した時、
classes2.dexが生成される

試していないが恐らく
メソッド数に制限がなくなるのかもしれない

方法は以下のとおり

1. build.gradleにMultiDexを有効にする旨を記載する
2. Applicationを継承しているクラスの継承先をMultiDexApplicationに変更する

## 1. build.gradleにMultiDexを有効にする旨を記載

こちらは公式にある通り
build.gradleに追記すれば問題ない

``` build.gradle
android {
	defaultConfig {
		multiDexEnabled true
	}
}

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

## 2. Applicationを継承しているクラスの継承先をMultiDexApplicationに変更する

公式にはAndroidManifest.xmlに
MultiDexApplicationクラスを指定するとあるが、
Applicationを継承しているクラスがある場合、
その継承先をMultiDexApplicationに変更する

``` App.java
import android.context.Context;
import android.support.multidex.MultiDexApplication;

public class App extends MultiDexApplication {
	@Override
	protected void attachBaseContext(Context base) {
		super.attachBaseContext(base);
		MultiDex.install(this);
	}
}
```

# MultiDexに対応させることで発生する問題

以上でMultiDexに対応できたのだが、
ここで一つ問題が発生した

ビルドは成功するようになったのだが、
起動時にクラッシュするようになったのだ

stackOverFlowを追いかけてみると
どうやらこの問題は4.4以下
つまりはAPI20(kitkat wear)以下の端末で
発生しうる問題らしい

確認してみたところ
5.0の端末では正常に起動した

厄介な問題である

正直これ以上Androidのバージョンに依存する問題を
追いかけたくなかったので、
次の対策に早急に移ることにした

# メソッドにProguardをかける

こちらの手順はすでに先方がしてあったので
あえて省かせていただいた

正直Proguardファイルを作るのが
面倒であったということが一番だ

方法については実際にやったことがないので
よくわからなかったが、
調べた感じだとProguardを使って
使用していないメソッドを無理やり削除する
というような感じだった

# 使用していないライブラリを削除する

自分はこれで64k以下にメソッド数を
減らすことができた

この問題については場合によるが、
比較的GooglePlayServiceのライブラリが
問題になるケースが多いようだ

自分もそのライブラリを分割して
使用していないライブラリを削除することで
15kほどメソッド数を減らすことができた

こんなコードが記載されていたら要注意である

``` build.gradle
dependencies {
	compile 'com.google.android.gms:play-services:+'
}
```

ちなみにこの+という記法は
最新バージョンをとってくるという意味だ

だいたいはマイナーバージョンの
最新バージョンをとってくるような感じに記載する

それはそうと、調べたところ
このライブラリは30kほどのメソッド数をはらんでいるようだ

現在はgoogleがそれを懸念してか
ライブラリを分割することができるので
それを利用して使用していないライブラリを削除する

分割できるライブラリについては
以下の記事を参考にさせていただいた

http://qiita.com/niusounds/items/f9a0ab3cbe07878ed8f6

丸パクリだが以下がその一覧らしい

| Google Play Services API | build.gradle |
|:-------------------------|:-------------|
| Google+                  | com.google.android.gms:play-services-plus:+|
| Google Account Login, Address API |  com.google.android.gms:play-services-identity:+ |
| Google Actions, Base Client Library | com.google.android.gms:play-services-base:+ |
| Google App Indexing      | com.google.android.gms:play-services-appindexing:+ |
| Google App Invites       | com.google.android.gms:play-services-appindexing:+ |
| Google Analytics         | com.google.android.gms:play-services-analytics:+ |
| Google Cast              | com.google.android.gms:play-services-cast:+ |
| Google Cloud Messaging   | com.google.android.gms:play-services-gcm:+ |
| Google Drive             | com.google.android.gms:play-services-drive:+ |
| Google Fit               | com.google.android.gms:play-services-fitness:+ |
| Google Location, Activity Recognition, and Places | com.google.android.gms:play-services-location:+ |
| Google Maps              | com.google.android.gms:play-services-maps:+ |
| Google Mobiles Ads       | com.google.android.gms:play-services-ads:+ |
| Google Nearby            | com.google.android.gms:play-services-nearby:+ |
| Google Panorama Viewer   | com.google.android.gms:play-services-panorama:+ |
| Google Play Game Services | com.google.android.gms:play-services-games:+ |
| Safety Net               | com.google.android.gms:play-services-safetynet:+ |
| Android Pay              | com.google.android.gms:play-services-wallet:+ |
| Android Wear             | com.google.android.gms:play-services-wearable:+ |

いろいろ最適化する方法はあるみたいだが、
自分はあまり働かせる頭がないので
以下のような古典的な方法で最適化した

1. とりあえず全部のライブラリを記載してコメントアウト
2. ビルドエラーになったらどこでなったかを調べて対応するライブラリを適用
3. ビルドが通れば必要ないライブラリを削除

我ながら情けないほど古典的だし、
こればかりはちょっと静的解析である
コンパイル言語に助けられたかもしれない
(そもそもコンパイル言語でなければこんな問題は発生しなかったのだが...)

# まとめ

以上で無事オーバーフローに対応できたわけだが
やってみて気づいたことは
順番が間違えていたということ

そもそもMultiDexに対応する前に
ライブラリの最適化をするべきだった

いやしかし移管してきたばかりで
今までapkの抽出が成功していたプロジェクトで
誰がこんなエラーが発生すると予測できただろうか...

正直今回の一番の問題は
google play servicesのライブラリを
メジャーアップデートも関係なく
最新バージョンを取ってきていることであったに違いない

おそらく今までビルドしてきたときまでは
ギリギリ65kを超えていなかったが、
今になって最新バージョンにライブラリをすることで
65k超えてしまったというところだろうか

こういう時限式のバグというのは
非常に厄介だ

特に今回のような問題であれば
どこまでgit上のコミットを遡ろうと
ビルドをすることができない

本当にタチが悪い

ということで自分もこの手のバグを
埋め込まないように気をつけよう