0

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.

JHH
  • 8,567
  • 8
  • 47
  • 91
  • "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?" -- correct, that is not allowed. "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?" -- that is handled by Gradle dependency resolution. Few developers have the *source* to apache-commons directly in multiple modules of an app. – CommonsWare Dec 14 '17 at 12:10
  • "I'd rather not rename com.foo.Foobar.java to com.foo.MyRenamedFooBar.java to eliminate the conflict" -- then stop using `lib-bar`. – CommonsWare Dec 14 '17 at 12:15
  • Right. It actually makes perfect sense, and great to have that established. Problem is, I have been given both a library and a few source files from a 3rd party, and I both need to use the library and also directly use the source files for other things. So I guess I either need to rename the files, or somehow find a way to exclude them during linkage? What annoys me is I don't understand why the duplicates are accepted when simply running the app. Exactly what step of the build process is not invoked when running the app directly (except Proguard)? – JHH Dec 14 '17 at 12:16
  • "I both need to use the library and also directly use the source files for other things" -- that doesn't make sense. If `com.foo.Foobar` is the same in both places, then have `lib-foo` depend upon your `lib-bar` and get `com.foo.Foobar` that way. Or, get rid of `lib-foo` and fold its code into `app`, which already depends on `lib-bar`. "What annoys me is I don't understand why the duplicates are accepted when simply running the app" -- they shouldn't be. You're probably tripping over an IDE bug. – CommonsWare Dec 14 '17 at 12:26
  • Yeah, that makes sense. I'm apparently having a slow day. Thanks. I moved the 3rd party lib to a new module and added dependencies to it from both lib-foo and lib-bar. It does expose stuff to lib-foo that it doesn't actually need since it only needs 2 of the hundreds of files in the library, but I can live with that. If you'd write an answer stating that the duplicate files are not allowed and that it's likely an IDE bug to allow it in some situations, I'd be happy to accept it (plus file a bug on AS). – JHH Dec 14 '17 at 13:10

1 Answers1

3

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.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • A dex file is most certainly not a zip archive :) – JesusFreke Dec 14 '17 at 18:22
  • @JesusFreke: Sorry -- that'll teach me for answering questions early in the morning. Thanks! – CommonsWare Dec 14 '17 at 18:23
  • Thanks. FWIW I put an issue on Android Studio but it was immediately closed as "working as intended" (to which I disagree since I think the responder misunderstood the issue to be about APK's produced by instant run not being shareable, whereas the issue is that they are sometimes "corrupt" in a random fashion), but I'm probably not getting any further. – JHH Dec 19 '17 at 07:40