Data BindingとMultidexの兼ね合いの問題を大体倒したので実用段階待ったなし
Data Bindingを導入したかったけどmultidexの兼ね合いで躓いたので問題をまとめます。 - visible trueで死んでましたが、設定でなんとか頑張れる事がわかったのでメモします。
結論
これらを加えるだけ。
build.gradle
android { defaultConfig { multiDexEnabled = true multiDexKeepProguard file('multi-dex-keep.txt') //<- new! } }
multi-dex-keep.txt
-keep public class * extends android.databinding.ViewDataBinding { *; }
大体解決するまで
大体解決するまでこういう事しましたというメモです。長いので読まなくてもいいです。
1. gradle pluginのソースを落とす
まずはgradle pluginでmultidexの処理している所を探す必要があるなーと思ってgradle pluginのソースを落としました。
Build Overview - Android Tools Project Site
でbranchはstudio-master-dev
を選択しました。ビルドする気でいたのでcase-sensitiveなdisk image作ってそこに持ってきました。
2. multidexを処理してるタスクを探す
multidexというか、maindexlist.txtを作ってる所を探すわけですが、よくわからないのでとりあえずmultidex
とかでgrepすると以下の場所にドンピシャっぽいタスクがあるのを見つけました。
studio-master-dev/tools/base/build-syste/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/CreateMainDexList.groovy
中身を見るとすげーそれっぽい感じ。
@TaskAction void output() { if (getAllClassesJarFile() == null) { throw new NullPointerException("No input file") } File _allClassesJarFile = getAllClassesJarFile() Set<String> mainDexClasses = callDx(_allClassesJarFile, getComponentsJarFile()) File _includeInMainDexJarFile = getIncludeInMainDexJarFile() if (_includeInMainDexJarFile != null) { mainDexClasses.addAll(callDx(_allClassesJarFile, _includeInMainDexJarFile)) } if (mainDexListFile != null) { Set<String> mainDexList = new HashSet<String>(Files.readLines(mainDexListFile, Charsets.UTF_8)) mainDexClasses.addAll(mainDexList) } String fileContent = Joiner.on(System.getProperty("line.separator")).join(mainDexClasses) Files.write(fileContent, getOutputFile(), Charsets.UTF_8) }
でもあんまり具体的な事はしてなさそうですね。mainDexClasses.addAll()
に値を渡しているcallDx()
が怪しそうです。
private Set<String> callDx(File allClassesJarFile, File jarOfRoots) { return getBuilder().createMainDexList(allClassesJarFile, jarOfRoots) }
callDx()
の中身もあんまりない...。次はgetBuilder()
が返す値を探す旅に出かけます。
3. 正体はAndroidBuilder
とりあえずcreateMainDexList
で全文検索すると以下のクラスが引っかかりました。
studio-master-dev/tools/base/build-system/builder/src/main/java/com/android/builder/core/AndroidBuilder.java
中身はこんなの。
public Set<String> createMainDexList( @NonNull File allClassesJarFile, @NonNull File jarOfRoots) throws ProcessException { BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools(); ProcessInfoBuilder builder = new ProcessInfoBuilder(); String dx = buildToolInfo.getPath(BuildToolInfo.PathId.DX_JAR); if (dx == null || !new File(dx).isFile()) { throw new IllegalStateException("dx.jar is missing"); } builder.setClasspath(dx); builder.setMain("com.android.multidex.ClassReferenceListBuilder"); builder.addArgs(jarOfRoots.getAbsolutePath()); builder.addArgs(allClassesJarFile.getAbsolutePath()); CachedProcessOutputHandler processOutputHandler = new CachedProcessOutputHandler(); mJavaProcessExecutor.execute(builder.createJavaProcess(), processOutputHandler) .rethrowFailure() .assertNormalExitValue(); String content = processOutputHandler.getProcessOutput().getStandardOutputAsString(); return Sets.newHashSet(Splitter.on('\n').split(content)); }
おや〜〜?builder.setMain("com.android.multidex.ClassReferenceListBuilder");
dx.jarの中のクラスを呼び出してますね。という事でこんな事つぶやいてます。
multidex問題追っかけてるけどどうやらdx側を直さないとだめぽいな
— 八木 (@sys1yagi) 2015, 6月 15
4. AOSPのソース持ってくる & ClassReferenceListBuilderのソース読む
という事でdxのソース読む為に持ってきます。Downloading the Source | Android Open Source Projectとかに書いてあります。とりあえずmaster
にしました。
com.android.multidex.ClassReferenceListBuilderの実装を見ると引数で渡されたjarファイルを展開して.class
を取り出してるだけっぽい事がわかります。AndroidBuilder#createMainDexList()
ではjarOfRoots.getAbsolutePath()
とallClassesJarFile.getAbsolutePath()
を渡しています。
実装を辿っていくと、これらはアプリケーションのビルド時にproject_root/module/build/intermediates/multi-dex/[flavor]/[variant]/
に生成されるcomponentClasses.jar
とallclasses.jar
である事がわかりました。
5. componentClasses.jarはどこで生成されている?
はーdexからまたpluginに戻ってきた
— 八木 (@sys1yagi) 2015, 6月 16
とりあえず./gradlew --debug assemble
でビルドプロセスを見てどのタスクで何が生成されているのかを見ます。
[INFO] [org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter] Executing task ':app:shrinkMultiDexComponents' (up-to-date check took 0.002 secs) due to: Output file //*略*//build/intermediates/multi-dex/product/debug/componentClasses.jar has changed.
するとshrinkMultiDexComponentsタスクでcomponentClasses.jar
が生成されてる事がわかりました。studio-master-dev
を検索すると/studio-master-dev/tools/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/ProGuardTaskConfigAction.java
で実行されている事がわかりました。実装を見ると以下の箇所が。
proguardComponentsTask.configuration(scope.getManifestKeepListFile());
これはcomponentClasses.jar
が生成される場所にあるmanifest_keep.txt
っぽい!このファイルの中身を見ると...
-keep class com.sys1yagi.android.hoge.Application { <init>(); void attachBaseContext(android.content.Context); } //略
という感じでproguardの設定ぽいものが書かれています。どうやらこの設定に沿ってallclasses.jar
からcomponentClasses.jar
を抽出してるっぽい事がわかりました。
6. manifest_keep.txtはどこで生成されている?
manifest_keep.txtに任意のkeep設定を差し込めれば勝てる気配がしてきました。ちなみにタスク名は以下の様に定義されてるみたいです。
ProGuardTaskConfigAction.java
@Override public String getName() { return scope.getTaskName("shrink", "MultiDexComponents"); }
"shrink"と"MultiDexComponents"の間にflavorとvariantsが入ります。という事でMultiDexComponents
で全文検索すると何か引っかかるかも?という事で検索すると/studio-master-dev/tools/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/CreateManifestKeepList.groovy
が引っかかりました。こっちのタスク名はこう
@Override String getName() { return scope.getTaskName("collect", "MultiDexComponents"); }
CreateManifestKeepList.groovyでは、AndroidManifest.xmlに定義されたコンポーネントをmanifest_keep.txt
に書き出してました。その他決め打ちでいくらかの設定を吐いてます。で、こんな実装が。
@Override void execute(CreateManifestKeepList manifestKeepListTask) { // since all the output have the same manifest, besides the versionCode, // we can take any of the output and use that. final BaseVariantOutputData output = scope.variantData.outputs.get(0) ConventionMappingHelper.map(manifestKeepListTask, "manifest") { output.getScope().getManifestOutputFile() } manifestKeepListTask.proguardFile = scope.variantConfiguration.getMultiDexKeepProguard() manifestKeepListTask.outputFile = scope.getManifestKeepListFile(); //variant.ext.collectMultiDexComponents = manifestKeepListTask }
おや〜〜? manifestKeepListTask.proguardFile = scope.variantConfiguration.getMultiDexKeepProguard()
という事でbuild.gradleに書けるのでは〜!?
で
build.gradle
android { defaultConfig { multiDexEnabled = true multiDexKeepProguard file('multi-dex-keep.txt') //<- new! } }
こうして
multi-dex-keep.txt
-keep public class * extends android.databinding.ViewDataBinding { *; }
こうじゃ
倒したのでは!?!?!?!?!?!!?!!?!?!?!?
— 八木 (@sys1yagi) 2015, 6月 17
これでmaindexlist.txt
にData Bindingによって生成されたXXXBindingクラスが出力されるようになりました!!!
おわりに
正式な設定項目なので他のケースでも何とかできそうでいいですね。
ところでC88で@mhidaka氏のAndroid本に参加します。Data Bindingについて書きまくるので買って下さい。
新たなる敵
Data Binding + Multidex倒したら「クックック奴と倒すとはな...さあやろうか」って感じで新たな問題が出てきました。テストコード側でBindingクラスに触ると死ぬ。
— 八木 (@sys1yagi) 2015, 6月 17
テストapkにも生成されたBindingクラスが入っていて、targetではtarget側のクラスをロードして?みたいなそういう感じぽい
— 八木 (@sys1yagi) 2015, 6月 17
(ヽ´ω`)…