読者です 読者をやめる 読者になる 読者になる

visible true

技術的なメモを書く

Data Bindingを導入したかったけどmultidexの兼ね合いで躓いたので問題をまとめます。

※解決しました http://sys1yagi.hatenablog.com/entry/2015/06/17/190547

個人アプリにフォイフォイ導入して上手く行ったので仕事の方でも、と思ってたら思わぬ罠で挫折したので記録しておきます。

環境

  • com.android.tools.build :gradle:1.3.0-beta3
  • com.android.databinding:dataBinder:1.0-rc0

結論

android gradle pluginのmultidex処理系の改修を待つ。具体的にはData Binding用に生成したクラス群をmaindexlist.txtに含める様に修正されるのを待つ。or adt-devに参加して直す

問題

ViewPager内のFragmentで利用するArrayAdapterのViewをData Bindingに適用しようとした所、実行時に以下のエラーが発生した。

Could not find class 'android.databinding.ViewDataBinding$IncludedLayoutIndex[][]',
 referenced from method com.hoge.databinding.XXXBinding.<clinit>

java.lang.IllegalAccessError: tried to access class
 android.databinding.ViewDataBinding$IncludedLayoutIndex[][]
 from class com.hoge.databinding.XXXBinding

com.hoge.databinding.XXXBindingはData Bindingによって生成されたクラス。エラーが起こった箇所はAdapter内のXXXBinding.inflate()の部分。これを実行した時にクラッシュする。

@Override
public View getView(int position, View convertView, ViewGroup parent) {
  if(convertView == null) {
    XXXBinding binding = XXXBinding
                    .inflate(LayoutInflater.from(getContext()));
    //...
  }
  //...

原因

XXXBindingクラスはandroid.databinding.ViewDataBindingクラスを継承している。

public class XXXBinding extends android.databinding.ViewDataBinding {
  //...
}

これらのクラスがmultidexによって分断されてしまうと、この問題が起こる。

classes.dex  <- android.databinding.ViewDataBindingが入ってる
classes2.dex <- XXXBindingが入ってる

つまり以下の条件を満たすときData Bindingの利用は厳しくなる。

  1. multidexを使っている (multiDexEnabled = trueにしている)
  2. multidexの結果classes.dexが分割される (ビルドしたあとbuild/intermediates/dex/[flavor]/[variant]/みると分かる)。multiDexEnabled = trueにしていても65535問題をクリアしていればdexは分割されない。分割されていなければ問題は出ない。
  3. BindingをFragmentやArrayAdapterで使っている(Activityなら多分問題ない)

この条件を満たしていても動く場合もあるかもしれないけどいつぶっ壊れるか分からないので怖い。

なぜ分断されるのか??

multidexでdex分割を処理する時、前段でprimary dex入りするクラスを推論するフェーズがある。ここではApplicationやActivityの依存関係からアプリ起動時に必要なクラス群を抽出しているぽい。この時android.databinding.ViewDataBindingはprimary dexに必ず格納されるが、生成されたXXXBindingはprimary dexから漏れる場合がある。現状のpluginではその辺の条件が足りてないんだと思う。

primary dex入りするクラス群はbuild/intermediates/multi-dex/[flavor]/[variant]/maindexlist.txtで確認できる。maindexlist.txtに生成されたBindingクラスが書かれていなければ問題が起こる可能性がある。

対応(暫定)

完全には分かっていないが、primary dex入りするクラスが参照しているクラスもprimary dex入りするらしい。つまりandroid.intent.category.LAUNCHERなActivity(ここではMainActivityとします)がXXXBindingを参照している場合maindexlist.txtにXXXBindingが含まれる様になる。という事で以下の様なコードを書くとクラッシュせず動作する様になる。

public class MainActivity extends AppCompatActivity {
  //この記述をすると動作する
  static {
    Class clazz = XXXBinding.class;
  }
}

この対応の問題点

  • XXXBindingが増える度に書かなければならないので漏れる可能性がある
  • Proguardのoptimizeで消される可能性がある
  • 書いてもsecondary dex入りする可能性は否定できない

暫定で動くものの総合的には問題が出る条件を満たす場合Data Bindingを見送る方がよさそう。

試したけどダメだった事

その他いくつか試したけどダメだった事のメモ。

  • multidex.keepを書く
    • Multi-dex Support を使おう の辺りが参考になる。dexに--main-dex-listオプションでprimary dexに入れるクラス群を定義したファイルを渡せる。これを使えばXXXBindingをprimary dexに入れられるのではないかと考えたがここにはファイル一つしか渡せない。既にpluginが推論して生成するmaindexlist.txtが自動で渡る様になっているので無理。渡したら次はmaindexlist.txtが無視されるので爆死する。
  • XXXFragmentやXXXAdapterでstatic initializer
    • XXXActivityじゃなく、FragmentやAdapterでstatic initializerを書いてみたがダメだった。
  • multiDexEnabled以外に何かオプションないかな?
    • なさそう

終わりに

サイコーなDaba Bindingですがこういった落とし穴がありました。大規模なアプリへの適用はまだ厳しそうな印象です。目下は簡単な再現環境をつくって issue tracker にぶち込もうかと思います。グーメン。