Feature Request: Android App Bundles

Just manually confirmed that it is possible to build an aab/apk with aapt2 using dx instead of d8.

1 Like

Confirmed a runtime startup crash when using D8 with multidex. The documentation on d8 is pretty limited, but here’s what I’ve found…

When MultiDex is enabled, EBuild uses the dx switch --multi-dex which the javadoc says “allows to generate several dex files if needed.” d8 does not have this switch, because it multidexes automatically when needed (I confirmed this when building in EBuild with D8).

My runtime crash is due to a NoClassDefFoundError on my class AccordanceAndroidApplication, which my manifest sets as the base Application class
<application android:name=".AccordanceAndroidApplication" ...

I did a dexdump on each of the .dex files and found that AccordanceAndroidApplication is in classes2.dex rather than classes.dex. The Multidex docs state that “If any class that’s required during startup is not provided in the primary DEX file, then your app crashes with the error java.lang.NoClassDefFoundError .”

So, it looks like EBuild needs to add the d8 switch --main-dex-list <file> when Multidex is enabled, and pass in a keep file. Here’s the docs on that switch:

–main-dex-list path

Specifies a text file that lists classes d8 should include in the main DEX file, which is typically named classes.dex . That is, when you don’t specify a list of classes using this flag, d8 does not guarentee which classes are included in the main DEX file.

Because the Android system loads the main DEX file first when starting your app, you can use this flag to prioritize certain classes at startup by compiling them into the main DEX file. This is particularly useful when supporting legacy multidex because only classes in the main DEX file are available at runtime until the legacy multidex library is loaded.

The keep file, according to the Gradle docs:

should contain one class per line, in the following format: com/example/MyClass.class

Once I get EBuild building on my machine I’ll try hardcoding a keep file in with the Application class I need to confirm that this is the solution.

Ah, so I guess when using D8, I’ll just need to always expect potentially more than one dex file to be generated, and prpovcess therm all (as I do for Multi-dev, in pre-D8). Lemme have a look at that code… (most of the android tool chain I basically “inherited” when going EBuild, so large parts of it are still a black box I merely ported, and did truly understand/review yet, though as I go thru it when changes are needed, I then do so. :wink:

Hmm. how to find what classes to keep, though? all there ones listed as Activities in the manifest?

That part is fixed.

1 Like

Gradle/Android Studio actually only provide an option to manually link a user-created file, so it relies on the developer to know which classes to keep. I suppose you could parse the manifest and automatically add any application, activities, services, etc in there, but it may be best to just let the dev add a custom txt file since they could potentially access classes through reflection, which would be near impossible to detect and would still break it if accessed too soon.

Sounds good. Re-reading that description, I understand that in “normal” cases, it should work no matter what dex file your main class is in, it just might not be as optimized as it could be. If so, that latest commit should have that fix (and I’ll add an option for a providing a custom file).

Yeah that’s a bit unclear to me. It almost sounds like it should “just work” for non-legacy mode (anything >= SDK 21) but I’m getting that crash on an sdk 29 device with a min sdk 21 project, so it does seem that even when not in legacy mode it still needs at least the Application class to be in the primary dex.

Well, the current code will only package classes.dex, so kid your class is in classes2.dex, it wont make it into the app, until you have my fix :wink:

Ah :joy: I guess I did only check the intermediate folder for dex files and not the apk :upside_down_face:

Yeah, basically D8 defaulted to the non-multi-dex path in the second step, and just took classes.dex, hardcoded. I changed it now and D8 and multi-dex use the same code (gather all generated .dex files).

D8 changes are in and merged for tomorrow’s build (UseD8 still defaults to false, and the new option is AndroidMainDexListFile; both exposed in Project settings, too. I’d appreciate if you could put this thru it’s paces, and if all is well, I’ll make UseD8 default to true (if available, of course) next week.

1 Like

I’ll have to look into this more later, but my initial build with D8 is failing on this (and related) error:

d8> Interface `com.squareup.okhttp.Callback` not found. It's needed to make sure desugaring of `com.dropbox.core.http.OkHttpRequestor$AsyncCallback` is correct. Desugaring will assume that this interface has no default method.

A quick search led me to this Stack Overflow post which initially suggested simply disabling desugaring on D8, which can be done with the --no-desugaring switch. I’ll have to look into it a bit more though, since the docs say “Use this flag only if you don’t intend to compile Java bytecode that uses Java 8 language features.” This doesn’t seem like a safe assumption to make for an external library.

I often see Java 7/8 compat stuff in Android builds solved with Gradle’s setting, but I’m not sure when exactly to use it.

compileOptions {
     sourceCompatibility JavaVersion.VERSION_1_8
     targetCompatibility JavaVersion.VERSION_1_8
 }

EDIT:
This ultimately maps into javac.

I also happened to notice in the build log that AndroidPredex task is still always using DX instead of picking up on D8. My build ultimately fails on a “no dex files found” error. I assumed this was due to the preceding Java 8 compatibility warning, but perhaps it was due to AndroidPack making D8 assumptions that weren’t true.

Just noticed the most recent commit compares file.PathExtension against *.dex when finding dex files. Should that be “dex” instead of *.dex? I’m not extremely familiar with the Elements RTL, but RTL2 doesn’t seem to expect the wildcard.

You’re right, that should be Where(f -> f.PathExtension = ".dex") — with dot, but without the asterisk. fixed.

1 Like

D8 appears to be working correctly now! Thanks Marc!

Also a note for anyone reading this thread or for EBuild documentation… After looking into this a bit more, I’ve found that the AndroidMainDexListFile setting is not necessary for apps with min sdk >= 21. Devices on 21+ run Android Runtime (ART) instead of Dalvik, which according to the docs:

At install time, ART compiles apps using the on-device dex2oat tool. This utility accepts DEX files as input and generates a compiled app executable for the target device.

In other words, on ART it doesn’t matter which dex file your classes end up in, since all .dex files are compiled into a single .oat file at install time, and ART executes this .oat file.

For multidex apps targeting minSdk < 21, you will need to follow the Android docs on supporting MultiDex and add the AndroidMainDexListFile with the classname of your custom (Multidex)Application class as described here.

Note, EBuild’s AndroidMainDexListFile is the exact equivalent of Gradle’s multiDexKeepFile. They both pass the same parameter to the same Android build tool and nothing more.

Awesome! I’ll make it the default for .2471.

cool thanx for writing this up, I’ll add this to the documentation!

1 Like

Question: would a similar file/setting be needed for classic dex in multi-dex mode, as well?

Yes. The exact option actually.

usage:
dx --dex [–debug] [–verbose] [–positions=] [–no-locals]
[–no-optimize] [–statistics] [–[no-]optimize-list=] [–no-strict]
[–keep-classes] [–output=] [–dump-to=] [–dump-width=]
[–dump-method=[*]] [–verbose-dump] [–no-files] [–core-library]
[–num-threads=] [–incremental] [–force-jumbo] [–no-warning]
[–multi-dex [–main-dex-list=< file > [–minimal-main-dex]]
[–input-list=] [–min-sdk-version=]
[–allow-all-interface-method-invokes]

Thanx. fixed to pass it for both.

I notice that I also still have to review incremental support for D8. Something for next week.

1 Like