Search code examples
androidandroid-libraryandroid-studio-3.0dex

Android Studio project runs fine but build APK fails due to "multiple dex files..."


Using Android Studio 3.0, I have an Android application project with the following layout:

app
 |
 - lib-foo
 - lib-bar

So I have an application module, using two library modules called lib-foo and lib-bar, respectively.

Now, here comes the interesting part:

  • The lib-foo module directly contains a Java source file com.foo.Foobar.java.
  • The lib-bar module contains an inlined .aar file, sourced as implementation files('libs/some-3rd-party-lib.aar'), and this library, in turn also contains com.foo.Foobar.java.

Running and debugging this project works just fine, an APK is produced and runs fine on the phone. However, when I try to do "Build APK" it fails with a

Multiple dex files define Lcom/foo/Foobar;

And yes, that file does exist in two places, but I'm not entirely sure what to think of this. I can imagine that one difference between "run" and "Build APK" would be the use of proguard and such, but is that the reason for "Build APK" failing while simply running the app directly from AS works, or what is the difference?

Is it not allowed to have the same source file in two places, in two different library modules where the file is only used internally within the module? I know that in the end, the entire set of classes is merged into one classes.jar inside the final APK, but surely there must be some kind of tolerance for duplicates of the exact same class, or else everyone would be having conflicts for things like apache-commons etc that is heavily used in lots of libaries?

Or are duplicate files in fact allowed internally in two different modules, but it's the static direct inclusion of an .aar file that messes up things?

How would I solve this issue? If I had control over the 3rd party library, I guess I could extract the common files as yet another library containing the least common denominator, and source it as a gradle dependency from both modules that need them, but I have no control over the 3rd party library. Perhaps putting the 3rd party library on Maven and source it as a versioned dependency instead of as a static file would do the trick, if this is just some bug caused by this inline .aar inclusion (heavy speculation)?

I would like to understand exactly why this fails, why it does not fail when running the app directly from AS, and how to solve it. I'd rather not rename com.foo.Foobar.java to com.foo.MyRenamedFooBar.java to eliminate the conflict.


Solution

  • A DEX file has classes in there based on their fully-qualified Java class names. As a result, when it comes to Java classes, to quote an old movie and TV show, "there can only be one" for any given fully-qualified Java class name.

    For dependencies coming from artifact repositories — your apache-commons scenario — Gradle can net that out and only include the dependency once. However, that works at the level of the artifact. So, for example, if your app depended on apache-commons and had a Java class from apache-commons in its source, there would be two copies of that class (one from the dependency, one from the source), and Gradle cannot do anything about that.

    Normally, if you have 2+ classes with the same fully-qualified class name, you will get some sort of build error. Android Studio 3.0 improved detection of this scenario a lot, as we're getting lots of new build errors from duplicate classes reported here on Stack Overflow.

    You apparently hit an edge case where Android Studio is not detecting it, at least in all scenarios (e.g., Instant Run is missing it). If you can create a reproducible test case, file an issue, and the tools team may be able to address it.