Androidのビルド時に出るIllegalArgumentExceptionと戦う

Titaniumで作ったAndroidアプリのプロジェクトをビルドしていると

```bash
java.lang.IllegalArgumentException: already added [パッケージ名]
```

こんなエラーに遭遇することがあります。読んで字のごとく、すでに追加されたクラスをさらに追加しようとしてdexerがエラーになっているのですが、モジュールを複数利用している際にはしばしば発生します。

Titaniumには現在のところ競合したクラスを解決する手段がありません。なので、自分で解決しないといけません。まだ泣いている場合ではありません。

大きく分けると２つのアプローチがあります。例えば、使っているモジュールのlib以下にgoogle-play-services.jarのような巨大で雑にまとまったパッケージがある場合、proguardで不要なクラスをごっそり削除してしまいましょう。これで動作しなければ、取りこぼしたものがあるということになるのでコツコツ除いていきます。

# Proguardで絞る

Android SDKのtools/proguard/lib以下にproguard.jarがあります。Proguardの機能を使って、jarファイルから必要最小限のパッケージを利用するよう余計なものを削り落とします。

最初に、モジュールのソースコードからlib/以下のjarファイルをバックアップして（まあgitで管理してもいいんだけど）android sdkのtools/proguard/lib/にコピーします。自分もそちらに移動して、設定ファイルを用意します。

```bash
$ cd $ANDROID_SDK/tools/proguard/lib/
$ cp YOUR_MODULE/android/lib/your_file.jar ./
$ cp your_file.jar your_file.jar.`date +%Y%m%d`
```

設定ファイルを同じくtools/proguard/lib/以下に用意します。ここではsettings.txtとしました。[と]に囲まれた箇所はそれぞれ編集してください

```text
-injars [これから処理するjarファイル]
-outjars [処理したjarファイルを保存する際のファイル名]

-libraryjars [Android SDKのパス]/extras/android/support/v4/android-support-v4.jar
-libraryjars [Android SDKのパス]/platforms/android-21/android.jar

-dontoptimize
-dontobfuscate
-dontwarn com.google.**.R
-dontwarn com.google.**.R$*
-dontnote

-keep public class [キープするpublicなクラス、省略する場合はcom.hoge.**、複数指定する場合はカンマ区切り] {
    public protected *;
}

-keep class [キープするクラス、省略する場合はcom.hoge.**、複数指定する場合はカンマ区切り] {
    java.lang.String NULL;
}
```

キープするクラスはソースコードを参照して探すことができます。例えば

```java
import com.google.android.gms.analytics.ExceptionParser;
import com.google.android.gms.analytics.ExceptionReporter;
import com.google.android.gms.analytics.GoogleAnalytics;
import com.google.android.gms.analytics.Tracker;
```

こんな感じでpublicなクラスを利用している場合はキープします。`com.google.android.gms.analytics.**`でキープするか、個々のクラスを指定するかは、場合によりますね…

これで作成したjarファイルをモジュールのソースコードに戻して、そのモジュールがビルドできたらOKです。必ず動作確認しましょう。

モジュールの試験は必ず専用のプロジェクトを作って最小限の機能を試すようにしましょう。モジュール同士が衝突して原因を追いにくくなることがあります。

# コツコツと不要なクラスを消す

モジュールのビルド自体はそのままのjarファイルでないといけないので、プロジェクトの`modules/android/`以下のjarファイルを操作します。

(1) 各モジュールのlib以下のjarファイルを探します。

```bash
$ find modules/android | grep "lib/.*jar"
```

(2) ダブッたclassはあるかな？

jarファイルの中身は

```bash
$ jar tf your_file.jar
```

で一覧することができます。ビルド時のエラーメッセージが

```bash
$ java.lang.IllegalArgumentException: already added: L[パッケージ名];
```

であれば、[パッケージ名]に該当するものがあるか探してみましょう。

```bash
$ jar tf your_file.jar | grep "[パッケージ名]"
```

見つかったら、おめでとうございます。そいつが犯人です。

(3) jarファイルを作り直す

jarファイルは割と簡単に展開することができます。プロジェクトディレクトリ以下の該当するjarファイルを作業場所にコピーして、jarで展開します。

```bash
$ cd $PROJECT_DIR
$ mkdir tmp && cd tmp
$ cp ../modules/android/YOUR_MODULE/VERSION/lib/your_file.jar ./
$ jar -xfv your_file.jar
```

これでディレクトリが作成されてjarファイルが展開されます。

```bash
$ find . -name "*[パッケージのクラス名]*"
./com/example/android/duplicatedClass.class
```

これが重複していたクラスなので、削除しちゃいます。jarコマンドにcオプションが付くとパッケージにするので、jarファイル名とそれに含めるクラスのディレクトリを引数として指定します。

```bash
$ cp your_file.jar your_file.jar.`date +%Y%m%d`
$ rm your_file.har
$ jar -cfv your_file.jar ./com
```

こうして出来たjarファイルをプロジェクト以下、modules/android以下の該当するモジュールのlib/直下に置いてプロジェクトをビルドします。もちろん、トリッキーなやり方ではあるので、ちゃんと動作確認しましょう。別のパッケージ名でjava.lang.IllegalArgumentExceptionが出る場合は、そちらのクラスを同じ手順で除きます。

１つや２つならこれでいいのですが、大量に見つかるような場合は利用するjarファイルを`jar tf your_file.jar`で一覧を作成してdiffを取って探すほうがいいと思います。最初はプロジェクトのディレクトリで

```bash
$ CLASSNAME="KeepName"; \
find modules/android -name "*jar" | grep "lib/.*jar" | while read file; \
do GREP=`jar tf $file | grep ${CLASSNAME}`;
  if test -n "${GREP}";
  then
    echo $file
  fi;
done
```
という感じで探していたのですが、雑なパッケージがあるとti.mapなんかを使った日にはもうすごいことになります。